Skip to content

Instantly share code, notes, and snippets.

@evadne
Last active November 5, 2023 18:36
Show Gist options
  • Save evadne/dfe9d1b891576798fe4b1285f82ecb80 to your computer and use it in GitHub Desktop.
Save evadne/dfe9d1b891576798fe4b1285f82ecb80 to your computer and use it in GitHub Desktop.
Elixir Tic Tac Toe with Minimax
defmodule TTT.Console do
def print(board) do
at = fn index ->
case elem(board, index) do
nil -> " "
:X -> "X"
:O -> "O"
end
end
IO.puts("""
#{at.(0)} | #{at.(1)} | #{at.(2)}
#{at.(3)} | #{at.(4)} | #{at.(5)}
#{at.(6)} | #{at.(7)} | #{at.(8)}
""")
end
def loop do
board = {nil, nil, nil, nil, nil, nil, nil, nil, nil}
loop(board, :X)
end
def loop(board, role) do
with :ok <- print(board),
{nil, _} <- TTT.find_move(board, role) do
case TTT.find_winner(board) do
:X -> IO.puts("X Wins")
:O -> IO.puts("O Wins")
_ -> IO.puts("Draw")
end
else
{board, _} -> loop(board, loop_role_after(role))
end
end
defp loop_role_after(role) do
case role do
:X -> :O
:O -> :X
end
end
end
defmodule TTT do
@winners [
{0, 1, 2},
{3, 4, 5},
{6, 7, 8},
{0, 3, 6},
{1, 4, 7},
{2, 5, 8},
{0, 4, 8},
{2, 4, 6}
]
@roles %{
X: %{next: :O, base: 1, operator: &>/2},
O: %{next: :X, base: -1, operator: &</2}
}
@compile {:inline, is_empty: 2}
def find_move(board, role) do
find_move(board, compute_depth(board), role)
end
def find_move(board, depth, role) do
cond do
depth == 0 || find_winner(board) -> {nil, compute_score(board)}
depth == 9 -> {put_elem(board, :rand.uniform(9) - 1, role), 0}
true -> find_move_minimax(board, depth, role)
end
end
def find_move_minimax(board, depth, role) do
for x <- 0..8, is_empty(board, x), reduce: {nil, @roles[role].base * -1000} do
{best_move, best_score} ->
proposed_move = put_elem(board, x, role)
{_, score} = find_move(proposed_move, depth - 1, @roles[role].next)
@roles[role].operator.(score, best_score) && {proposed_move, score} || {best_move, best_score}
end
end
def find_winner(board) do
Enum.reduce_while(@winners, nil, fn {a, b, c}, _ ->
case {elem(board, a), elem(board, b), elem(board, c)} do
{:X, :X, :X} -> {:halt, :X}
{:O, :O, :O} -> {:halt, :O}
_ -> {:cont, nil}
end
end)
end
def compute_depth(board) do
for x <- 0..8, is_empty(board, x), reduce: 0 do
i -> i + 1
end
end
def compute_score(board) do
cond do
role = find_winner(board) -> @roles[role].base * (1 + compute_depth(board))
true -> 0
end
end
defp is_empty(board, index) do
is_nil(elem(board, index))
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment