Created
November 29, 2014 23:31
-
-
Save robmerrell/1cea79b5a3d553834d8c to your computer and use it in GitHub Desktop.
Elixir Quiz TicTacToe solution
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.