Skip to main content

README.md

# Riichi

Work in progress Riichi Mahjong engine in pure Elixir.

The documentation can be found at <https://riichi.hexdocs.pm>.

## Installation

The package can be installed by adding `riichi` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:riichi, "~> 0.2"}
  ]
end
```

## Quick start

1. Create a ruleset. You can use one of the `default_*` helpers or construct `%Riichi.Rules{}` directly.

```elixir
rules = Riichi.Rules.default_four_player()
```

2. Initialize the engine.

```elixir
{events, state} = Riichi.new(rules)
```

This will create a new game with a random wall.
A second argument can be passed to `new` to initialize the game with a pre-generated wall (see `Riichi.Wall`).
The engine is pure, so you'll need to keep the updated `state` after every action.
For usage in actual games you'll want to wrap it in a `GenServer`.

3. Choose an action to perform.

The `events` are a list of `Riichi.Event.t()` structs representing what happened in the game.

The engine will progress the state until a player input is needed, so the output will frequently contain more than one event.
The last event in the list will have a `valid_actions` field containing a map of `%{Actor.t() => [Riichi.Action.t()]}`.
Each player that has a corresponding entry in `valid_actions` **must** choose one action and send it back.

```elixir
last_event = Enum.at(events, -1)
# suppose an event like this actually exists in the output
[%Riichi.Action.Discard{} = discard_action | _] = last_event.valid_actions[:actor1]
{:ok, {new_events, new_state}} = Riichi.process_action(state, discard_action)
```

4. Personalize the event list.

This event list contains complete information and **isn't meant for sending to players directly**.
When using this library for implementing an actual game, you need to be careful to not accidentally send information that's meant to be hidden.
For that, first split the event list into multiple, obscured views, one per player, like so:

```elixir
# note that this doesn't need access to the full game state - only the rules
views = Riichi.personalize_events(rules, events)
# views is %{Actor.t() => [Event.t()]}
```

5. Repeat action submission until the game is over.

The `Riichi.Event.GameEnd` event marks the end of the game.

You may deal with clients that take too long to respond by calling either:

- `Riichi.force_continue(state)` which sends the highest priority action for each actor who hasn't submitted an action. This prefers skipping, then calling ron/tsumo when available, then discarding the drawn tile.
- `Riichi.skip(state)` which sends `Riichi.Action.Skip` for actors that can perform it.

## Other functionality

Most of the intended public API surface lives in the `Riichi` and `Riichi.Rules` modules.
There are however other modules you may find useful:

- `Riichi.Tile` - tile parsing, sorting and comparison
- `Riichi.Hand` and `Riichi.Hand.Meld` - mahjong hand data structures and operations
- `Riichi.Decomposer` - hand interpretation and wait detection
- `Riichi.Wall` - wall generation, shuffling and utilities for operating on the wall
- `Riichi.Scoring` - hand scoring

### Scoring example

```elixir
import Riichi.Tile, only: :sigils

hand =
  [ ~t"1m", ~t"1m", ~t"3m", ~t"3m", ~t"5m", ~t"5m",
    ~t"7m", ~t"7m", ~t"9m", ~t"9m", ~t"2p", ~t"2p", ~t"4p" ]
  |> Riichi.Hand.new()

input = %Riichi.Scoring.Input{
  rules: Riichi.Rules.default_four_player(),
  event: %Riichi.Event.Discard{actor: :actor2, tile: ~t"4p"},
  decompositions: Riichi.Decomposer.decompose(hand),
  dora_indicators: [],
  ura_dora_indicators: [],
  dealer?: true,
  riichi: %Riichi.Player.RiichiFlags{ippatsu?: true},
  first_chance?: false,
  last_turn?: true,
  after_a_kan?: false,
  round_wind: :east,
  seat_wind: :east
}

%Riichi.Scoring{han: 5, fu: 25, value: {:ron, 12000}} = Riichi.Scoring.calculate(input)
```

## Design and implementation goals

- Pure Elixir, no NIFs.
- Immutable state machines with no side-effects.
- Simple algorithms with no binary blobs or precomputed tables.
- Rich output format by default. Game clients should not need to reproduce the engine.
- [WIP] Compatible with a multitude of other log formats.

## License

AGPL-3.0-only.