Packages

Briscola card game, rules, and simulation utilities.

Current section

Files

Jump to
briscola lib strategy sim.ex
Raw

lib/strategy/sim.ex

defmodule Briscola.Strategy.Simulator do
@moduledoc """
A module for simulating games of Briscola using different strategies.
"""
alias Briscola.Game
alias Briscola.Strategy.Simulator
defstruct [:game, :strategies, :log, :on_message]
@type t() :: %__MODULE__{
game: Game.t(),
strategies: list(module()),
log: list(log_message()),
on_message: (log_message() -> any())
}
@type log_message() ::
{:game_over, [Briscola.Player.t()]}
| {:trick_winner, integer()}
| {:player_turn, integer(), Briscola.Card.t()}
| {:start, Briscola.Game.t()}
@type new_options() :: [on_message: (Game.t(), log_message() -> any())]
@doc """
Create a new simulator with a game and a list of strategies.
Player 0 will use the first strategy, player 1 the second, and so on.
"""
@spec new([module()], new_options()) :: t()
def new(game, strategies, opts \\ [])
def new(game, strategies, _opts) when length(game.players) != length(strategies) do
raise ArgumentError, "Number of strategies must match the number of players"
end
def new(game, strategies, opts) do
message_handler =
Keyword.get(opts, :on_message, Keyword.get(opts, :on_message, fn _, _ -> nil end))
%Simulator{
game: game,
strategies: strategies,
log: [{:start, game}],
on_message: message_handler
}
end
@doc """
Simulate until the game is over.
"""
def run(sim) do
case List.first(sim.log) do
{:game_over, _} -> sim
_ -> run(sim_turn(sim))
end
end
@doc """
Simulate a single turn in the game.
That may mean playing a card, scoring a trick then redealing, or ending the game.
"""
def sim_turn(%Simulator{} = sim) do
game = sim.game
strategies = sim.strategies
log = sim.log
{game, event} =
cond do
Game.game_over?(game) ->
{game, {:game_over, Game.leaders(game)}}
Game.should_score_trick?(game) ->
{:ok, game, trick_winner} = Game.score_trick(game)
event = {:trick_winner, trick_winner}
game =
case Game.redeal(game) do
{:error, :not_enough_cards} -> game
g -> g
end
{game, event}
# Play the card chosen by the strategy
true ->
player_index = game.action_on
strategy = Enum.at(strategies, player_index)
card_index = strategy.choose_card(game, player_index)
played_card = Enum.at(Enum.at(game.players, player_index).hand, card_index)
event =
{:player_turn, player_index, played_card}
{:ok, game} = Game.play(game, card_index)
{game, event}
end
log = [event | log]
sim.on_message.(game, event)
case event do
{:game_over, _} ->
%Simulator{sim | log: log}
_ ->
%Simulator{sim | game: game, log: log}
end
end
end