1
0
mirror of https://github.com/danbee/chess synced 2025-03-04 08:39:06 +00:00

Now we're correctly handling moves

This commit is contained in:
Daniel Barber 2021-07-13 18:21:12 -05:00
parent dfc3fd669e
commit 5363271b95
10 changed files with 243 additions and 144 deletions

View File

@ -2,17 +2,19 @@ defmodule Chess.Board do
@moduledoc false @moduledoc false
def transform(board) do def transform(board) do
Enum.map(0..7, fn (rank) -> Enum.map(0..7, fn rank ->
Enum.map(0..7, fn (file) -> {rank,
board Enum.map(0..7, fn file ->
|> piece({file, rank}) {file,
end) board
|> piece({file, rank})}
end)}
end) end)
end end
def search(board, %{"type" => type, "colour" => colour}) do def search(board, %{"type" => type, "colour" => colour}) do
board board
|> Enum.filter(fn({_index, piece}) -> |> Enum.filter(fn {_index, piece} ->
match?(%{"type" => ^type, "colour" => ^colour}, piece) match?(%{"type" => ^type, "colour" => ^colour}, piece)
end) end)
|> indexes_to_tuples |> indexes_to_tuples
@ -20,7 +22,7 @@ defmodule Chess.Board do
def search(board, %{"colour" => colour}) do def search(board, %{"colour" => colour}) do
board board
|> Enum.filter(fn({_index, piece}) -> |> Enum.filter(fn {_index, piece} ->
match?(%{"colour" => ^colour}, piece) match?(%{"colour" => ^colour}, piece)
end) end)
|> indexes_to_tuples |> indexes_to_tuples
@ -31,9 +33,9 @@ defmodule Chess.Board do
end end
def move_piece(board, %{ def move_piece(board, %{
"from" => [from_file, from_rank], from: {from_file, from_rank},
"to" => [to_file, to_rank] to: {to_file, to_rank}
}) do }) do
{piece, board} = Map.pop(board, to_index({from_file, from_rank})) {piece, board} = Map.pop(board, to_index({from_file, from_rank}))
{piece_captured, board} = Map.pop(board, to_index({to_file, to_rank})) {piece_captured, board} = Map.pop(board, to_index({to_file, to_rank}))
board = Map.put(board, to_index({to_file, to_rank}), piece) board = Map.put(board, to_index({to_file, to_rank}), piece)
@ -42,8 +44,8 @@ defmodule Chess.Board do
if castling_move?(piece, from_file, to_file) do if castling_move?(piece, from_file, to_file) do
board board
|> castling_move(%{ |> castling_move(%{
"from" => [from_file, from_rank], from: {from_file, from_rank},
"to" => [to_file, to_rank] to: {to_file, to_rank}
}) })
|> Map.get(:board) |> Map.get(:board)
else else
@ -55,39 +57,40 @@ defmodule Chess.Board do
to: %{"file" => to_file, "rank" => to_rank}, to: %{"file" => to_file, "rank" => to_rank},
board: board, board: board,
piece: piece, piece: piece,
piece_captured: piece_captured, piece_captured: piece_captured
} }
end end
def castling_move?(%{"type" => "king"}, 4, to_file) do def castling_move?(%{"type" => "king"}, 4, to_file) do
to_file == 2 || to_file == 6 to_file == 2 || to_file == 6
end end
def castling_move?(_, _, _), do: false def castling_move?(_, _, _), do: false
def castling_move(board, %{"from" => [4, rank], "to" => [2, _rank]}) do def castling_move(board, %{from: {4, rank}, to: {2, _rank}}) do
move_piece(board, %{ move_piece(board, %{
"from" => [0, rank], from: {0, rank},
"to" => [3, rank], to: {3, rank}
}) })
end end
def castling_move(board, %{"from" => [4, rank], "to" => [6, _rank]}) do
def castling_move(board, %{from: {4, rank}, to: {6, _rank}}) do
move_piece(board, %{ move_piece(board, %{
"from" => [7, rank], from: {7, rank},
"to" => [5, rank], to: {5, rank}
}) })
end end
def default do def default do
%{ %{
"0,7" => %{"type" => "rook", "colour" => "black"}, "0,7" => %{"type" => "rook", "colour" => "black"},
"1,7" => %{"type" => "knight", "colour" => "black"}, "1,7" => %{"type" => "knight", "colour" => "black"},
"2,7" => %{"type" => "bishop", "colour" => "black"}, "2,7" => %{"type" => "bishop", "colour" => "black"},
"3,7" => %{"type" => "queen", "colour" => "black"}, "3,7" => %{"type" => "queen", "colour" => "black"},
"4,7" => %{"type" => "king", "colour" => "black"}, "4,7" => %{"type" => "king", "colour" => "black"},
"5,7" => %{"type" => "bishop", "colour" => "black"}, "5,7" => %{"type" => "bishop", "colour" => "black"},
"6,7" => %{"type" => "knight", "colour" => "black"}, "6,7" => %{"type" => "knight", "colour" => "black"},
"7,7" => %{"type" => "rook", "colour" => "black"}, "7,7" => %{"type" => "rook", "colour" => "black"},
"0,6" => %{"type" => "pawn", "colour" => "black"}, "0,6" => %{"type" => "pawn", "colour" => "black"},
"1,6" => %{"type" => "pawn", "colour" => "black"}, "1,6" => %{"type" => "pawn", "colour" => "black"},
"2,6" => %{"type" => "pawn", "colour" => "black"}, "2,6" => %{"type" => "pawn", "colour" => "black"},
@ -96,7 +99,6 @@ defmodule Chess.Board do
"5,6" => %{"type" => "pawn", "colour" => "black"}, "5,6" => %{"type" => "pawn", "colour" => "black"},
"6,6" => %{"type" => "pawn", "colour" => "black"}, "6,6" => %{"type" => "pawn", "colour" => "black"},
"7,6" => %{"type" => "pawn", "colour" => "black"}, "7,6" => %{"type" => "pawn", "colour" => "black"},
"0,1" => %{"type" => "pawn", "colour" => "white"}, "0,1" => %{"type" => "pawn", "colour" => "white"},
"1,1" => %{"type" => "pawn", "colour" => "white"}, "1,1" => %{"type" => "pawn", "colour" => "white"},
"2,1" => %{"type" => "pawn", "colour" => "white"}, "2,1" => %{"type" => "pawn", "colour" => "white"},
@ -105,15 +107,14 @@ defmodule Chess.Board do
"5,1" => %{"type" => "pawn", "colour" => "white"}, "5,1" => %{"type" => "pawn", "colour" => "white"},
"6,1" => %{"type" => "pawn", "colour" => "white"}, "6,1" => %{"type" => "pawn", "colour" => "white"},
"7,1" => %{"type" => "pawn", "colour" => "white"}, "7,1" => %{"type" => "pawn", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"},
"1,0" => %{"type" => "knight", "colour" => "white"}, "1,0" => %{"type" => "knight", "colour" => "white"},
"2,0" => %{"type" => "bishop", "colour" => "white"}, "2,0" => %{"type" => "bishop", "colour" => "white"},
"3,0" => %{"type" => "queen", "colour" => "white"}, "3,0" => %{"type" => "queen", "colour" => "white"},
"4,0" => %{"type" => "king", "colour" => "white"}, "4,0" => %{"type" => "king", "colour" => "white"},
"5,0" => %{"type" => "bishop", "colour" => "white"}, "5,0" => %{"type" => "bishop", "colour" => "white"},
"6,0" => %{"type" => "knight", "colour" => "white"}, "6,0" => %{"type" => "knight", "colour" => "white"},
"7,0" => %{"type" => "rook", "colour" => "white"} "7,0" => %{"type" => "rook", "colour" => "white"}
} }
end end
@ -123,13 +124,13 @@ defmodule Chess.Board do
defp indexes_to_tuples(list) do defp indexes_to_tuples(list) do
list list
|> Enum.map(fn({index, _piece}) -> index_to_tuple(index) end) |> Enum.map(fn {index, _piece} -> index_to_tuple(index) end)
end end
defp index_to_tuple(index) do defp index_to_tuple(index) do
index index
|> String.split(",") |> String.split(",")
|> Enum.map(&(String.to_integer(&1))) |> Enum.map(&String.to_integer(&1))
|> List.to_tuple |> List.to_tuple()
end end
end end

View File

@ -14,11 +14,15 @@ defmodule Chess.GameState do
cond do cond do
player_checkmated?(board, colour) -> player_checkmated?(board, colour) ->
"checkmate" "checkmate"
player_stalemated?(board, colour) -> player_stalemated?(board, colour) ->
"stalemate" "stalemate"
king_in_check?(board, colour) -> king_in_check?(board, colour) ->
"check" "check"
true -> nil
true ->
nil
end end
end end
@ -36,7 +40,7 @@ defmodule Chess.GameState do
king = king =
board board
|> Board.search(%{"type" => "king", "colour" => colour}) |> Board.search(%{"type" => "king", "colour" => colour})
|> List.first |> List.first()
if is_nil(king) do if is_nil(king) do
raise "There is no #{colour} king!" raise "There is no #{colour} king!"
@ -49,7 +53,7 @@ defmodule Chess.GameState do
def player_cannot_move?(board, colour) do def player_cannot_move?(board, colour) do
board board
|> Board.search(%{"colour" => colour}) |> Board.search(%{"colour" => colour})
|> Enum.all?(fn({file, rank}) -> |> Enum.all?(fn {file, rank} ->
board board
|> piece_cannot_move?({file, rank}) |> piece_cannot_move?({file, rank})
end) end)
@ -62,9 +66,9 @@ defmodule Chess.GameState do
board board
|> Moves.available({file, rank}) |> Moves.available({file, rank})
|> Enum.all?(fn({to_file, to_rank}) -> |> Enum.all?(fn {to_file, to_rank} ->
board board
|> Board.move_piece(%{"from" => [file, rank], "to" => [to_file, to_rank]}) |> Board.move_piece(%{from: {file, rank}, to: {to_file, to_rank}})
|> Map.get(:board) |> Map.get(:board)
|> king_in_check?(piece["colour"]) |> king_in_check?(piece["colour"])
end) end)

View File

@ -19,10 +19,10 @@ defmodule Chess.Moves do
game.board game.board
|> Board.move_piece(move_params) |> Board.move_piece(move_params)
Multi.new Multi.new()
|> Multi.update(:game, Game.move_changeset(game, params)) |> Multi.update(:game, Game.move_changeset(game, params))
|> Multi.insert(:move, Ecto.build_assoc(game, :moves, params)) |> Multi.insert(:move, Ecto.build_assoc(game, :moves, params))
|> Repo.transaction |> Repo.transaction()
end end
def available(board, {file, rank}, move_list \\ []) do def available(board, {file, rank}, move_list \\ []) do
@ -33,14 +33,19 @@ defmodule Chess.Moves do
case piece do case piece do
%{"type" => "pawn"} -> %{"type" => "pawn"} ->
Pawn.moves(board, {file, rank}) Pawn.moves(board, {file, rank})
%{"type" => "rook"} -> %{"type" => "rook"} ->
Rook.moves(board, {file, rank}) Rook.moves(board, {file, rank})
%{"type" => "bishop"} -> %{"type" => "bishop"} ->
Bishop.moves(board, {file, rank}) Bishop.moves(board, {file, rank})
%{"type" => "knight"} -> %{"type" => "knight"} ->
Knight.moves(board, {file, rank}) Knight.moves(board, {file, rank})
%{"type" => "king"} -> %{"type" => "king"} ->
King.moves(board, {file, rank}, move_list) King.moves(board, {file, rank}, move_list)
%{"type" => "queen"} -> %{"type" => "queen"} ->
Queen.moves(board, {file, rank}) Queen.moves(board, {file, rank})
end end

View File

@ -18,12 +18,13 @@ defmodule Chess.Moves.Pieces.King.Castling do
[] []
end end
end end
def moves(_board, _piece, _move_list), do: [] def moves(_board, _piece, _move_list), do: []
def _moves(board, _rank, colour, move_list) do def _moves(board, _rank, colour, move_list) do
board board
|> Board.search(%{"type" => "rook", "colour" => colour}) |> Board.search(%{"type" => "rook", "colour" => colour})
|> Enum.map(fn ({file, rank}) -> |> Enum.map(fn {file, rank} ->
case file do case file do
0 -> queen_side_move(board, rank, colour, move_list) 0 -> queen_side_move(board, rank, colour, move_list)
7 -> king_side_move(board, rank, colour, move_list) 7 -> king_side_move(board, rank, colour, move_list)
@ -35,44 +36,50 @@ defmodule Chess.Moves.Pieces.King.Castling do
defp king_has_moved?(move_list, colour) do defp king_has_moved?(move_list, colour) do
move_list move_list
|> Enum.any?(fn (move) -> |> Enum.any?(fn move ->
match?(%Move{ match?(
piece: %{"type" => "king", "colour" => ^colour} %Move{
}, move) piece: %{"type" => "king", "colour" => ^colour}
},
move
)
end) end)
end end
defp queen_side_move(board, rank, colour, move_list) do defp queen_side_move(board, rank, colour, move_list) do
if queen_side_squares_empty?(board, rank) && if queen_side_squares_empty?(board, rank) &&
!queen_side_in_check?(board, rank, colour) && !queen_side_in_check?(board, rank, colour) &&
!rook_has_moved?(0, move_list, colour) do !rook_has_moved?(0, move_list, colour) do
{2, rank} {2, rank}
end end
end end
defp king_side_move(board, rank, colour, move_list) do defp king_side_move(board, rank, colour, move_list) do
if king_side_squares_empty?(board, rank) && if king_side_squares_empty?(board, rank) &&
!king_side_in_check?(board, rank, colour) && !king_side_in_check?(board, rank, colour) &&
!rook_has_moved?(7, move_list, colour) do !rook_has_moved?(7, move_list, colour) do
{6, rank} {6, rank}
end end
end end
defp rook_has_moved?(file, move_list, colour) do defp rook_has_moved?(file, move_list, colour) do
move_list move_list
|> Enum.any?(fn (move) -> |> Enum.any?(fn move ->
match?(%Move{ match?(
piece: %{"type" => "rook", "colour" => ^colour}, %Move{
from: %{"file" => ^file}, piece: %{"type" => "rook", "colour" => ^colour},
}, move) from: %{"file" => ^file}
},
move
)
end) end)
end end
defp queen_side_in_check?(board, rank, colour) do defp queen_side_in_check?(board, rank, colour) do
[{2, rank}, {3, rank}] [{2, rank}, {3, rank}]
|> Enum.any?(fn ({to_file, to_rank}) -> |> Enum.any?(fn {to_file, to_rank} ->
board board
|> Board.move_piece(%{"from" => [4, rank], "to" => [to_file, to_rank]}) |> Board.move_piece(%{from: {4, rank}, to: {to_file, to_rank}})
|> Map.get(:board) |> Map.get(:board)
|> GameState.king_in_check?(colour) |> GameState.king_in_check?(colour)
end) end)
@ -80,9 +87,9 @@ defmodule Chess.Moves.Pieces.King.Castling do
defp king_side_in_check?(board, rank, colour) do defp king_side_in_check?(board, rank, colour) do
[{5, rank}, {6, rank}] [{5, rank}, {6, rank}]
|> Enum.any?(fn ({to_file, to_rank}) -> |> Enum.any?(fn {to_file, to_rank} ->
board board
|> Board.move_piece(%{"from" => [4, rank], "to" => [to_file, to_rank]}) |> Board.move_piece(%{from: {4, rank}, to: {to_file, to_rank}})
|> Map.get(:board) |> Map.get(:board)
|> GameState.king_in_check?(colour) |> GameState.king_in_check?(colour)
end) end)

View File

@ -14,14 +14,14 @@ defmodule Chess.Store.Game do
alias Chess.Store.User alias Chess.Store.User
schema "games" do schema "games" do
field :board, :map, default: Board.default() field(:board, :map, default: Board.default())
field :turn, :string, default: "white" field(:turn, :string, default: "white")
field :state, :string field(:state, :string)
belongs_to :user, User belongs_to(:user, User)
belongs_to :opponent, User, references: :id belongs_to(:opponent, User, references: :id)
has_many :moves, Move has_many(:moves, Move)
timestamps() timestamps()
end end
@ -55,15 +55,17 @@ defmodule Chess.Store.Game do
end end
def for_user_id(user_id) do def for_user_id(user_id) do
from game in Game, from(game in Game,
where: game.user_id == ^user_id, where: game.user_id == ^user_id,
or_where: game.opponent_id == ^user_id or_where: game.opponent_id == ^user_id
)
end end
def check_game_state(changeset) do def check_game_state(changeset) do
changeset changeset
|> put_change( |> put_change(
:state, GameState.state(changeset.changes.board, changeset.changes.turn) :state,
GameState.state(changeset.changes.board, changeset.changes.turn)
) )
end end
@ -78,6 +80,7 @@ defmodule Chess.Store.Game do
changeset changeset
end end
end end
def validate_king_in_check(changeset, _, _), do: changeset def validate_king_in_check(changeset, _, _), do: changeset
def ordered(query) do def ordered(query) do

View File

@ -32,7 +32,7 @@ defmodule ChessWeb.GameChannel do
board: Board.transform(game.board), board: Board.transform(game.board),
turn: game.turn, turn: game.turn,
state: game.state, state: game.state,
moves: MoveList.transform(game.moves), moves: MoveList.transform(game.moves)
} }
socket socket
@ -57,6 +57,7 @@ defmodule ChessWeb.GameChannel do
update_opponent(socket, game) update_opponent(socket, game)
{:noreply, socket} {:noreply, socket}
{:error, :game, changeset, _} -> {:error, :game, changeset, _} ->
{message, _} = changeset.errors[:board] {message, _} = changeset.errors[:board]
@ -65,21 +66,26 @@ defmodule ChessWeb.GameChannel do
end end
def handle_in( def handle_in(
"game:get_available_moves", "game:get_available_moves",
%{"square" => [file, rank]}, %{"square" => [file, rank]},
socket socket
) do ) do
game = game =
socket.assigns.user_id socket.assigns.user_id
|> Queries.game_with_moves(socket.assigns.game_id) |> Queries.game_with_moves(socket.assigns.game_id)
moves = Moves.available(game.board, { moves =
String.to_integer(file), Moves.available(
String.to_integer(rank) game.board,
}, game.moves) {
String.to_integer(file),
String.to_integer(rank)
},
game.moves
)
reply = %{ reply = %{
moves: Enum.map(moves, &(Tuple.to_list(&1))) moves: Enum.map(moves, &Tuple.to_list(&1))
} }
{:reply, {:ok, reply}, socket} {:reply, {:ok, reply}, socket}
@ -88,27 +94,29 @@ defmodule ChessWeb.GameChannel do
def update_opponent(socket, game) do def update_opponent(socket, game) do
opponent_id = opponent_id =
opponent(game, socket.assigns.user_id).id opponent(game, socket.assigns.user_id).id
|> Integer.to_string |> Integer.to_string()
send_update(socket) send_update(socket)
"game:#{game.id}" "game:#{game.id}"
|> Presence.list |> Presence.list()
|> case do |> case do
%{^opponent_id => _} -> %{^opponent_id => _} ->
nil nil
_ -> _ ->
socket socket
|> Emails.opponent_moved_email(game) |> Emails.opponent_moved_email(game)
|> Mailer.deliver_later |> Mailer.deliver_later()
end end
end end
def track_presence(socket) do def track_presence(socket) do
{:ok, _} = Presence.track(socket, socket.assigns.user_id, %{ {:ok, _} =
user_id: socket.assigns.user_id, Presence.track(socket, socket.assigns.user_id, %{
online_at: inspect(System.system_time(:second)) user_id: socket.assigns.user_id,
}) online_at: inspect(System.system_time(:second))
})
socket socket
|> push("presence_state", Presence.list(socket)) |> push("presence_state", Presence.list(socket))
@ -116,8 +124,8 @@ defmodule ChessWeb.GameChannel do
def convert_params(%{"from" => from, "to" => to}) do def convert_params(%{"from" => from, "to" => to}) do
%{ %{
"from" => Enum.map(from, &(String.to_integer(&1))), "from" => Enum.map(from, &String.to_integer(&1)),
"to" => Enum.map(to, &(String.to_integer(&1))), "to" => Enum.map(to, &String.to_integer(&1))
} }
end end
@ -130,7 +138,7 @@ defmodule ChessWeb.GameChannel do
board: Board.transform(game.board), board: Board.transform(game.board),
turn: game.turn, turn: game.turn,
state: game.state, state: game.state,
moves: MoveList.transform(game.moves), moves: MoveList.transform(game.moves)
} }
ChessWeb.Endpoint.broadcast("game:#{game.id}", "game:update", payload) ChessWeb.Endpoint.broadcast("game:#{game.id}", "game:update", payload)

View File

@ -1,4 +1,4 @@
<div class="board board--black-to-move board--player-is-<%= player_colour(@user, @game) %>"> <div class="board board--<%= @game.turn %>-to-move board--player-is-<%= player_colour(@user, @game) %>">
<div class="board__rank-labels"> <div class="board__rank-labels">
<div class="board__label">1</div> <div class="board__label">1</div>
<div class="board__label">2</div> <div class="board__label">2</div>
@ -22,14 +22,14 @@
</div> </div>
<div class="board__body"> <div class="board__body">
<%= for rank <- 0..7 do %> <%= for {rank, row} <- @board do %>
<div class="board__row"> <div class="board__row">
<%= for file <- 0..7 do %> <%= for {file, piece} <- row do %>
<%= render ChessWeb.SquareView, <%= render ChessWeb.SquareView,
"square.html", "square.html",
rank: rank, rank: rank,
file: file, file: file,
piece: @game.board["#{file},#{rank}"], piece: piece,
selected: {file, rank} == @selected, selected: {file, rank} == @selected,
available: {file, rank} in @available %> available: {file, rank} in @available %>
<% end %> <% end %>

View File

@ -5,6 +5,7 @@ defmodule ChessWeb.BoardLive do
alias Chess.Store.Game alias Chess.Store.Game
alias Chess.Repo alias Chess.Repo
alias Chess.Board alias Chess.Board
alias Chess.Moves
import Chess.Auth, only: [get_user!: 1] import Chess.Auth, only: [get_user!: 1]
@ -19,11 +20,24 @@ defmodule ChessWeb.BoardLive do
Game.for_user(user) Game.for_user(user)
|> Repo.get!(game_id) |> Repo.get!(game_id)
{:ok, assign(socket, game: game, user: user, selected: nil, available: [])} {:ok, assign(socket, default_assigns(game, user))}
end end
def handle_event("click", %{"rank" => rank, "file" => file}, socket) do def handle_event("click", %{"rank" => rank, "file" => file}, socket) do
{:noreply, socket |> handle_click(file, rank)} {
:noreply,
socket |> handle_click(String.to_integer(file), String.to_integer(rank))
}
end
defp default_assigns(game, user) do
%{
board: Board.transform(game.board),
game: game,
user: user,
selected: nil,
available: []
}
end end
defp handle_click(socket, file, rank) do defp handle_click(socket, file, rank) do
@ -34,27 +48,56 @@ defmodule ChessWeb.BoardLive do
colour = ChessWeb.GameView.player_colour(user, game) colour = ChessWeb.GameView.player_colour(user, game)
assigns = assigns =
case socket.assigns do if colour == game.turn do
%{:selected => nil} -> case socket.assigns do
case Board.piece(board, {file, rank}) do %{selected: nil} ->
%{"colour" => ^colour} -> handle_selection(board, colour, file, rank)
[{:selected, selected(file, rank)}]
_ -> _ ->
[] handle_move(socket.assigns, file, rank)
end end
_ ->
[{:selected, nil}]
end end
assign(socket, assigns) assign(socket, assigns)
end end
defp selected(file, rank) do defp handle_selection(board, colour, file, rank) do
{ case Board.piece(board, {file, rank}) do
String.to_integer(file), %{"colour" => ^colour} ->
String.to_integer(rank) [
} {:selected, {file, rank}},
{:available, Moves.available(board, {file, rank})}
]
_ ->
[]
end
end
defp handle_move(
%{game: game, available: available, selected: selected},
file,
rank
) do
if {file, rank} in available do
new_game =
game
|> Moves.make_move(%{from: selected, to: {file, rank}})
|> case do
{:ok, %{game: new_game}} ->
new_game
end
new_board = Board.transform(new_game.board)
[
{:selected, nil},
{:available, []},
{:board, new_board},
{:game, new_game}
]
else
[{:selected, nil}, {:available, []}]
end
end end
end end

View File

@ -6,35 +6,62 @@ defmodule Chess.BoardTest do
alias Chess.Board alias Chess.Board
test "returns a piece from the board" do test "returns a piece from the board" do
board = Board.default board = Board.default()
expected_piece = %{"type" => "pawn", "colour" => "white"} expected_piece = %{"type" => "pawn", "colour" => "white"}
assert Board.piece(board, {4, 1}) == expected_piece assert Board.piece(board, {4, 1}) == expected_piece
end end
test "finds pieces on the board" do test "finds pieces on the board" do
board = Board.default board = Board.default()
piece = %{"type" => "pawn", "colour" => "white"} piece = %{"type" => "pawn", "colour" => "white"}
expected_result = [ expected_result = [
{0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}, {5, 1}, {6, 1}, {7, 1}, {0, 1},
{1, 1},
{2, 1},
{3, 1},
{4, 1},
{5, 1},
{6, 1},
{7, 1}
] ]
assert Board.search(board, piece) == expected_result assert Board.search(board, piece) == expected_result
end end
test "finds pieces on the board with a partial search" do test "finds pieces on the board with a partial search" do
board = Board.default board = Board.default()
piece = %{"colour" => "white"} piece = %{"colour" => "white"}
expected_result = [
{0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}, {5, 1}, {6, 1}, {7, 1}, expected_result =
{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0}, {7, 0}, [
] |> Enum.sort {0, 1},
{1, 1},
{2, 1},
{3, 1},
{4, 1},
{5, 1},
{6, 1},
{7, 1},
{0, 0},
{1, 0},
{2, 0},
{3, 0},
{4, 0},
{5, 0},
{6, 0},
{7, 0}
]
|> Enum.sort()
assert Board.search(board, piece) == expected_result assert Board.search(board, piece) == expected_result
end end
test "finds a single piece on the board" do test "finds a single piece on the board" do
board = Board.default board = Board.default()
piece = %{"type" => "king", "colour" => "black"} piece = %{"type" => "king", "colour" => "black"}
assert Board.search(board, piece) == [{4, 7}] assert Board.search(board, piece) == [{4, 7}]
@ -42,44 +69,41 @@ defmodule Chess.BoardTest do
test "moves a piece" do test "moves a piece" do
board = %{ board = %{
"3,0" => %{"type" => "queen", "colour" => "white"}, "3,0" => %{"type" => "queen", "colour" => "white"}
} }
%{board: new_board} = %{board: new_board} = Board.move_piece(board, %{from: {3, 0}, to: {5, 2}})
Board.move_piece(board, %{"from" => [3, 0], "to" => [5, 2]})
assert new_board == %{ assert new_board == %{
"5,2" => %{"type" => "queen", "colour" => "white"}, "5,2" => %{"type" => "queen", "colour" => "white"}
} }
end end
test "can perform a castling move on the kings side" do test "can perform a castling move on the kings side" do
board = %{ board = %{
"4,0" => %{"type" => "king", "colour" => "white"}, "4,0" => %{"type" => "king", "colour" => "white"},
"7,0" => %{"type" => "rook", "colour" => "white"}, "7,0" => %{"type" => "rook", "colour" => "white"}
} }
%{board: new_board} = %{board: new_board} = Board.move_piece(board, %{from: {4, 0}, to: {6, 0}})
Board.move_piece(board, %{"from" => [4, 0], "to" => [6, 0]})
assert new_board == %{ assert new_board == %{
"6,0" => %{"type" => "king", "colour" => "white"}, "6,0" => %{"type" => "king", "colour" => "white"},
"5,0" => %{"type" => "rook", "colour" => "white"}, "5,0" => %{"type" => "rook", "colour" => "white"}
} }
end end
test "can perform a castling move on the queens side" do test "can perform a castling move on the queens side" do
board = %{ board = %{
"4,0" => %{"type" => "king", "colour" => "white"}, "4,0" => %{"type" => "king", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"}, "0,0" => %{"type" => "rook", "colour" => "white"}
} }
%{board: new_board} = %{board: new_board} = Board.move_piece(board, %{from: {4, 0}, to: {2, 0}})
Board.move_piece(board, %{"from" => [4, 0], "to" => [2, 0]})
assert new_board == %{ assert new_board == %{
"2,0" => %{"type" => "king", "colour" => "white"}, "2,0" => %{"type" => "king", "colour" => "white"},
"3,0" => %{"type" => "rook", "colour" => "white"}, "3,0" => %{"type" => "rook", "colour" => "white"}
} }
end end
end end

View File

@ -20,6 +20,7 @@ defmodule Chess.Store.GameTest do
opponent_id: opponent.id, opponent_id: opponent.id,
turn: "white" turn: "white"
} }
changeset = Game.changeset(%Game{}, attrs) changeset = Game.changeset(%Game{}, attrs)
assert changeset.valid? assert changeset.valid?
@ -33,6 +34,7 @@ defmodule Chess.Store.GameTest do
opponent_id: 2, opponent_id: 2,
turn: "white" turn: "white"
} }
changeset = Game.changeset(%Game{}, attrs) changeset = Game.changeset(%Game{}, attrs)
assert changeset.valid? assert changeset.valid?
@ -81,18 +83,20 @@ defmodule Chess.Store.GameTest do
user = insert(:user) user = insert(:user)
opponent = insert(:opponent) opponent = insert(:opponent)
game = insert(:game, %{ game =
board: Board.default, insert(:game, %{
user_id: user.id, board: Board.default(),
opponent_id: opponent.id, user_id: user.id,
}) opponent_id: opponent.id
})
move_params = %{"from" => [4, 1], "to" => [4, 3]} move_params = %{from: {4, 1}, to: {4, 3}}
changeset = Game.move_changeset( changeset =
game, Game.move_changeset(
Board.move_piece(game.board, move_params) game,
) Board.move_piece(game.board, move_params)
)
assert {:ok, new_game} = Repo.update(changeset) assert {:ok, new_game} = Repo.update(changeset)
assert new_game.turn == "black" assert new_game.turn == "black"