Skip to content

Instantly share code, notes, and snippets.

@robmerrell
Created November 29, 2014 23:31
Show Gist options
  • Save robmerrell/1cea79b5a3d553834d8c to your computer and use it in GitHub Desktop.
Save robmerrell/1cea79b5a3d553834d8c to your computer and use it in GitHub Desktop.
Elixir Quiz TicTacToe solution
# solution for http://elixirquiz.github.io/2014-11-22-tic-tac-toe-part-1-the-game.html
defmodule Game do
defstruct board: [["_", "_", "_"], ["_", "_", "_"], ["_", "_", "_"]], # 3 x 3
current_player_pid: "",
previous_player_pid: ""
def draw_board(%Game{board: board}) do
Enum.each board, &IO.puts/1
end
def can_place_tile(%Game{board: board}, row, col) do
Enum.at(board, row) |> Enum.at(col) == "_"
end
def place_tile_on_board(%Game{board: board}, row, col, tile) do
new_row = Enum.at(board, row)|> List.replace_at(col, tile)
List.replace_at(board, row, new_row)
end
def check_for_win(%Game{board: board}) do
case board do
[[x, x, x], _, _] -> x != "_"
[_, [x, x, x], _] -> x != "_"
[_, _, [x, x, x]] -> x != "_"
[[x, _, _], [x, _, _], [x, _, _]] -> x != "_"
[[_, x, _], [_, x, _], [_, x, _]] -> x != "_"
[[_, _, x], [_, _, x], [_, _, x]] -> x != "_"
[[x, _, _], [_, x, _], [_, _, x]] -> x != "_"
[[_, _, x], [_, x, _], [x, _, _]] -> x != "_"
_ -> false
end
end
def check_board_full(%Game{board: board}) do
total = Enum.reduce board, 0, fn(row, acc) ->
acc + Enum.count row, &(&1 != "_")
end
total == 9
end
end
defmodule Player do
def start_link(tile) do
Agent.start_link fn ->
:random.seed(:erlang.now())
tile
end
end
def tile(player) do
Agent.get player, &(&1)
end
def take_turn(player) do
Agent.get player, fn(p) ->
IO.puts "#{p} is taking their turn"
row = :random.uniform(3)-1
col = :random.uniform(3)-1
{row, col}
end
end
def celebrate(player) do
Agent.get player, fn(tile) ->
IO.puts "#{tile} won! \\o/"
end
end
end
defmodule TicTacToe do
def start_link(player1, player2) do
Agent.start_link fn ->
%Game{current_player_pid: player1, previous_player_pid: player2}
end
end
def play_game(game_pid) do
Agent.update game_pid, fn(game) ->
# let the player take a turn
{row, col} = _player_input(game)
tile = Player.tile(game.current_player_pid)
new_board = Game.place_tile_on_board(game, row, col, tile)
# update the game board
updated_game = %Game{game |
board: new_board,
current_player_pid: game.previous_player_pid,
previous_player_pid: game.current_player_pid}
Game.draw_board(updated_game)
updated_game
end
# determine if we are done playing
done = Agent.get game_pid, fn(game) ->
won = Game.check_for_win(game) # 3 in a row
if won, do: Player.celebrate(game.previous_player_pid) # prev because users have been switched
board_full = Game.check_board_full(game)
if board_full && !won, do: IO.puts "Nobody won :("
won || board_full
end
if !done, do: play_game(game_pid)
end
defp _player_input(game) do
{row, col} = Player.take_turn(game.current_player_pid)
if !Game.can_place_tile(game, row, col) do
IO.puts "#{row}, #{col} already occupied. Trying again..."
_player_input(game)
else
{row, col}
end
end
end
{:ok, player1} = Player.start_link("x")
{:ok, player2} = Player.start_link("o")
{:ok, game_pid} = TicTacToe.start_link(player1, player2)
TicTacToe.play_game(game_pid)
@BennyHallett
Copy link

This is cool. I like the use of reduce to determine if the board is full or not, I used Enum.any?/2 in my implementation. Looking forward to the next part.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment