From 18aa9faab4f9ebcf8d963dc35bc0468029c879aa Mon Sep 17 00:00:00 2001 From: Dan Barber Date: Sat, 17 Mar 2018 23:05:43 -0400 Subject: [PATCH] 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 --- lib/chess/moves/bishop.ex | 32 +++--------------- lib/chess/moves/generator.ex | 37 ++++++++++++++------- lib/chess/moves/king.ex | 4 +-- lib/chess/moves/knight.ex | 6 ++-- lib/chess/moves/pawn.ex | 6 ++-- lib/chess/moves/piece.ex | 55 ++++++++++++++++++++++++++++++ lib/chess/moves/rook.ex | 32 +++--------------- test/chess/moves/piece_test.exs | 59 +++++++++++++++++++++++++++++++++ 8 files changed, 153 insertions(+), 78 deletions(-) create mode 100644 lib/chess/moves/piece.ex create mode 100644 test/chess/moves/piece_test.exs diff --git a/lib/chess/moves/bishop.ex b/lib/chess/moves/bishop.ex index 59dee89..f4e590f 100644 --- a/lib/chess/moves/bishop.ex +++ b/lib/chess/moves/bishop.ex @@ -4,33 +4,9 @@ defmodule Chess.Moves.Bishop do alias Chess.Moves.Generator def moves(board, {file, rank}) do - moves_northeast(board, {file, rank}) ++ - moves_southeast(board, {file, rank}) ++ - moves_northwest(board, {file, rank}) ++ - moves_southwest(board, {file, rank}) - 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}) + Generator.moves(board, {file, rank}, {1, 1}) ++ + Generator.moves(board, {file, rank}, {1, -1}) ++ + Generator.moves(board, {file, rank}, {-1, 1}) ++ + Generator.moves(board, {file, rank}, {-1, -1}) end end diff --git a/lib/chess/moves/generator.ex b/lib/chess/moves/generator.ex index 947d313..b2b61ce 100644 --- a/lib/chess/moves/generator.ex +++ b/lib/chess/moves/generator.ex @@ -1,12 +1,25 @@ 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 - def moves(_colour, _board, {0, _rank}, {-1, _}), do: [] - def moves(_colour, _board, {_file, 0}, {_, -1}), do: [] - def moves(_colour, _board, {7, _rank}, {1, _}), do: [] - def moves(_colour, _board, {_file, 7}, {_, 1}), do: [] - def moves(colour, board, {file, rank}, {fv, rv}) do + def _moves(_colour, _board, {0, _rank}, {-1, _}), do: [] + def _moves(_colour, _board, {_file, 0}, {_, -1}), do: [] + def _moves(_colour, _board, {7, _rank}, {1, _}), do: [] + def _moves(_colour, _board, {_file, 7}, {_, 1}), do: [] + def _moves(colour, board, {file, rank}, {fv, rv}) do next_square = {file + fv, rank + rv} cond do can_capture_piece?(colour, board, next_square) -> @@ -14,22 +27,22 @@ defmodule Chess.Moves.Generator do obstruction?(colour, board, next_square) -> [] true -> - [next_square | moves(colour, board, next_square, {fv, rv})] + [next_square | _moves(colour, board, next_square, {fv, rv})] end end # Move generation for pieces that follow a pattern - def moves(_colour, _board, {_file, _rank}, []), do: [] - def moves(colour, board, {file, rank}, [{fv, rv} | moves]) do + def _moves(_colour, _board, {_file, _rank}, []), do: [] + def _moves(colour, board, {file, rank}, [{fv, rv} | moves]) do move_square = {file + fv, rank + rv} cond do outside_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) -> - [move_square | moves(colour, board, {file, rank}, moves)] + [move_square | _moves(colour, board, {file, rank}, moves)] true -> - [move_square | moves(colour, board, {file, rank}, moves)] + [move_square | _moves(colour, board, {file, rank}, moves)] end end diff --git a/lib/chess/moves/king.ex b/lib/chess/moves/king.ex index 1121d95..4d482b2 100644 --- a/lib/chess/moves/king.ex +++ b/lib/chess/moves/king.ex @@ -4,9 +4,7 @@ defmodule Chess.Moves.King do alias Chess.Moves.Generator def moves(board, {file, rank}) do - board["#{file},#{rank}"] - |> Map.get("colour") - |> Generator.moves(board, {file, rank}, patterns()) + Generator.moves(board, {file, rank}, patterns()) end defp patterns do diff --git a/lib/chess/moves/knight.ex b/lib/chess/moves/knight.ex index 0b4a109..c521b67 100644 --- a/lib/chess/moves/knight.ex +++ b/lib/chess/moves/knight.ex @@ -4,12 +4,10 @@ defmodule Chess.Moves.Knight do alias Chess.Moves.Generator def moves(board, {file, rank}) do - board["#{file},#{rank}"] - |> Map.get("colour") - |> Generator.moves(board, {file, rank}, patterns()) + Generator.moves(board, {file, rank}, pattern()) end - defp patterns do + def pattern do [{1, 2}, {2, 1}, {-1, 2}, {-2, 1}, {1, -2}, {2, -1}, {-1, -2}, {-2, -1}] end end diff --git a/lib/chess/moves/pawn.ex b/lib/chess/moves/pawn.ex index cc8405c..b134217 100644 --- a/lib/chess/moves/pawn.ex +++ b/lib/chess/moves/pawn.ex @@ -18,14 +18,14 @@ defmodule Chess.Moves.Pawn do |> Map.get("colour") colour - |> _capture_moves(board, {file, rank}, patterns(colour)) + |> _capture_moves(board, {file, rank}, pattern(colour)) end - defp patterns("white") do + def pattern("white") do [{-1, 1}, {1, 1}] end - defp patterns("black") do + def pattern("black") do [{-1, -1}, {1, -1}] end diff --git a/lib/chess/moves/piece.ex b/lib/chess/moves/piece.ex new file mode 100644 index 0000000..f107bb5 --- /dev/null +++ b/lib/chess/moves/piece.ex @@ -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 diff --git a/lib/chess/moves/rook.ex b/lib/chess/moves/rook.ex index 13be14d..4d79372 100644 --- a/lib/chess/moves/rook.ex +++ b/lib/chess/moves/rook.ex @@ -4,33 +4,9 @@ defmodule Chess.Moves.Rook do alias Chess.Moves.Generator def moves(board, {file, rank}) do - moves_north(board, {file, rank}) ++ - moves_south(board, {file, rank}) ++ - moves_east(board, {file, rank}) ++ - moves_west(board, {file, rank}) - 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}) + Generator.moves(board, {file, rank}, {0, 1}) ++ + Generator.moves(board, {file, rank}, {0, -1}) ++ + Generator.moves(board, {file, rank}, {-1, 0}) ++ + Generator.moves(board, {file, rank}, {1, 0}) end end diff --git a/test/chess/moves/piece_test.exs b/test/chess/moves/piece_test.exs new file mode 100644 index 0000000..c861f31 --- /dev/null +++ b/test/chess/moves/piece_test.exs @@ -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