mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 23:37:35 +00:00
Chess: Refactor game logic into LibChess for use in engines
In the future UCI protocol stuff will also go into LibChess.
This commit is contained in:
parent
ffece9cfba
commit
d2cb5e0f48
7 changed files with 165 additions and 148 deletions
|
@ -1,9 +1,8 @@
|
|||
set(SOURCES
|
||||
main.cpp
|
||||
Chess.cpp
|
||||
ChessWidget.cpp
|
||||
PromotionDialog.cpp
|
||||
)
|
||||
|
||||
serenity_bin(Chess)
|
||||
target_link_libraries(Chess LibGUI)
|
||||
target_link_libraries(Chess LibChess LibGUI)
|
||||
|
|
|
@ -1,543 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020, the SerenityOS developers.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "Chess.h"
|
||||
#include <AK/Assertions.h>
|
||||
#include <AK/LogStream.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
String Chess::char_for_piece(Chess::Type type)
|
||||
{
|
||||
switch (type) {
|
||||
case Type::Knight:
|
||||
return "N";
|
||||
case Type::Bishop:
|
||||
return "B";
|
||||
case Type::Rook:
|
||||
return "R";
|
||||
case Type::Queen:
|
||||
return "Q";
|
||||
case Type::King:
|
||||
return "K";
|
||||
case Type::Pawn:
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
Chess::Square::Square(const StringView& name)
|
||||
{
|
||||
ASSERT(name.length() == 2);
|
||||
char filec = name[0];
|
||||
char rankc = name[1];
|
||||
|
||||
if (filec >= 'a' && filec <= 'h') {
|
||||
file = filec - 'a';
|
||||
} else if (filec >= 'A' && filec <= 'H') {
|
||||
file = filec - 'A';
|
||||
} else {
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
if (rankc >= '1' && rankc <= '8') {
|
||||
rank = rankc - '1';
|
||||
} else {
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
String Chess::Square::to_algebraic() const
|
||||
{
|
||||
StringBuilder builder;
|
||||
builder.append(file - 'a');
|
||||
builder.append(rank - '1');
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
String Chess::Move::to_long_algebraic() const
|
||||
{
|
||||
StringBuilder builder;
|
||||
builder.append(from.to_algebraic());
|
||||
builder.append(to.to_algebraic());
|
||||
builder.append(char_for_piece(promote_to).to_lowercase());
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
Chess::Chess()
|
||||
{
|
||||
// Fill empty spaces.
|
||||
for (unsigned rank = 2; rank < 6; ++rank) {
|
||||
for (unsigned file = 0; file < 8; ++file) {
|
||||
set_piece({ rank, file }, EmptyPiece);
|
||||
}
|
||||
}
|
||||
|
||||
// Fill white pawns.
|
||||
for (unsigned file = 0; file < 8; ++file) {
|
||||
set_piece({ 1, file }, { Colour::White, Type::Pawn });
|
||||
}
|
||||
|
||||
// Fill black pawns.
|
||||
for (unsigned file = 0; file < 8; ++file) {
|
||||
set_piece({ 6, file }, { Colour::Black, Type::Pawn });
|
||||
}
|
||||
|
||||
// Fill while pieces.
|
||||
set_piece(Square("a1"), { Colour::White, Type::Rook });
|
||||
set_piece(Square("b1"), { Colour::White, Type::Knight });
|
||||
set_piece(Square("c1"), { Colour::White, Type::Bishop });
|
||||
set_piece(Square("d1"), { Colour::White, Type::Queen });
|
||||
set_piece(Square("e1"), { Colour::White, Type::King });
|
||||
set_piece(Square("f1"), { Colour::White, Type::Bishop });
|
||||
set_piece(Square("g1"), { Colour::White, Type::Knight });
|
||||
set_piece(Square("h1"), { Colour::White, Type::Rook });
|
||||
|
||||
// Fill black pieces.
|
||||
set_piece(Square("a8"), { Colour::Black, Type::Rook });
|
||||
set_piece(Square("b8"), { Colour::Black, Type::Knight });
|
||||
set_piece(Square("c8"), { Colour::Black, Type::Bishop });
|
||||
set_piece(Square("d8"), { Colour::Black, Type::Queen });
|
||||
set_piece(Square("e8"), { Colour::Black, Type::King });
|
||||
set_piece(Square("f8"), { Colour::Black, Type::Bishop });
|
||||
set_piece(Square("g8"), { Colour::Black, Type::Knight });
|
||||
set_piece(Square("h8"), { Colour::Black, Type::Rook });
|
||||
}
|
||||
|
||||
Chess::Piece Chess::get_piece(const Square& square) const
|
||||
{
|
||||
ASSERT(square.rank < 8);
|
||||
ASSERT(square.file < 8);
|
||||
return m_board[square.rank][square.file];
|
||||
}
|
||||
|
||||
Chess::Piece Chess::set_piece(const Square& square, const Piece& piece)
|
||||
{
|
||||
ASSERT(square.rank < 8);
|
||||
ASSERT(square.file < 8);
|
||||
return m_board[square.rank][square.file] = piece;
|
||||
}
|
||||
|
||||
bool Chess::is_legal(const Move& move, Colour colour) const
|
||||
{
|
||||
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::is_legal_no_check(const Move& move, Colour colour) const
|
||||
{
|
||||
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 && move.promote_to != Type::None)
|
||||
return false;
|
||||
|
||||
if (move.promote_to == Type::Pawn || move.promote_to == Type::King)
|
||||
return false;
|
||||
|
||||
if (piece.type == Type::Pawn) {
|
||||
int dir = (colour == Colour::White) ? +1 : -1;
|
||||
unsigned start_rank = (colour == Colour::White) ? 1 : 6;
|
||||
unsigned other_start_rank = (colour == Colour::White) ? 6 : 1;
|
||||
unsigned en_passant_rank = (colour == Colour::White) ? 4 : 3;
|
||||
unsigned promotion_rank = (colour == Colour::White) ? 7 : 0;
|
||||
|
||||
if (move.to.rank == promotion_rank) {
|
||||
if (move.promote_to == Type::Pawn || move.promote_to == Type::King || move.promote_to == Type::None)
|
||||
return false;
|
||||
} else if (move.promote_to != Type::None) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (move.to.rank == move.from.rank + dir && 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 + dir && (move.to.file == move.from.file + 1 || move.to.file == move.from.file - 1)) {
|
||||
Move en_passant_last_move = { { other_start_rank, move.to.file }, { en_passant_rank, move.to.file } };
|
||||
if (get_piece(move.to).colour == opposing_colour(colour)) {
|
||||
// Pawn capture.
|
||||
return true;
|
||||
} else if (m_last_move.has_value() && move.from.rank == en_passant_rank && m_last_move.value() == en_passant_last_move
|
||||
&& get_piece(en_passant_last_move.to) == Piece(opposing_colour(colour), Type::Pawn)) {
|
||||
// En passant.
|
||||
return true;
|
||||
}
|
||||
} else if (move.from.rank == start_rank && move.to.rank == move.from.rank + (2 * dir) && move.to.file == move.from.file
|
||||
&& get_piece(move.to).type == Type::None && get_piece({ move.from.rank + dir, move.from.file }).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)
|
||||
{
|
||||
Chess clone = *this;
|
||||
clone.m_previous_states = {};
|
||||
auto state_count = 0;
|
||||
if (m_previous_states.contains(clone))
|
||||
state_count = m_previous_states.get(clone).value();
|
||||
m_previous_states.set(clone, state_count + 1);
|
||||
|
||||
m_turn = opposing_colour(colour);
|
||||
|
||||
m_last_move = move;
|
||||
m_moves_since_capture++;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (get_piece(move.from).type == Type::Pawn && ((colour == Colour::Black && move.to.rank == 0) || (colour == Colour::White && move.to.rank == 7))) {
|
||||
// Pawn Promotion
|
||||
set_piece(move.to, { colour, move.promote_to });
|
||||
set_piece(move.from, EmptyPiece);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (get_piece(move.from).type == Type::Pawn && move.from.file != move.to.file && get_piece(move.to).type == Type::None) {
|
||||
// En passant.
|
||||
if (colour == Colour::White) {
|
||||
set_piece({ move.to.rank - 1, move.to.file }, EmptyPiece);
|
||||
} else {
|
||||
set_piece({ move.to.rank + 1, move.to.file }, EmptyPiece);
|
||||
}
|
||||
m_moves_since_capture = 0;
|
||||
}
|
||||
|
||||
if (get_piece(move.to).colour != Colour::None)
|
||||
m_moves_since_capture = 0;
|
||||
|
||||
set_piece(move.to, get_piece(move.from));
|
||||
set_piece(move.from, EmptyPiece);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Chess::Result Chess::game_result() const
|
||||
{
|
||||
bool sufficient_material = false;
|
||||
bool no_more_pieces_allowed = false;
|
||||
Optional<Square> bishop;
|
||||
Square::for_each([&](Square sq) {
|
||||
if (get_piece(sq).type == Type::Queen || get_piece(sq).type == Type::Rook || get_piece(sq).type == Type::Pawn) {
|
||||
sufficient_material = true;
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
|
||||
if (get_piece(sq).type != Type::None && get_piece(sq).type != Type::King && no_more_pieces_allowed) {
|
||||
sufficient_material = true;
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
|
||||
if (get_piece(sq).type == Type::Knight)
|
||||
no_more_pieces_allowed = true;
|
||||
|
||||
if (get_piece(sq).type == Type::Bishop) {
|
||||
if (bishop.has_value()) {
|
||||
if (get_piece(sq).colour == get_piece(bishop.value()).colour) {
|
||||
sufficient_material = true;
|
||||
return IterationDecision::Break;
|
||||
} else if (sq.is_light() != bishop.value().is_light()) {
|
||||
sufficient_material = true;
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
no_more_pieces_allowed = true;
|
||||
} else {
|
||||
bishop = sq;
|
||||
}
|
||||
}
|
||||
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
if (!sufficient_material)
|
||||
return Result::InsufficientMaterial;
|
||||
|
||||
bool are_legal_moves = false;
|
||||
generate_moves([&](Move m) {
|
||||
(void)m;
|
||||
are_legal_moves = true;
|
||||
return IterationDecision::Break;
|
||||
});
|
||||
|
||||
if (are_legal_moves) {
|
||||
if (m_moves_since_capture >= 75 * 2)
|
||||
return Result::SeventyFiveMoveRule;
|
||||
if (m_moves_since_capture == 50 * 2)
|
||||
return Result::FiftyMoveRule;
|
||||
|
||||
auto repeats = m_previous_states.get(*this);
|
||||
if (repeats.has_value()) {
|
||||
if (repeats.value() == 3)
|
||||
return Result::ThreeFoldRepitition;
|
||||
if (repeats.value() >= 5)
|
||||
return Result::FiveFoldRepitition;
|
||||
}
|
||||
|
||||
return Result::NotFinished;
|
||||
}
|
||||
|
||||
if (in_check(turn()))
|
||||
return Result::CheckMate;
|
||||
|
||||
return Result::StaleMate;
|
||||
}
|
||||
|
||||
bool Chess::is_promotion_move(const Move& move, Colour colour) const
|
||||
{
|
||||
if (colour == Colour::None)
|
||||
colour = turn();
|
||||
|
||||
Move queen_move = move;
|
||||
queen_move.promote_to = Type::Queen;
|
||||
if (!is_legal(queen_move, colour))
|
||||
return false;
|
||||
|
||||
if (get_piece(move.from).type == Type::Pawn && ((colour == Colour::Black && move.to.rank == 0) || (colour == Colour::White && move.to.rank == 7)))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Chess::operator==(const Chess& other) const
|
||||
{
|
||||
bool equal_squares = true;
|
||||
Square::for_each([&](Square sq) {
|
||||
if (get_piece(sq) != other.get_piece(sq)) {
|
||||
equal_squares = false;
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
if (!equal_squares)
|
||||
return false;
|
||||
|
||||
if (m_white_can_castle_queenside != other.m_white_can_castle_queenside)
|
||||
return false;
|
||||
if (m_white_can_castle_kingside != other.m_white_can_castle_kingside)
|
||||
return false;
|
||||
if (m_black_can_castle_queenside != other.m_black_can_castle_queenside)
|
||||
return false;
|
||||
if (m_black_can_castle_kingside != other.m_black_can_castle_kingside)
|
||||
return false;
|
||||
|
||||
return turn() == other.turn();
|
||||
}
|
|
@ -1,282 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020, the SerenityOS developers.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/IterationDecision.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/StringView.h>
|
||||
#include <AK/Traits.h>
|
||||
|
||||
class Chess {
|
||||
public:
|
||||
enum class Type {
|
||||
Pawn,
|
||||
Knight,
|
||||
Bishop,
|
||||
Rook,
|
||||
Queen,
|
||||
King,
|
||||
None,
|
||||
};
|
||||
static String char_for_piece(Type type);
|
||||
|
||||
enum class Colour {
|
||||
White,
|
||||
Black,
|
||||
None,
|
||||
};
|
||||
static Colour opposing_colour(Colour colour) { return (colour == Colour::White) ? Colour::Black : Colour::White; }
|
||||
|
||||
struct Piece {
|
||||
Colour colour;
|
||||
Type type;
|
||||
bool operator==(const Piece& other) const { return colour == other.colour && type == other.type; }
|
||||
|
||||
};
|
||||
static constexpr Piece EmptyPiece = { Colour::None, Type::None };
|
||||
|
||||
struct Square {
|
||||
unsigned rank; // zero indexed;
|
||||
unsigned file;
|
||||
Square(const StringView& name);
|
||||
Square(const unsigned& rank, const unsigned& file)
|
||||
: rank(rank)
|
||||
, file(file)
|
||||
{
|
||||
}
|
||||
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; }
|
||||
bool is_light() const { return (rank % 2) != (file % 2); }
|
||||
String to_algebraic() const;
|
||||
};
|
||||
|
||||
struct Move {
|
||||
Square from;
|
||||
Square to;
|
||||
Type promote_to;
|
||||
Move(const StringView& algebraic);
|
||||
Move(const Square& from, const Square& to, const Type& promote_to = Type::None)
|
||||
: from(from)
|
||||
, to(to)
|
||||
, promote_to(promote_to)
|
||||
{
|
||||
}
|
||||
bool operator==(const Move& other) const { return from == other.from && to == other.to && promote_to == other.promote_to; }
|
||||
|
||||
String to_long_algebraic() const;
|
||||
};
|
||||
|
||||
Chess();
|
||||
|
||||
Piece get_piece(const Square&) const;
|
||||
Piece set_piece(const Square&, const Piece&);
|
||||
|
||||
bool is_legal(const Move&, Colour colour = Colour::None) const;
|
||||
bool in_check(Colour colour) const;
|
||||
|
||||
bool is_promotion_move(const Move&, Colour colour = Colour::None) const;
|
||||
|
||||
bool apply_move(const Move&, Colour colour = Colour::None);
|
||||
const Optional<Move>& last_move() const { return m_last_move; }
|
||||
|
||||
enum class Result {
|
||||
CheckMate,
|
||||
StaleMate,
|
||||
FiftyMoveRule,
|
||||
SeventyFiveMoveRule,
|
||||
ThreeFoldRepitition,
|
||||
FiveFoldRepitition,
|
||||
InsufficientMaterial,
|
||||
NotFinished,
|
||||
};
|
||||
|
||||
template<typename Callback>
|
||||
void generate_moves(Callback callback, Colour colour = Colour::None) const;
|
||||
Result game_result() const;
|
||||
|
||||
Colour turn() const { return m_turn; };
|
||||
|
||||
bool operator==(const Chess& other) const;
|
||||
|
||||
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 };
|
||||
Optional<Move> m_last_move;
|
||||
int m_moves_since_capture { 0 };
|
||||
|
||||
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 };
|
||||
|
||||
HashMap<Chess, int> m_previous_states;
|
||||
friend struct AK::Traits<Chess>;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct AK::Traits<Chess::Piece> : public GenericTraits<Chess::Piece> {
|
||||
static unsigned hash(Chess::Piece piece)
|
||||
{
|
||||
return pair_int_hash(static_cast<u32>(piece.colour), static_cast<u32>(piece.type));
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct AK::Traits<Chess> : public GenericTraits<Chess> {
|
||||
static unsigned hash(Chess chess)
|
||||
{
|
||||
unsigned hash = 0;
|
||||
hash = pair_int_hash(hash, static_cast<u32>(chess.m_white_can_castle_queenside));
|
||||
hash = pair_int_hash(hash, static_cast<u32>(chess.m_white_can_castle_kingside));
|
||||
hash = pair_int_hash(hash, static_cast<u32>(chess.m_black_can_castle_queenside));
|
||||
hash = pair_int_hash(hash, static_cast<u32>(chess.m_black_can_castle_kingside));
|
||||
|
||||
hash = pair_int_hash(hash, static_cast<u32>(chess.m_black_can_castle_kingside));
|
||||
|
||||
Chess::Square::for_each([&](Chess::Square sq) {
|
||||
hash = pair_int_hash(hash, Traits<Chess::Piece>::hash(chess.get_piece(sq)));
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Callback>
|
||||
void Chess::generate_moves(Callback callback, Colour colour) const
|
||||
{
|
||||
if (colour == Colour::None)
|
||||
colour = turn();
|
||||
|
||||
auto try_move = [&](Move m) {
|
||||
if (is_legal(m, colour)) {
|
||||
if (callback(m) == IterationDecision::Break)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
Square::for_each([&](Square sq) {
|
||||
auto piece = get_piece(sq);
|
||||
if (piece.colour != colour)
|
||||
return IterationDecision::Continue;
|
||||
|
||||
bool keep_going = true;
|
||||
if (piece.type == Type::Pawn) {
|
||||
for (auto& piece : Vector({Type::None, Type::Knight, Type::Bishop, Type::Rook, Type::Queen})) {
|
||||
keep_going =
|
||||
try_move({sq, {sq.rank+1, sq.file}, piece}) &&
|
||||
try_move({sq, {sq.rank+2, sq.file}, piece}) &&
|
||||
try_move({sq, {sq.rank-1, sq.file}, piece}) &&
|
||||
try_move({sq, {sq.rank-2, sq.file}, piece}) &&
|
||||
try_move({sq, {sq.rank+1, sq.file+1}}) &&
|
||||
try_move({sq, {sq.rank+1, sq.file-1}}) &&
|
||||
try_move({sq, {sq.rank-1, sq.file+1}}) &&
|
||||
try_move({sq, {sq.rank-1, sq.file-1}});
|
||||
}
|
||||
} else if (piece.type == Type::Knight) {
|
||||
keep_going =
|
||||
try_move({sq, {sq.rank+2, sq.file+1}}) &&
|
||||
try_move({sq, {sq.rank+2, sq.file-1}}) &&
|
||||
try_move({sq, {sq.rank+1, sq.file+2}}) &&
|
||||
try_move({sq, {sq.rank+1, sq.file-2}}) &&
|
||||
try_move({sq, {sq.rank-2, sq.file+1}}) &&
|
||||
try_move({sq, {sq.rank-2, sq.file-1}}) &&
|
||||
try_move({sq, {sq.rank-1, sq.file+2}}) &&
|
||||
try_move({sq, {sq.rank-1, sq.file-2}});
|
||||
} else if (piece.type == Type::Bishop) {
|
||||
for (int dr = -1; dr <= 1; dr += 2) {
|
||||
for (int df = -1; df <= 1; df += 2) {
|
||||
for (Square to = sq; to.in_bounds(); to = { to.rank + dr, to.file + df }) {
|
||||
if (!try_move({ sq, to }))
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (piece.type == Type::Rook) {
|
||||
for (int dr = -1; dr <= 1; dr++) {
|
||||
for (int df = -1; df <= 1; df++) {
|
||||
if ((dr == 0) != (df == 0)) {
|
||||
for (Square to = sq; to.in_bounds(); to = { to.rank + dr, to.file + df }) {
|
||||
if (!try_move({ sq, to }))
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (piece.type == Type::Queen) {
|
||||
for (int dr = -1; dr <= 1; dr++) {
|
||||
for (int df = -1; df <= 1; df++) {
|
||||
if (dr != 0 || df != 0) {
|
||||
for (Square to = sq; to.in_bounds(); to = { to.rank + dr, to.file + df }) {
|
||||
if (!try_move({ sq, to }))
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (piece.type == Type::King) {
|
||||
for (int dr = -1; dr <= 1; dr++) {
|
||||
for (int df = -1; df <= 1; df++) {
|
||||
if (!try_move({ sq, { sq.rank + dr, sq.file + df } }))
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
}
|
||||
|
||||
// Castling moves.
|
||||
if (sq == Square("e1")) {
|
||||
keep_going = try_move({ sq, Square("c1") }) && try_move({ sq, Square("g1") });
|
||||
} else if (sq == Square("e8")) {
|
||||
keep_going = try_move({ sq, Square("c8") }) && try_move({ sq, Square("g8") });
|
||||
}
|
||||
}
|
||||
|
||||
if (keep_going) {
|
||||
return IterationDecision::Continue;
|
||||
} else {
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
});
|
||||
}
|
|
@ -123,22 +123,22 @@ void ChessWidget::mouseup_event(GUI::MouseEvent& event)
|
|||
}
|
||||
|
||||
if (board().apply_move(move)) {
|
||||
if (board().game_result() != Chess::Result::NotFinished) {
|
||||
if (board().game_result() != Chess::Board::Result::NotFinished) {
|
||||
|
||||
bool over = true;
|
||||
String msg;
|
||||
switch (board().game_result()) {
|
||||
case Chess::Result::CheckMate:
|
||||
case Chess::Board::Result::CheckMate:
|
||||
if (board().turn() == Chess::Colour::White) {
|
||||
msg = "Black wins by Checkmate.";
|
||||
} else {
|
||||
msg = "White wins by Checkmate.";
|
||||
}
|
||||
break;
|
||||
case Chess::Result::StaleMate:
|
||||
case Chess::Board::Result::StaleMate:
|
||||
msg = "Draw by Stalemate.";
|
||||
break;
|
||||
case Chess::Result::FiftyMoveRule:
|
||||
case Chess::Board::Result::FiftyMoveRule:
|
||||
update();
|
||||
if (GUI::MessageBox::show(window(), "50 moves have elapsed without a capture. Claim Draw?", "Claim Draw?",
|
||||
GUI::MessageBox::Type::Information, GUI::MessageBox::InputType::YesNo)
|
||||
|
@ -148,10 +148,10 @@ void ChessWidget::mouseup_event(GUI::MouseEvent& event)
|
|||
over = false;
|
||||
}
|
||||
break;
|
||||
case Chess::Result::SeventyFiveMoveRule:
|
||||
case Chess::Board::Result::SeventyFiveMoveRule:
|
||||
msg = "Draw by 75 move rule.";
|
||||
break;
|
||||
case Chess::Result::ThreeFoldRepitition:
|
||||
case Chess::Board::Result::ThreeFoldRepitition:
|
||||
update();
|
||||
if (GUI::MessageBox::show(window(), "The same board state has repeated three times. Claim Draw?", "Claim Draw?",
|
||||
GUI::MessageBox::Type::Information, GUI::MessageBox::InputType::YesNo)
|
||||
|
@ -161,10 +161,10 @@ void ChessWidget::mouseup_event(GUI::MouseEvent& event)
|
|||
over = false;
|
||||
}
|
||||
break;
|
||||
case Chess::Result::FiveFoldRepitition:
|
||||
case Chess::Board::Result::FiveFoldRepitition:
|
||||
msg = "Draw by fivefold repitition.";
|
||||
break;
|
||||
case Chess::Result::InsufficientMaterial:
|
||||
case Chess::Board::Result::InsufficientMaterial:
|
||||
msg = "Draw by insufficient material.";
|
||||
break;
|
||||
default:
|
||||
|
@ -239,7 +239,7 @@ RefPtr<Gfx::Bitmap> ChessWidget::get_piece_graphic(const Chess::Piece& piece) co
|
|||
|
||||
void ChessWidget::reset()
|
||||
{
|
||||
m_board = Chess();
|
||||
m_board = Chess::Board();
|
||||
m_drag_enabled = true;
|
||||
update();
|
||||
}
|
||||
|
|
|
@ -26,11 +26,11 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "Chess.h"
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/NonnullRefPtr.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/StringView.h>
|
||||
#include <LibChess/Chess.h>
|
||||
#include <LibGUI/Widget.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
|
||||
|
@ -47,7 +47,7 @@ public:
|
|||
virtual void mouseup_event(GUI::MouseEvent&) override;
|
||||
virtual void mousemove_event(GUI::MouseEvent&) override;
|
||||
|
||||
Chess& board() { return m_board; };
|
||||
Chess::Board& board() { return m_board; };
|
||||
|
||||
Chess::Colour side() const { return m_side; };
|
||||
void set_side(Chess::Colour side) { m_side = side; };
|
||||
|
@ -74,7 +74,7 @@ public:
|
|||
void set_board_theme(const StringView& name);
|
||||
|
||||
private:
|
||||
Chess m_board;
|
||||
Chess::Board m_board;
|
||||
BoardTheme m_board_theme { "Beige", Color::from_rgb(0xb58863), Color::from_rgb(0xf0d9b5) };
|
||||
Color m_move_highlight_color { Color::from_rgba(0x66ccee00) };
|
||||
Chess::Colour m_side { Chess::Colour::White };
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue