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

Determine whether a piece is being attacked

This also refactors a bunch of the movement generation code to simplify
things and make it more flexible. This allows us to use the same code to
check pieces under attack.

* Still need to add pawns
This commit is contained in:
Daniel Barber 2018-03-17 23:05:43 -04:00
parent 474860a004
commit 18aa9faab4
Signed by: danbarber
GPG Key ID: 931D8112E0103DD8
8 changed files with 153 additions and 78 deletions

View File

@ -4,33 +4,9 @@ defmodule Chess.Moves.Bishop do
alias Chess.Moves.Generator alias Chess.Moves.Generator
def moves(board, {file, rank}) do def moves(board, {file, rank}) do
moves_northeast(board, {file, rank}) ++ Generator.moves(board, {file, rank}, {1, 1}) ++
moves_southeast(board, {file, rank}) ++ Generator.moves(board, {file, rank}, {1, -1}) ++
moves_northwest(board, {file, rank}) ++ Generator.moves(board, {file, rank}, {-1, 1}) ++
moves_southwest(board, {file, rank}) Generator.moves(board, {file, rank}, {-1, -1})
end
defp moves_northeast(board, {file, rank}) do
board["#{file},#{rank}"]
|> Map.get("colour")
|> Generator.moves(board, {file, rank}, {1, 1})
end
defp moves_southeast(board, {file, rank}) do
board["#{file},#{rank}"]
|> Map.get("colour")
|> Generator.moves(board, {file, rank}, {1, -1})
end
defp moves_northwest(board, {file, rank}) do
board["#{file},#{rank}"]
|> Map.get("colour")
|> Generator.moves(board, {file, rank}, {-1, 1})
end
defp moves_southwest(board, {file, rank}) do
board["#{file},#{rank}"]
|> Map.get("colour")
|> Generator.moves(board, {file, rank}, {-1, -1})
end end
end end

View File

@ -1,12 +1,25 @@
defmodule Chess.Moves.Generator do defmodule Chess.Moves.Generator do
@moduledoc false @moduledoc """
Generates moves for a piece. We can generate moves in a straight line,
and these can be combined to make an entire pieces allowed moves. Or we
can generate moves based on a pattern of vectors. The knight works
like this.
"""
# `movement` is either a vector (bishop, rook, queen)
# or a pattern (king, knight)
def moves(board, {file, rank}, movement) do
board["#{file},#{rank}"]
|> Map.get("colour")
|> _moves(board, {file, rank}, movement)
end
# Move generation for pieces that move in straight lines # Move generation for pieces that move in straight lines
def moves(_colour, _board, {0, _rank}, {-1, _}), do: [] def _moves(_colour, _board, {0, _rank}, {-1, _}), do: []
def moves(_colour, _board, {_file, 0}, {_, -1}), do: [] def _moves(_colour, _board, {_file, 0}, {_, -1}), do: []
def moves(_colour, _board, {7, _rank}, {1, _}), do: [] def _moves(_colour, _board, {7, _rank}, {1, _}), do: []
def moves(_colour, _board, {_file, 7}, {_, 1}), do: [] def _moves(_colour, _board, {_file, 7}, {_, 1}), do: []
def moves(colour, board, {file, rank}, {fv, rv}) do def _moves(colour, board, {file, rank}, {fv, rv}) do
next_square = {file + fv, rank + rv} next_square = {file + fv, rank + rv}
cond do cond do
can_capture_piece?(colour, board, next_square) -> can_capture_piece?(colour, board, next_square) ->
@ -14,22 +27,22 @@ defmodule Chess.Moves.Generator do
obstruction?(colour, board, next_square) -> obstruction?(colour, board, next_square) ->
[] []
true -> true ->
[next_square | moves(colour, board, next_square, {fv, rv})] [next_square | _moves(colour, board, next_square, {fv, rv})]
end end
end end
# Move generation for pieces that follow a pattern # Move generation for pieces that follow a pattern
def moves(_colour, _board, {_file, _rank}, []), do: [] def _moves(_colour, _board, {_file, _rank}, []), do: []
def moves(colour, board, {file, rank}, [{fv, rv} | moves]) do def _moves(colour, board, {file, rank}, [{fv, rv} | moves]) do
move_square = {file + fv, rank + rv} move_square = {file + fv, rank + rv}
cond do cond do
outside_board?(move_square) || outside_board?(move_square) ||
obstruction?(colour, board, move_square) -> obstruction?(colour, board, move_square) ->
moves(colour, board, {file, rank}, moves) _moves(colour, board, {file, rank}, moves)
can_capture_piece?(colour, board, move_square) -> can_capture_piece?(colour, board, move_square) ->
[move_square | moves(colour, board, {file, rank}, moves)] [move_square | _moves(colour, board, {file, rank}, moves)]
true -> true ->
[move_square | moves(colour, board, {file, rank}, moves)] [move_square | _moves(colour, board, {file, rank}, moves)]
end end
end end

View File

@ -4,9 +4,7 @@ defmodule Chess.Moves.King do
alias Chess.Moves.Generator alias Chess.Moves.Generator
def moves(board, {file, rank}) do def moves(board, {file, rank}) do
board["#{file},#{rank}"] Generator.moves(board, {file, rank}, patterns())
|> Map.get("colour")
|> Generator.moves(board, {file, rank}, patterns())
end end
defp patterns do defp patterns do

View File

@ -4,12 +4,10 @@ defmodule Chess.Moves.Knight do
alias Chess.Moves.Generator alias Chess.Moves.Generator
def moves(board, {file, rank}) do def moves(board, {file, rank}) do
board["#{file},#{rank}"] Generator.moves(board, {file, rank}, pattern())
|> Map.get("colour")
|> Generator.moves(board, {file, rank}, patterns())
end end
defp patterns do def pattern do
[{1, 2}, {2, 1}, {-1, 2}, {-2, 1}, {1, -2}, {2, -1}, {-1, -2}, {-2, -1}] [{1, 2}, {2, 1}, {-1, 2}, {-2, 1}, {1, -2}, {2, -1}, {-1, -2}, {-2, -1}]
end end
end end

View File

@ -18,14 +18,14 @@ defmodule Chess.Moves.Pawn do
|> Map.get("colour") |> Map.get("colour")
colour colour
|> _capture_moves(board, {file, rank}, patterns(colour)) |> _capture_moves(board, {file, rank}, pattern(colour))
end end
defp patterns("white") do def pattern("white") do
[{-1, 1}, {1, 1}] [{-1, 1}, {1, 1}]
end end
defp patterns("black") do def pattern("black") do
[{-1, -1}, {1, -1}] [{-1, -1}, {1, -1}]
end end

55
lib/chess/moves/piece.ex Normal file
View File

@ -0,0 +1,55 @@
defmodule Chess.Moves.Piece do
@moduledoc false
alias Chess.Moves.Generator
alias Chess.Moves.Knight
def attacked?(board, {file, rank}) do
attacked_by_rook_or_queen?(board, {file, rank}) ||
attacked_by_bishop_or_queen?(board, {file, rank}) ||
attacked_by_knight?(board, {file, rank})
end
defp attacked_by_rook_or_queen?(board, {file, rank}) do
_attacked?(board, {file, rank}, {0, 1}, ["rook", "queen"]) ||
_attacked?(board, {file, rank}, {0, -1}, ["rook", "queen"]) ||
_attacked?(board, {file, rank}, {1, 0}, ["rook", "queen"]) ||
_attacked?(board, {file, rank}, {-1, 0}, ["rook", "queen"])
end
defp attacked_by_bishop_or_queen?(board, {file, rank}) do
_attacked?(board, {file, rank}, {1, 1}, ["bishop", "queen"]) ||
_attacked?(board, {file, rank}, {1, -1}, ["bishop", "queen"]) ||
_attacked?(board, {file, rank}, {-1, 1}, ["bishop", "queen"]) ||
_attacked?(board, {file, rank}, {-1, -1}, ["bishop", "queen"])
end
defp attacked_by_knight?(board, {file, rank}) do
_attacked?(board, {file, rank}, Knight.pattern, "knight")
end
defp _attacked?(board, {file, rank}, {fv, rv}, pieces) do
{file, rank} =
board
|> Generator.moves({file, rank}, {fv, rv})
|> List.last
piece = board["#{file},#{rank}"]
Enum.any?(pieces, &(match?(%{"type" => &1}, piece)))
end
defp _attacked?(board, {file, rank}, pattern, piece_type) do
moves =
board
|> Generator.moves({file, rank}, pattern)
Enum.any?(moves, &(match_piece(board, &1, piece_type)))
end
defp match_piece(board, {file, rank}, piece_type) do
piece = board["#{file},#{rank}"]
piece["type"] == piece_type
end
end

View File

@ -4,33 +4,9 @@ defmodule Chess.Moves.Rook do
alias Chess.Moves.Generator alias Chess.Moves.Generator
def moves(board, {file, rank}) do def moves(board, {file, rank}) do
moves_north(board, {file, rank}) ++ Generator.moves(board, {file, rank}, {0, 1}) ++
moves_south(board, {file, rank}) ++ Generator.moves(board, {file, rank}, {0, -1}) ++
moves_east(board, {file, rank}) ++ Generator.moves(board, {file, rank}, {-1, 0}) ++
moves_west(board, {file, rank}) Generator.moves(board, {file, rank}, {1, 0})
end
defp moves_north(board, {file, rank}) do
board["#{file},#{rank}"]
|> Map.get("colour")
|> Generator.moves(board, {file, rank}, {0, 1})
end
defp moves_south(board, {file, rank}) do
board["#{file},#{rank}"]
|> Map.get("colour")
|> Generator.moves(board, {file, rank}, {0, -1})
end
defp moves_east(board, {file, rank}) do
board["#{file},#{rank}"]
|> Map.get("colour")
|> Generator.moves(board, {file, rank}, {1, 0})
end
defp moves_west(board, {file, rank}) do
board["#{file},#{rank}"]
|> Map.get("colour")
|> Generator.moves(board, {file, rank}, {-1, 0})
end end
end end

View File

@ -0,0 +1,59 @@
defmodule Chess.Moves.PieceTest do
use Chess.DataCase
alias Chess.Moves.Piece
test "piece is not being attacked" do
board = %{
"4,5" => %{"type" => "king", "colour" => "white"},
"2,1" => %{"type" => "rook", "colour" => "black"},
}
refute Piece.attacked?(board, {4, 5})
end
test "piece can be attacked by a rook" do
board = %{
"4,5" => %{"type" => "king", "colour" => "white"},
"2,5" => %{"type" => "rook", "colour" => "black"},
}
assert Piece.attacked?(board, {4, 5})
end
test "piece can be attacked by a bishop" do
board = %{
"4,5" => %{"type" => "king", "colour" => "white"},
"6,7" => %{"type" => "bishop", "colour" => "black"},
}
assert Piece.attacked?(board, {4, 5})
end
test "piece can be attacked by a queen" do
board = %{
"4,5" => %{"type" => "king", "colour" => "white"},
"6,7" => %{"type" => "queen", "colour" => "black"},
}
assert Piece.attacked?(board, {4, 5})
end
test "piece is not attacked by a knight" do
board = %{
"4,5" => %{"type" => "king", "colour" => "white"},
"7,7" => %{"type" => "knight", "colour" => "black"},
}
refute Piece.attacked?(board, {4, 5})
end
test "piece can be attacked by a knight" do
board = %{
"4,5" => %{"type" => "king", "colour" => "white"},
"5,7" => %{"type" => "knight", "colour" => "black"},
}
assert Piece.attacked?(board, {4, 5})
end
end