mirror of
https://github.com/danbee/chess
synced 2025-03-04 08:39:06 +00:00
Castling moves! ♔→←♖
This commit is contained in:
parent
e700efc288
commit
9e36945f8d
@ -30,22 +30,53 @@ defmodule Chess.Board do
|
||||
board["#{file},#{rank}"]
|
||||
end
|
||||
|
||||
def move_piece(board, %{"from" => from, "to" => to}) do
|
||||
[from_file, from_rank] = from
|
||||
[to_file, to_rank] = to
|
||||
def move_piece(board, %{
|
||||
"from" => [from_file, from_rank],
|
||||
"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}")
|
||||
{piece_captured, board} = Map.pop(board, "#{to_file},#{to_rank}")
|
||||
board =
|
||||
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},
|
||||
to: %{"file" => to_file, "rank" => to_rank},
|
||||
board: Map.put(board, "#{to_file},#{to_rank}", piece),
|
||||
board: board,
|
||||
piece: piece,
|
||||
piece_captured: piece_captured,
|
||||
}
|
||||
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
|
||||
%{
|
||||
"0,7" => %{"type" => "rook", "colour" => "black"},
|
||||
@ -86,6 +117,10 @@ defmodule Chess.Board do
|
||||
}
|
||||
end
|
||||
|
||||
defp to_index({file, rank}) do
|
||||
"#{file},#{rank}"
|
||||
end
|
||||
|
||||
defp indexes_to_tuples(list) do
|
||||
list
|
||||
|> Enum.map(fn({index, _piece}) -> index_to_tuple(index) end)
|
||||
|
||||
@ -15,7 +15,9 @@ defmodule Chess.Moves do
|
||||
alias Chess.Moves.Pieces.King
|
||||
|
||||
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.update(:game, Game.move_changeset(game, params))
|
||||
@ -23,7 +25,7 @@ defmodule Chess.Moves do
|
||||
|> Repo.transaction
|
||||
end
|
||||
|
||||
def available(board, {file, rank}) do
|
||||
def available(board, {file, rank}, move_list \\ []) do
|
||||
piece =
|
||||
board
|
||||
|> Board.piece({file, rank})
|
||||
@ -38,7 +40,7 @@ defmodule Chess.Moves do
|
||||
%{"type" => "knight"} ->
|
||||
Knight.moves(board, {file, rank})
|
||||
%{"type" => "king"} ->
|
||||
King.moves(board, {file, rank})
|
||||
King.moves(board, {file, rank}, move_list)
|
||||
%{"type" => "queen"} ->
|
||||
Queen.moves(board, {file, rank})
|
||||
end
|
||||
|
||||
@ -2,9 +2,11 @@ defmodule Chess.Moves.Pieces.King do
|
||||
@moduledoc false
|
||||
|
||||
alias Chess.Moves.Generator
|
||||
alias Chess.Moves.Pieces.King.Castling
|
||||
|
||||
def moves(board, {file, rank}) do
|
||||
Generator.moves(board, {file, rank}, pattern())
|
||||
def moves(board, {file, rank}, move_list) do
|
||||
Generator.moves(board, {file, rank}, pattern()) ++
|
||||
Castling.moves(board, {file, rank}, move_list)
|
||||
end
|
||||
|
||||
defp pattern do
|
||||
|
||||
102
lib/chess/moves/pieces/king/castling.ex
Normal file
102
lib/chess/moves/pieces/king/castling.ex
Normal 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
|
||||
@ -19,10 +19,4 @@ defmodule Chess.Repo.Queries do
|
||||
|> preload(:moves)
|
||||
|> Repo.get!(game_id)
|
||||
end
|
||||
|
||||
def game_for_user(user_id, game_id) do
|
||||
user_id
|
||||
|> Game.for_user_id()
|
||||
|> Repo.get!(game_id)
|
||||
end
|
||||
end
|
||||
|
||||
@ -61,12 +61,12 @@ defmodule ChessWeb.GameChannel do
|
||||
) do
|
||||
game =
|
||||
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, {
|
||||
String.to_integer(file),
|
||||
String.to_integer(rank)
|
||||
})
|
||||
}, game.moves)
|
||||
|
||||
reply = %{
|
||||
moves: Enum.map(moves, &(Tuple.to_list(&1)))
|
||||
|
||||
@ -46,10 +46,40 @@ defmodule Chess.BoardTest do
|
||||
}
|
||||
|
||||
%{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 == %{
|
||||
"5,2" => %{"type" => "queen", "colour" => "white"},
|
||||
}
|
||||
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
|
||||
|
||||
151
test/chess/moves/pieces/king/castling_test.exs
Normal file
151
test/chess/moves/pieces/king/castling_test.exs
Normal 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
|
||||
Loading…
Reference in New Issue
Block a user