-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.