README.md

# TamerlaneHelpers

Helper library for building Tamerlane card game rules engines.

## Installation

Add `tamerlane_helpers` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:tamerlane_helpers, "~> 0.1.0"}
  ]
end
```

## Quick Start

```elixir
defmodule MyGame.Rules do
  @behaviour TamerlaneHelpers.RulesEngine

  alias TamerlaneHelpers.Helpers, as: H
  alias TamerlaneHelpers.Response

  @impl true
  def config do
    %{
      "game" => %{"name" => "My Game", "min_players" => 2, "max_players" => 4},
      "deck" => "52",
      "stacks" => [%{"id" => "discard", "label" => "Discard", "layout" => "pile"}]
    }
  end

  @impl true
  def init(player_ids) do
    %{
      "players" => Enum.map(player_ids, &%{"id" => &1, "meta" => %{}}),
      "state" => %{"phase" => "deal"}
    }
  end

  @impl true
  def next(state, players, action) do
    hand = H.hand_for(players, state["turn"])
    prompt = H.build_prompt(state["turn"], hand, name: "play", count: 1)
    decorations = H.build_decorations([{state["turn"], "turn", "Your Turn"}])

    response = Response.new([], state, prompt: prompt, decorations: decorations)
    {:ok, Response.to_map(response)}
  end
end
```

## Modules

### `TamerlaneHelpers.Helpers`

Core helper functions:

- **Player Management**: `hand_for/2`, `seating_from/2`, `next_player/2`, `other_player/2`, `player_ids/1`
- **Card Operations**: `card_value/1`, `card_suit/1`, `cards_of_suit/2`
- **Events**: `draw/2`, `draw_to_stack/2`, `move_cards/2`, `reset_cards/0`, `start_new_hand/1`
- **Locations**: `stack/1`, `hand/1`
- **Prompts**: `build_prompt/3`
- **Decorations**: `build_decorations/1`
- **State**: `increment_in/2`, `build_infos/2`
- **Scoring**: `game_over?/2`, `winners/1`
- **Turn Validation**: `extract_action/1`, `turn?/2`, `validate_turn/2`
- **Utilities**: `hand_empty_after_play?/3`

### `TamerlaneHelpers.Response`

Response struct for `next/3` return values. State must be a map with a `"phase"` key:

```elixir
state = %{"phase" => "play", "turn" => "Alice"}
response = Response.new(events, state,
  prompt: prompt,
  decorations: decorations,
  infos: infos
)
{:ok, Response.to_map(response)}
```

### `TamerlaneHelpers.TrickTaking`

Helpers for trick-taking games using Parlett's notation:

- `playable_cards/3` - Filter hand to legally playable cards
- `trick_winner/2` - Determine who won a trick
- `lead_suit/1` - Get the led suit from trick cards
- `follows_suit?/2` - Check if a card follows the led suit
- `heads_trick?/3` - Check if a card would win the current trick

```elixir
# Standard Whist: follow suit or play any card
playable = TrickTaking.playable_cards(state, hand, rules: [:f, :r])

# Tarock: must trump if void in led suit
playable = TrickTaking.playable_cards(state, hand,
  rules: [:f, :t, :r],
  trump_suit: "trumps"
)

# Determine trick winner
{winner, card} = TrickTaking.trick_winner(trick_cards, trump_suit: "spades")
```

### `TamerlaneHelpers.RulesEngine`

Behaviour that rules engines must implement:

```elixir
@callback config() :: map()
@callback init([String.t()]) :: map()
@callback next(map(), [map()], map() | nil) :: {:ok, map()}
```

## License

MIT