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

Castling moves! ♔→←♖

This commit is contained in:
Daniel Barber 2018-05-18 11:44:08 -04:00
parent e700efc288
commit 9e36945f8d
Signed by: danbarber
GPG Key ID: 931D8112E0103DD8
8 changed files with 336 additions and 20 deletions

View File

@ -30,22 +30,53 @@ defmodule Chess.Board do
board["#{file},#{rank}"] board["#{file},#{rank}"]
end end
def move_piece(board, %{"from" => from, "to" => to}) do def move_piece(board, %{
[from_file, from_rank] = from "from" => [from_file, from_rank],
[to_file, to_rank] = to "to" => [to_file, to_rank]
}) do
{piece, board} = Map.pop(board, to_index({from_file, from_rank}))
{piece_captured, board} = Map.pop(board, to_index({to_file, to_rank}))
board = Map.put(board, to_index({to_file, to_rank}), piece)
{piece, board} = Map.pop(board, "#{from_file},#{from_rank}") board =
{piece_captured, board} = Map.pop(board, "#{to_file},#{to_rank}") if castling_move?(piece, from_file, to_file) do
board
|> castling_move(%{
"from" => [from_file, from_rank],
"to" => [to_file, to_rank]
})
|> Map.get(:board)
else
board
end
%{ %{
from: %{"file" => from_file, "rank" => from_rank}, from: %{"file" => from_file, "rank" => from_rank},
to: %{"file" => to_file, "rank" => to_rank}, to: %{"file" => to_file, "rank" => to_rank},
board: Map.put(board, "#{to_file},#{to_rank}", piece), 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
to_file == 2 || to_file == 6
end
def castling_move?(_, _, _), do: false
def castling_move(board, %{"from" => [4, rank], "to" => [2, _rank]}) do
move_piece(board, %{
"from" => [0, rank],
"to" => [3, rank],
})
end
def castling_move(board, %{"from" => [4, rank], "to" => [6, _rank]}) do
move_piece(board, %{
"from" => [7, rank],
"to" => [5, rank],
})
end
def default do def default do
%{ %{
"0,7" => %{"type" => "rook", "colour" => "black"}, "0,7" => %{"type" => "rook", "colour" => "black"},
@ -86,6 +117,10 @@ defmodule Chess.Board do
} }
end end
defp to_index({file, rank}) do
"#{file},#{rank}"
end
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)

View File

@ -15,7 +15,9 @@ defmodule Chess.Moves do
alias Chess.Moves.Pieces.King alias Chess.Moves.Pieces.King
def make_move(game, move_params) do def make_move(game, move_params) do
params = Board.move_piece(game.board, move_params) params =
game.board
|> 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))
@ -23,7 +25,7 @@ defmodule Chess.Moves do
|> Repo.transaction |> Repo.transaction
end end
def available(board, {file, rank}) do def available(board, {file, rank}, move_list \\ []) do
piece = piece =
board board
|> Board.piece({file, rank}) |> Board.piece({file, rank})
@ -38,7 +40,7 @@ defmodule Chess.Moves do
%{"type" => "knight"} -> %{"type" => "knight"} ->
Knight.moves(board, {file, rank}) Knight.moves(board, {file, rank})
%{"type" => "king"} -> %{"type" => "king"} ->
King.moves(board, {file, rank}) 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

@ -2,9 +2,11 @@ defmodule Chess.Moves.Pieces.King do
@moduledoc false @moduledoc false
alias Chess.Moves.Generator alias Chess.Moves.Generator
alias Chess.Moves.Pieces.King.Castling
def moves(board, {file, rank}) do def moves(board, {file, rank}, move_list) do
Generator.moves(board, {file, rank}, pattern()) Generator.moves(board, {file, rank}, pattern()) ++
Castling.moves(board, {file, rank}, move_list)
end end
defp pattern do defp pattern do

View File

@ -0,0 +1,102 @@
defmodule Chess.Moves.Pieces.King.Castling do
@moduledoc false
alias Chess.Board
alias Chess.GameState
alias Chess.Store.Move
def moves(board, {4, rank}, move_list) when rank == 0 or rank == 7 do
colour =
board
|> Board.piece({4, rank})
|> Map.get("colour")
if not king_has_moved?(move_list, colour) do
board
|> _moves(rank, colour, move_list)
else
[]
end
end
def moves(_board, _piece, _move_list), do: []
def _moves(board, _rank, colour, move_list) do
board
|> Board.search(%{"type" => "rook", "colour" => colour})
|> Enum.map(fn ({file, rank}) ->
case file do
0 -> queen_side_move(board, rank, colour, move_list)
7 -> king_side_move(board, rank, colour, move_list)
_ -> nil
end
end)
|> Enum.reject(&is_nil(&1))
end
defp king_has_moved?(move_list, colour) do
move_list
|> Enum.any?(fn (move) ->
match?(%Move{
piece: %{"type" => "king", "colour" => ^colour}
}, move)
end)
end
defp queen_side_move(board, rank, colour, move_list) do
if queen_side_squares_empty?(board, rank) &&
!queen_side_in_check?(board, rank, colour) &&
!rook_has_moved?(0, move_list, colour) do
{2, rank}
end
end
defp king_side_move(board, rank, colour, move_list) do
if king_side_squares_empty?(board, rank) &&
!king_side_in_check?(board, rank, colour) &&
!rook_has_moved?(7, move_list, colour) do
{6, rank}
end
end
defp rook_has_moved?(file, move_list, colour) do
move_list
|> Enum.any?(fn (move) ->
match?(%Move{
piece: %{"type" => "rook", "colour" => ^colour},
from: %{"file" => ^file},
}, move)
end)
end
defp queen_side_in_check?(board, rank, colour) do
[{2, rank}, {3, rank}]
|> Enum.any?(fn ({to_file, to_rank}) ->
board
|> Board.move_piece(%{"from" => [4, rank], "to" => [to_file, to_rank]})
|> Map.get(:board)
|> GameState.king_in_check?(colour)
end)
end
defp king_side_in_check?(board, rank, colour) do
[{5, rank}, {6, rank}]
|> Enum.any?(fn ({to_file, to_rank}) ->
board
|> Board.move_piece(%{"from" => [4, rank], "to" => [to_file, to_rank]})
|> Map.get(:board)
|> GameState.king_in_check?(colour)
end)
end
defp queen_side_squares_empty?(board, rank) do
[{1, rank}, {2, rank}, {3, rank}]
|> Enum.map(&Board.piece(board, &1))
|> Enum.all?(&is_nil(&1))
end
defp king_side_squares_empty?(board, rank) do
[{5, rank}, {6, rank}]
|> Enum.map(&Board.piece(board, &1))
|> Enum.all?(&is_nil(&1))
end
end

View File

@ -19,10 +19,4 @@ defmodule Chess.Repo.Queries do
|> preload(:moves) |> preload(:moves)
|> Repo.get!(game_id) |> Repo.get!(game_id)
end end
def game_for_user(user_id, game_id) do
user_id
|> Game.for_user_id()
|> Repo.get!(game_id)
end
end end

View File

@ -61,12 +61,12 @@ defmodule ChessWeb.GameChannel do
) do ) do
game = game =
socket.assigns.current_user_id socket.assigns.current_user_id
|> Queries.game_for_user(socket.assigns.game_id) |> Queries.game_with_moves(socket.assigns.game_id)
moves = Moves.available(game.board, { moves = Moves.available(game.board, {
String.to_integer(file), String.to_integer(file),
String.to_integer(rank) String.to_integer(rank)
}) }, game.moves)
reply = %{ reply = %{
moves: Enum.map(moves, &(Tuple.to_list(&1))) moves: Enum.map(moves, &(Tuple.to_list(&1)))

View File

@ -46,10 +46,40 @@ defmodule Chess.BoardTest do
} }
%{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
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"7,0" => %{"type" => "rook", "colour" => "white"},
}
%{board: new_board} =
Board.move_piece(board, %{"from" => [4, 0], "to" => [6, 0]})
assert new_board == %{
"6,0" => %{"type" => "king", "colour" => "white"},
"5,0" => %{"type" => "rook", "colour" => "white"},
}
end
test "can perform a castling move on the queens side" do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"},
}
%{board: new_board} =
Board.move_piece(board, %{"from" => [4, 0], "to" => [2, 0]})
assert new_board == %{
"2,0" => %{"type" => "king", "colour" => "white"},
"3,0" => %{"type" => "rook", "colour" => "white"},
}
end
end end

View File

@ -0,0 +1,151 @@
defmodule Chess.Moves.Pieces.King.CastlingTest do
use Chess.DataCase
alias Chess.Moves
alias Chess.Store.Move
test "king can move two spaces to castle with the king side rook" do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"7,0" => %{"type" => "rook", "colour" => "white"},
}
moves = Moves.available(board, {4, 0})
expected_moves = Enum.sort([
{3, 0}, {5, 0}, {3, 1}, {4, 1}, {5, 1},
{6, 0},
])
assert Enum.sort(moves) == expected_moves
end
test "king can move two spaces to castle with the queen side rook" do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"},
}
moves = Moves.available(board, {4, 0})
expected_moves = Enum.sort([
{3, 0}, {5, 0}, {3, 1}, {4, 1}, {5, 1},
{2, 0},
])
assert Enum.sort(moves) == expected_moves
end
test "cannot castle if a piece is in the way" do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"3,0" => %{"type" => "queen", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"},
}
moves = Moves.available(board, {4, 0})
expected_moves = Enum.sort([
{5, 0}, {3, 1}, {4, 1}, {5, 1},
])
assert Enum.sort(moves) == expected_moves
end
test "cannot castle if it would result in the king being in check" do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"},
"2,7" => %{"type" => "queen", "colour" => "black"},
}
moves = Moves.available(board, {4, 0})
expected_moves = Enum.sort([
{3, 0}, {5, 0}, {3, 1}, {4, 1}, {5, 1},
])
assert Enum.sort(moves) == expected_moves
end
test "cannot castle if the king moves through a space that is attacked" do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"},
"3,7" => %{"type" => "queen", "colour" => "black"},
}
moves = Moves.available(board, {4, 0})
expected_moves = Enum.sort([
{3, 0}, {5, 0}, {3, 1}, {4, 1}, {5, 1},
])
assert Enum.sort(moves) == expected_moves
end
test "cannot castle if the king has moved" do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"},
}
move_list = [
%Move{
from: %{"file" => 4, "rank" => 0},
to: %{"file" => 4, "rank" => 1},
piece: %{"type" => "king", "colour" => "white"}
},
%Move{
from: %{"file" => 4, "rank" => 1},
to: %{"file" => 4, "rank" => 0},
piece: %{"type" => "king", "colour" => "white"}
},
]
moves = Moves.available(board, {4, 0}, move_list)
expected_moves = Enum.sort([
{3, 0}, {5, 0}, {3, 1}, {4, 1}, {5, 1},
])
assert Enum.sort(moves) == expected_moves
end
test "cannot castle if the queen side rook has moved" do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"},
}
move_list = [
%Move{
from: %{"file" => 0, "rank" => 0},
to: %{"file" => 0, "rank" => 1},
piece: %{"type" => "rook", "colour" => "white"}
},
%Move{
from: %{"file" => 0, "rank" => 1},
to: %{"file" => 0, "rank" => 0},
piece: %{"type" => "rook", "colour" => "white"}
},
]
moves = Moves.available(board, {4, 0}, move_list)
expected_moves = Enum.sort([
{3, 0}, {5, 0}, {3, 1}, {4, 1}, {5, 1},
])
assert Enum.sort(moves) == expected_moves
end
test "cannot castle if the king side rook has moved" do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"7,0" => %{"type" => "rook", "colour" => "white"},
}
move_list = [
%Move{
from: %{"file" => 7, "rank" => 0},
to: %{"file" => 7, "rank" => 1},
piece: %{"type" => "rook", "colour" => "white"}
},
%Move{
from: %{"file" => 7, "rank" => 1},
to: %{"file" => 7, "rank" => 0},
piece: %{"type" => "rook", "colour" => "white"}
},
]
moves = Moves.available(board, {4, 0}, move_list)
expected_moves = Enum.sort([
{3, 0}, {5, 0}, {3, 1}, {4, 1}, {5, 1},
])
assert Enum.sort(moves) == expected_moves
end
end