Current section
Files
Jump to
Current section
Files
src/xo.erl
-module(xo).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-export([next_turn/1, start/1, play_again/2, get_random_move/2, play/2, get_smart_moves/1, map/2, to_string/1, to_state/1]).
-export_type([player/0, game/0, play_error/0, rules/0, state/0, outcome/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type player() :: x | o.
-opaque game() :: {playing,
player(),
player(),
list({{integer(), integer()}, xo@internal@mark:mark()})} |
{game_over,
player(),
player(),
list({{integer(), integer()}, xo@internal@mark:mark()}),
xo@internal@referee:outcome()}.
-type play_error() :: {out_of_bounds, {integer(), integer()}} |
{occupied, {integer(), integer()}} |
game_already_ended.
-type rules() :: {rules, boolean(), boolean()}.
-type state() :: {state, player(), player(), outcome()}.
-type outcome() :: {win,
player(),
list({{integer(), integer()},
{integer(), integer()},
{integer(), integer()}})} |
{draw, player()} |
undecided.
-file("src/xo.gleam", 17).
-spec next_turn(player()) -> player().
next_turn(Player) ->
case Player of
x ->
o;
o ->
x
end.
-file("src/xo.gleam", 32).
?DOC(" Start a new game such that the given player plays first.\n").
-spec start(player()) -> game().
start(Player) ->
{playing, Player, Player, []}.
-file("src/xo.gleam", 85).
?DOC(" Reset the game while respecting the given rules.\n").
-spec play_again(game(), rules()) -> game().
play_again(Game, Rules) ->
case Game of
{playing, First, _, _} ->
start(First);
{game_over, First@1, Turn, _, Outcome} ->
case Outcome of
{win, _, _} ->
case erlang:element(2, Rules) of
true ->
start(Turn);
false ->
start(next_turn(Turn))
end;
{draw, _} ->
case erlang:element(3, Rules) of
true ->
start(next_turn(Turn));
false ->
start(First@1)
end
end
end.
-file("src/xo.gleam", 150).
-spec tile_to_string(gleam@option:option(player())) -> binary().
tile_to_string(Tile) ->
case Tile of
{some, x} ->
<<"x"/utf8>>;
{some, o} ->
<<"o"/utf8>>;
none ->
<<"."/utf8>>
end.
-file("src/xo.gleam", 160).
-spec get_random_move(game(), prng@seed:seed()) -> {ok,
{{integer(), integer()}, prng@seed:seed()}} |
{error, nil}.
get_random_move(Game, Seed) ->
case Game of
{playing, _, _, Board} ->
xo@internal@ai:get_random_move(Board, Seed);
_ ->
{error, nil}
end.
-file("src/xo.gleam", 176).
-spec player_to_mark(player()) -> xo@internal@mark:mark().
player_to_mark(Player) ->
case Player of
x ->
x;
o ->
o
end.
-file("src/xo.gleam", 47).
-spec play(game(), {integer(), integer()}) -> {ok, game()} |
{error, play_error()}.
play(Game, Pos) ->
case Game of
{playing, First, Turn, Board} ->
case xo@internal@board:in_bounds(Pos) of
true ->
case xo@internal@board:is_open(Board, Pos) of
true ->
Mark = player_to_mark(Turn),
Next_board = xo@internal@board:put(Board, Pos, Mark),
case xo@internal@referee:decide(Next_board, Mark) of
none ->
{ok,
{playing,
First,
next_turn(Turn),
Next_board}};
{some, Outcome} ->
{ok,
{game_over,
First,
Turn,
Next_board,
Outcome}}
end;
false ->
{error, {occupied, Pos}}
end;
false ->
{error, {out_of_bounds, Pos}}
end;
_ ->
{error, game_already_ended}
end.
-file("src/xo.gleam", 167).
-spec get_smart_moves(game()) -> list({integer(), integer()}).
get_smart_moves(Game) ->
case Game of
{playing, _, Turn, Board} ->
xo@internal@ai:get_smart_moves(Board, player_to_mark(Turn));
_ ->
[]
end.
-file("src/xo.gleam", 183).
-spec mark_to_player(xo@internal@mark:mark()) -> player().
mark_to_player(Mark) ->
case Mark of
x ->
x;
o ->
o
end.
-file("src/xo.gleam", 139).
-spec map(
game(),
fun(({integer(), integer()}, gleam@option:option(player())) -> GVK)
) -> list(GVK).
map(Game, F) ->
xo@internal@board:map(
erlang:element(4, Game),
fun(Pos, Mark_tile) ->
F(Pos, gleam@option:map(Mark_tile, fun mark_to_player/1))
end
).
-file("src/xo.gleam", 145).
-spec to_string(game()) -> binary().
to_string(Game) ->
_pipe = map(Game, fun(_, Tile) -> tile_to_string(Tile) end),
gleam@string:concat(_pipe).
-file("src/xo.gleam", 190).
-spec location_to_line(xo@internal@referee:location()) -> {{integer(),
integer()},
{integer(), integer()},
{integer(), integer()}}.
location_to_line(Location) ->
case Location of
r1 ->
{{0, 0}, {0, 1}, {0, 2}};
r2 ->
{{1, 0}, {1, 1}, {1, 2}};
r3 ->
{{2, 0}, {2, 1}, {2, 2}};
c1 ->
{{0, 0}, {1, 0}, {2, 0}};
c2 ->
{{0, 1}, {1, 1}, {2, 1}};
c3 ->
{{0, 2}, {1, 2}, {2, 2}};
d1 ->
{{0, 0}, {1, 1}, {2, 2}};
d2 ->
{{0, 2}, {1, 1}, {2, 0}}
end.
-file("src/xo.gleam", 122).
-spec to_state(game()) -> state().
to_state(Game) ->
case Game of
{playing, First, Turn, _} ->
{state, First, Turn, undecided};
{game_over, First@1, Turn@1, _, Outcome} ->
{state, First@1, Turn@1, case Outcome of
{win, Mark, Locations} ->
{win,
mark_to_player(Mark),
gleam@list:map(Locations, fun location_to_line/1)};
{draw, Mark@1} ->
{draw, mark_to_player(Mark@1)}
end}
end.