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}"]
|
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)
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
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)
|
|> 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
|
||||||
|
|||||||
@ -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)))
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
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