Packages

The essentials of Tic-tac-toe extracted into a library.

Current section

Files

Jump to
xo src xo@internal@ai.erl
Raw

src/xo@internal@ai.erl

-module(xo@internal@ai).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-export([get_random_move/2, get_smart_moves/2]).
-export_type([state/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.
?MODULEDOC(false).
-type state() :: {state, integer(), list({integer(), integer()})}.
-file("src/xo/internal/ai.gleam", 10).
?DOC(false).
-spec get_random_move(
list({{integer(), integer()}, xo@internal@mark:mark()}),
prng@seed:seed()
) -> {ok, {{integer(), integer()}, prng@seed:seed()}} | {error, nil}.
get_random_move(Board, Seed) ->
case xo@internal@board:open_positions(Board) of
[Pos | Rest] ->
_pipe = prng@random:uniform(Pos, Rest),
_pipe@1 = prng@random:step(_pipe, Seed),
{ok, _pipe@1};
[] ->
{error, nil}
end.
-file("src/xo/internal/ai.gleam", 87).
?DOC(false).
-spec score(xo@internal@referee:outcome()) -> integer().
score(Outcome) ->
case Outcome of
{win, _, _} ->
2;
{draw, _} ->
1
end.
-file("src/xo/internal/ai.gleam", 68).
?DOC(false).
-spec negamax(
list({{integer(), integer()}, xo@internal@mark:mark()}),
xo@internal@mark:mark(),
xo@internal@mark:mark(),
integer()
) -> integer().
negamax(Board, Turn, Prev_turn, Color) ->
case xo@internal@referee:decide(Board, Prev_turn) of
none ->
Final_value = begin
_pipe = Board,
_pipe@1 = xo@internal@board:open_positions(_pipe),
gleam@list:fold(
_pipe@1,
-3,
fun(Value, Pos) ->
Next_board = xo@internal@board:put(Board, Pos, Turn),
gleam@int:max(
Value,
Color * negamax(
Next_board,
Prev_turn,
Turn,
- Color
)
)
end
)
end,
Color * Final_value;
{some, Outcome} ->
- (Color * score(Outcome))
end.
-file("src/xo/internal/ai.gleam", 37).
?DOC(false).
-spec find_best_moves(
list({{integer(), integer()}, xo@internal@mark:mark()}),
xo@internal@mark:mark(),
xo@internal@mark:mark(),
list({integer(), integer()})
) -> list({integer(), integer()}).
find_best_moves(Board, Turn, Prev_turn, Open_positions) ->
case xo@internal@referee:decide(Board, Prev_turn) of
none ->
Final_state = begin
_pipe = Open_positions,
gleam@list:fold(
_pipe,
{state, -3, []},
fun(State, Pos) ->
Next_board = xo@internal@board:put(Board, Pos, Turn),
Next_value = negamax(Next_board, Prev_turn, Turn, -1),
case Next_value > erlang:element(2, State) of
true ->
{state, Next_value, [Pos]};
false ->
case Next_value =:= erlang:element(2, State) of
true ->
_record = State,
{state,
erlang:element(2, _record),
[Pos | erlang:element(3, State)]};
false ->
State
end
end
end
)
end,
lists:reverse(erlang:element(3, Final_state));
_ ->
[]
end.
-file("src/xo/internal/ai.gleam", 20).
?DOC(false).
-spec get_smart_moves(
list({{integer(), integer()}, xo@internal@mark:mark()}),
xo@internal@mark:mark()
) -> list({integer(), integer()}).
get_smart_moves(Board, Mark) ->
Open_positions = xo@internal@board:open_positions(Board),
case erlang:length(Open_positions) of
0 ->
Open_positions;
1 ->
Open_positions;
9 ->
Open_positions;
_ ->
find_best_moves(
Board,
Mark,
xo@internal@mark:swap(Mark),
Open_positions
)
end.