mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-26 01:20:25 +00:00
Chess: Add legal move checking
This commit is contained in:
parent
7333916252
commit
e05372cee2
Notes:
sideshowbarker
2024-07-19 03:35:51 +09:00
Author: https://github.com/petelliott Commit: https://github.com/SerenityOS/serenity/commit/e05372cee2c Pull-request: https://github.com/SerenityOS/serenity/pull/3099 Reviewed-by: https://github.com/stelar7
2 changed files with 267 additions and 13 deletions
|
@ -26,6 +26,9 @@
|
|||
|
||||
#include "Chess.h"
|
||||
#include <AK/Assertions.h>
|
||||
#include <AK/LogStream.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
Chess::Square::Square(const StringView& name)
|
||||
{
|
||||
|
@ -102,26 +105,250 @@ Chess::Piece Chess::set_piece(const Square& square, const Piece& piece)
|
|||
return m_board[square.rank][square.file] = piece;
|
||||
}
|
||||
|
||||
bool Chess::is_legal(const Move& move) const
|
||||
bool Chess::is_legal(const Move& move, Colour colour) const
|
||||
{
|
||||
// FIXME: Impelement actual chess logic.
|
||||
return get_piece(move.from).colour == turn() && get_piece(move.to).colour != turn();
|
||||
if (colour == Colour::None)
|
||||
colour = turn();
|
||||
|
||||
if (!is_legal_no_check(move, colour))
|
||||
return false;
|
||||
|
||||
Chess clone = *this;
|
||||
clone.apply_illegal_move(move, colour);
|
||||
if (clone.in_check(colour))
|
||||
return false;
|
||||
|
||||
// Don't allow castling through check or out of check.
|
||||
Vector<Square> check_squares;
|
||||
if (colour == Colour::White && move.from == Square("e1") && get_piece(Square("e1")) == Piece(Colour::White, Type::King)) {
|
||||
if (move.to == Square("a1") || move.to == Square("c1")) {
|
||||
check_squares = { Square("e1"), Square("d1"), Square("c1") };
|
||||
} else if (move.to == Square("h1") || move.to == Square("g1")) {
|
||||
check_squares = { Square("e1"), Square("f1"), Square("g1") };
|
||||
}
|
||||
} else if (colour == Colour::Black && move.from == Square("e8") && get_piece(Square("e8")) == Piece(Colour::Black, Type::King)) {
|
||||
if (move.to == Square("a8") || move.to == Square("c8")) {
|
||||
check_squares = { Square("e8"), Square("d8"), Square("c8") };
|
||||
} else if (move.to == Square("h8") || move.to == Square("g8")) {
|
||||
check_squares = { Square("e8"), Square("f8"), Square("g8") };
|
||||
}
|
||||
}
|
||||
for (auto& square : check_squares) {
|
||||
Chess clone = *this;
|
||||
clone.set_piece(move.from, EmptyPiece);
|
||||
clone.set_piece(square, { colour, Type::King });
|
||||
if (clone.in_check(colour))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Chess::apply_move(const Move& move)
|
||||
bool Chess::is_legal_no_check(const Move& move, Colour colour) const
|
||||
{
|
||||
if (!is_legal(move)) {
|
||||
auto piece = get_piece(move.from);
|
||||
if (piece.colour != colour)
|
||||
return false;
|
||||
|
||||
if (move.to.rank > 7 || move.to.file > 7)
|
||||
return false;
|
||||
|
||||
if (piece.type == Type::Pawn) {
|
||||
// FIXME: Add en passant.
|
||||
if (colour == Colour::White) {
|
||||
if (move.to.rank == move.from.rank + 1 && move.to.file == move.from.file && get_piece(move.to).type == Type::None) {
|
||||
// Regular pawn move.
|
||||
return true;
|
||||
} else if (move.to.rank == move.from.rank + 1 && (move.to.file == move.from.file + 1 || move.to.file == move.from.file - 1)
|
||||
&& get_piece(move.to).colour == Colour::Black) {
|
||||
// Pawn capture.
|
||||
return true;
|
||||
} else if (move.from.rank == 1 && move.to.rank == move.from.rank + 2 && move.to.file == move.from.file && get_piece(move.to).type == Type::None) {
|
||||
// 2 square pawn move from initial position.
|
||||
return true;
|
||||
}
|
||||
} else if (colour == Colour::Black) {
|
||||
if (move.to.rank == move.from.rank - 1 && move.to.file == move.from.file && get_piece(move.to).type == Type::None) {
|
||||
// Regular pawn move.
|
||||
return true;
|
||||
} else if (move.to.rank == move.from.rank - 1 && (move.to.file == move.from.file + 1 || move.to.file == move.from.file - 1)
|
||||
&& get_piece(move.to).colour == Colour::White) {
|
||||
// Pawn capture.
|
||||
return true;
|
||||
} else if (move.from.rank == 6 && move.to.rank == move.from.rank - 2 && move.to.file == move.from.file && get_piece(move.to).type == Type::None) {
|
||||
// 2 square pawn move from initial position.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else if (piece.type == Type::Knight) {
|
||||
int rank_delta = abs(move.to.rank - move.from.rank);
|
||||
int file_delta = abs(move.to.file - move.from.file);
|
||||
if (get_piece(move.to).colour != colour && max(rank_delta, file_delta) == 2 && min(rank_delta, file_delta) == 1) {
|
||||
return true;
|
||||
}
|
||||
} else if (piece.type == Type::Bishop) {
|
||||
int rank_delta = move.to.rank - move.from.rank;
|
||||
int file_delta = move.to.file - move.from.file;
|
||||
if (abs(rank_delta) == abs(file_delta)) {
|
||||
int dr = rank_delta / abs(rank_delta);
|
||||
int df = file_delta / abs(file_delta);
|
||||
for (Square sq = move.from; sq != move.to; sq.rank += dr, sq.file += df) {
|
||||
if (get_piece(sq).type != Type::None && sq != move.from) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (get_piece(move.to).colour != colour) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (piece.type == Type::Rook) {
|
||||
int rank_delta = move.to.rank - move.from.rank;
|
||||
int file_delta = move.to.file - move.from.file;
|
||||
if (rank_delta == 0 || file_delta == 0) {
|
||||
int dr = (rank_delta) ? rank_delta / abs(rank_delta) : 0;
|
||||
int df = (file_delta) ? file_delta / abs(file_delta) : 0;
|
||||
for (Square sq = move.from; sq != move.to; sq.rank += dr, sq.file += df) {
|
||||
if (get_piece(sq).type != Type::None && sq != move.from) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (get_piece(move.to).colour != colour) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (piece.type == Type::Queen) {
|
||||
int rank_delta = move.to.rank - move.from.rank;
|
||||
int file_delta = move.to.file - move.from.file;
|
||||
if (abs(rank_delta) == abs(file_delta) || rank_delta == 0 || file_delta == 0) {
|
||||
int dr = (rank_delta) ? rank_delta / abs(rank_delta) : 0;
|
||||
int df = (file_delta) ? file_delta / abs(file_delta) : 0;
|
||||
for (Square sq = move.from; sq != move.to; sq.rank += dr, sq.file += df) {
|
||||
if (get_piece(sq).type != Type::None && sq != move.from) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (get_piece(move.to).colour != colour) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (piece.type == Type::King) {
|
||||
int rank_delta = move.to.rank - move.from.rank;
|
||||
int file_delta = move.to.file - move.from.file;
|
||||
if (abs(rank_delta) <= 1 && abs(file_delta) <= 1) {
|
||||
if (get_piece(move.to).colour != colour) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (colour == Colour::White) {
|
||||
if ((move.to == Square("a1") || move.to == Square("c1")) && m_white_can_castle_queenside && get_piece(Square("b1")).type == Type::None && get_piece(Square("c1")).type == Type::None && get_piece(Square("d1")).type == Type::None) {
|
||||
|
||||
return true;
|
||||
} else if ((move.to == Square("h1") || move.to == Square("g1")) && m_white_can_castle_kingside && get_piece(Square("f1")).type == Type::None && get_piece(Square("g1")).type == Type::None) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if ((move.to == Square("a8") || move.to == Square("c8")) && m_black_can_castle_queenside && get_piece(Square("b8")).type == Type::None && get_piece(Square("c8")).type == Type::None && get_piece(Square("d8")).type == Type::None) {
|
||||
|
||||
return true;
|
||||
} else if ((move.to == Square("h8") || move.to == Square("g8")) && m_black_can_castle_kingside && get_piece(Square("f8")).type == Type::None && get_piece(Square("g8")).type == Type::None) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Chess::in_check(Colour colour) const
|
||||
{
|
||||
Square king_square = { 50, 50 };
|
||||
Square::for_each([&](const Square& square) {
|
||||
if (get_piece(square) == Piece(colour, Type::King)) {
|
||||
king_square = square;
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
bool check = false;
|
||||
Square::for_each([&](const Square& square) {
|
||||
if (is_legal({ square, king_square }, opposing_colour(colour))) {
|
||||
check = true;
|
||||
return IterationDecision::Break;
|
||||
} else if (get_piece(square) == Piece(opposing_colour(colour), Type::King) && is_legal_no_check({ square, king_square }, opposing_colour(colour))) {
|
||||
// The King is a special case, because it would be in check if it put the opposing king in check.
|
||||
check = true;
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
return check;
|
||||
}
|
||||
|
||||
bool Chess::apply_move(const Move& move, Colour colour)
|
||||
{
|
||||
if (colour == Colour::None)
|
||||
colour = turn();
|
||||
|
||||
if (!is_legal(move, colour))
|
||||
return false;
|
||||
|
||||
return apply_illegal_move(move, colour);
|
||||
}
|
||||
|
||||
bool Chess::apply_illegal_move(const Move& move, Colour colour)
|
||||
{
|
||||
m_turn = opposing_colour(colour);
|
||||
|
||||
// FIXME: pawn promotion
|
||||
|
||||
if (move.from == Square("a1") || move.to == Square("a1") || move.from == Square("e1"))
|
||||
m_white_can_castle_queenside = false;
|
||||
if (move.from == Square("h1") || move.to == Square("h1") || move.from == Square("e1"))
|
||||
m_white_can_castle_kingside = false;
|
||||
if (move.from == Square("a8") || move.to == Square("a8") || move.from == Square("e8"))
|
||||
m_black_can_castle_queenside = false;
|
||||
if (move.from == Square("h8") || move.to == Square("h8") || move.from == Square("e8"))
|
||||
m_black_can_castle_kingside = false;
|
||||
|
||||
if (colour == Colour::White && move.from == Square("e1") && get_piece(Square("e1")) == Piece(Colour::White, Type::King)) {
|
||||
if (move.to == Square("a1") || move.to == Square("c1")) {
|
||||
set_piece(Square("e1"), EmptyPiece);
|
||||
set_piece(Square("a1"), EmptyPiece);
|
||||
set_piece(Square("c1"), { Colour::White, Type::King });
|
||||
set_piece(Square("d1"), { Colour::White, Type::Rook });
|
||||
return true;
|
||||
} else if (move.to == Square("h1") || move.to == Square("g1")) {
|
||||
set_piece(Square("e1"), EmptyPiece);
|
||||
set_piece(Square("h1"), EmptyPiece);
|
||||
set_piece(Square("g1"), { Colour::White, Type::King });
|
||||
set_piece(Square("f1"), { Colour::White, Type::Rook });
|
||||
return true;
|
||||
}
|
||||
} else if (colour == Colour::Black && move.from == Square("e8") && get_piece(Square("e8")) == Piece(Colour::Black, Type::King)) {
|
||||
if (move.to == Square("a8") || move.to == Square("c8")) {
|
||||
set_piece(Square("e8"), EmptyPiece);
|
||||
set_piece(Square("a8"), EmptyPiece);
|
||||
set_piece(Square("c8"), { Colour::White, Type::King });
|
||||
set_piece(Square("d8"), { Colour::White, Type::Rook });
|
||||
return true;
|
||||
} else if (move.to == Square("h8") || move.to == Square("g8")) {
|
||||
set_piece(Square("e8"), EmptyPiece);
|
||||
set_piece(Square("h8"), EmptyPiece);
|
||||
set_piece(Square("g8"), { Colour::Black, Type::King });
|
||||
set_piece(Square("f8"), { Colour::Black, Type::Rook });
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
set_piece(move.to, get_piece(move.from));
|
||||
set_piece(move.from, EmptyPiece);
|
||||
|
||||
if (m_turn == Colour::White) {
|
||||
m_turn = Colour::Black;
|
||||
} else if (m_turn == Colour::Black) {
|
||||
m_turn = Colour::White;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <AK/IterationDecision.h>
|
||||
#include <AK/StringView.h>
|
||||
#include <AK/Traits.h>
|
||||
|
||||
|
@ -46,6 +47,7 @@ public:
|
|||
Black,
|
||||
None,
|
||||
};
|
||||
static Colour opposing_colour(Colour colour) { return (colour == Colour::White) ? Colour::Black : Colour::White; }
|
||||
|
||||
struct Piece {
|
||||
Colour colour;
|
||||
|
@ -64,6 +66,21 @@ public:
|
|||
{
|
||||
}
|
||||
bool operator==(const Square& other) const { return rank == other.rank && file == other.file; }
|
||||
|
||||
template<typename Callback>
|
||||
static void for_each(Callback callback)
|
||||
{
|
||||
for (int rank = 0; rank < 8; ++rank) {
|
||||
for (int file = 0; file < 8; ++file) {
|
||||
if (callback(Square(rank, file)) == IterationDecision::Break) {
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
exit:;
|
||||
}
|
||||
|
||||
bool in_bounds() const { return rank < 8 && file < 8; }
|
||||
};
|
||||
|
||||
struct Move {
|
||||
|
@ -82,15 +99,25 @@ public:
|
|||
Piece get_piece(const Square&) const;
|
||||
Piece set_piece(const Square&, const Piece&);
|
||||
|
||||
bool is_legal(const Move&) const;
|
||||
bool is_legal(const Move&, Colour colour = Colour::None) const;
|
||||
bool in_check(Colour colour) const;
|
||||
|
||||
bool apply_move(const Move&);
|
||||
bool apply_move(const Move&, Colour colour = Colour::None);
|
||||
|
||||
Colour turn() const { return m_turn; };
|
||||
|
||||
Colour turn() const { return m_turn; };
|
||||
private:
|
||||
bool is_legal_no_check(const Move&, Colour colour) const;
|
||||
bool apply_illegal_move(const Move&, Colour colour);
|
||||
|
||||
Piece m_board[8][8];
|
||||
Colour m_turn { Colour::White };
|
||||
|
||||
bool m_white_can_castle_kingside { true };
|
||||
bool m_white_can_castle_queenside { true };
|
||||
bool m_black_can_castle_kingside { true };
|
||||
bool m_black_can_castle_queenside { true };
|
||||
};
|
||||
|
||||
template<>
|
||||
|
|
Loading…
Reference in a new issue