2020-08-10 12:54:32 -06:00
|
|
|
/*
|
|
|
|
|
* Copyright (c) 2020, the SerenityOS developers.
|
|
|
|
|
*
|
2021-04-22 01:24:48 -07:00
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
2020-08-10 12:54:32 -06:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#pragma once
|
|
|
|
|
|
2020-08-12 14:41:35 -06:00
|
|
|
#include <AK/HashMap.h>
|
2020-08-11 14:09:20 -06:00
|
|
|
#include <AK/IterationDecision.h>
|
2020-08-11 21:13:48 -06:00
|
|
|
#include <AK/Optional.h>
|
2020-08-10 12:54:32 -06:00
|
|
|
#include <AK/StringView.h>
|
|
|
|
|
#include <AK/Traits.h>
|
2020-08-19 17:53:50 -06:00
|
|
|
#include <AK/Vector.h>
|
2020-08-10 12:54:32 -06:00
|
|
|
|
2020-08-18 15:02:53 -06:00
|
|
|
namespace Chess {
|
|
|
|
|
|
2021-06-19 16:33:20 -06:00
|
|
|
enum class Type : u8 {
|
2020-08-18 15:02:53 -06:00
|
|
|
Pawn,
|
|
|
|
|
Knight,
|
|
|
|
|
Bishop,
|
|
|
|
|
Rook,
|
|
|
|
|
Queen,
|
|
|
|
|
King,
|
|
|
|
|
None,
|
|
|
|
|
};
|
2020-08-10 12:54:32 -06:00
|
|
|
|
2023-01-08 19:15:51 -05:00
|
|
|
StringView char_for_piece(Type type);
|
2021-11-11 00:55:02 +01:00
|
|
|
Chess::Type piece_for_char_promotion(StringView str);
|
2020-08-10 12:54:32 -06:00
|
|
|
|
2021-06-19 16:33:20 -06:00
|
|
|
enum class Color : u8 {
|
2020-08-18 15:02:53 -06:00
|
|
|
White,
|
|
|
|
|
Black,
|
|
|
|
|
None,
|
|
|
|
|
};
|
2020-08-18 14:29:27 -06:00
|
|
|
|
2021-01-09 13:44:11 +01:00
|
|
|
Color opposing_color(Color color);
|
2020-08-18 15:02:53 -06:00
|
|
|
|
|
|
|
|
struct Piece {
|
2020-08-21 13:46:07 +02:00
|
|
|
constexpr Piece()
|
2021-01-09 13:44:11 +01:00
|
|
|
: color(Color::None)
|
2020-08-21 13:46:07 +02:00
|
|
|
, type(Type::None)
|
|
|
|
|
{
|
|
|
|
|
}
|
2021-01-09 13:44:11 +01:00
|
|
|
constexpr Piece(Color c, Type t)
|
|
|
|
|
: color(c)
|
2020-08-21 13:46:07 +02:00
|
|
|
, type(t)
|
|
|
|
|
{
|
|
|
|
|
}
|
2021-01-09 13:44:11 +01:00
|
|
|
Color color : 4;
|
2020-08-21 13:46:07 +02:00
|
|
|
Type type : 4;
|
2022-04-01 20:58:27 +03:00
|
|
|
bool operator==(Piece const& other) const { return color == other.color && type == other.type; }
|
2020-08-18 15:02:53 -06:00
|
|
|
};
|
|
|
|
|
|
2021-01-09 13:44:11 +01:00
|
|
|
constexpr Piece EmptyPiece = { Color::None, Type::None };
|
2020-08-18 15:02:53 -06:00
|
|
|
|
|
|
|
|
struct Square {
|
2021-06-19 16:33:20 -06:00
|
|
|
i8 rank; // zero indexed;
|
|
|
|
|
i8 file;
|
2022-07-11 20:52:44 +00:00
|
|
|
|
2021-11-11 00:55:02 +01:00
|
|
|
Square(StringView name);
|
2022-07-11 20:52:44 +00:00
|
|
|
|
|
|
|
|
Square(char const name[3])
|
|
|
|
|
: Square({ name, 2 })
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-01 20:58:27 +03:00
|
|
|
Square(int const& rank, int const& file)
|
2020-08-18 15:02:53 -06:00
|
|
|
: rank(rank)
|
|
|
|
|
, file(file)
|
|
|
|
|
{
|
|
|
|
|
}
|
2022-04-01 20:58:27 +03:00
|
|
|
bool operator==(Square const& other) const { return rank == other.rank && file == other.file; }
|
2020-08-18 15:02:53 -06:00
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
return;
|
2020-08-11 14:09:20 -06:00
|
|
|
}
|
|
|
|
|
}
|
2020-08-18 15:02:53 -06:00
|
|
|
}
|
2020-08-11 14:09:20 -06:00
|
|
|
|
2021-05-16 14:55:20 +02:00
|
|
|
bool in_bounds() const { return rank >= 0 && file >= 0 && rank < 8 && file < 8; }
|
2020-08-18 15:02:53 -06:00
|
|
|
bool is_light() const { return (rank % 2) != (file % 2); }
|
2022-12-04 18:02:33 +00:00
|
|
|
DeprecatedString to_algebraic() const;
|
2020-08-18 15:02:53 -06:00
|
|
|
};
|
2020-08-10 12:54:32 -06:00
|
|
|
|
2020-12-10 17:34:06 +01:00
|
|
|
class Board;
|
|
|
|
|
|
2020-08-18 15:02:53 -06:00
|
|
|
struct Move {
|
|
|
|
|
Square from;
|
|
|
|
|
Square to;
|
|
|
|
|
Type promote_to;
|
2020-12-04 14:26:26 +01:00
|
|
|
Piece piece;
|
2021-06-19 16:33:20 -06:00
|
|
|
bool is_check : 1 = false;
|
|
|
|
|
bool is_mate : 1 = false;
|
|
|
|
|
bool is_capture : 1 = false;
|
|
|
|
|
bool is_ambiguous : 1 = false;
|
2020-12-04 14:26:26 +01:00
|
|
|
Square ambiguous { 50, 50 };
|
2021-11-11 00:55:02 +01:00
|
|
|
Move(StringView long_algebraic);
|
2022-04-01 20:58:27 +03:00
|
|
|
Move(Square const& from, Square const& to, Type const& promote_to = Type::None)
|
2020-08-18 15:02:53 -06:00
|
|
|
: from(from)
|
|
|
|
|
, to(to)
|
|
|
|
|
, promote_to(promote_to)
|
|
|
|
|
{
|
|
|
|
|
}
|
2022-04-01 20:58:27 +03:00
|
|
|
bool operator==(Move const& other) const { return from == other.from && to == other.to && promote_to == other.promote_to; }
|
2020-08-18 14:29:27 -06:00
|
|
|
|
2022-04-01 20:58:27 +03:00
|
|
|
static Move from_algebraic(StringView algebraic, const Color turn, Board const& board);
|
2022-12-04 18:02:33 +00:00
|
|
|
DeprecatedString to_long_algebraic() const;
|
|
|
|
|
DeprecatedString to_algebraic() const;
|
2020-08-18 15:02:53 -06:00
|
|
|
};
|
2020-08-10 12:54:32 -06:00
|
|
|
|
2020-08-18 15:02:53 -06:00
|
|
|
class Board {
|
|
|
|
|
public:
|
|
|
|
|
Board();
|
2022-08-14 15:00:52 +02:00
|
|
|
Board clone_without_history() const;
|
2020-08-10 12:54:32 -06:00
|
|
|
|
2022-04-01 20:58:27 +03:00
|
|
|
Piece get_piece(Square const&) const;
|
|
|
|
|
Piece set_piece(Square const&, Piece const&);
|
2020-08-10 12:54:32 -06:00
|
|
|
|
2022-04-01 20:58:27 +03:00
|
|
|
bool is_legal(Move const&, Color color = Color::None) const;
|
2021-01-09 13:44:11 +01:00
|
|
|
bool in_check(Color color) const;
|
2020-08-10 12:54:32 -06:00
|
|
|
|
2022-04-01 20:58:27 +03:00
|
|
|
bool is_promotion_move(Move const&, Color color = Color::None) const;
|
2020-08-11 22:53:11 -06:00
|
|
|
|
2022-04-01 20:58:27 +03:00
|
|
|
bool apply_move(Move const&, Color color = Color::None);
|
|
|
|
|
Optional<Move> const& last_move() const { return m_last_move; }
|
2020-08-10 12:54:32 -06:00
|
|
|
|
2022-12-04 18:02:33 +00:00
|
|
|
DeprecatedString to_fen() const;
|
2020-12-10 16:44:38 +01:00
|
|
|
|
2020-08-11 14:10:39 -06:00
|
|
|
enum class Result {
|
|
|
|
|
CheckMate,
|
|
|
|
|
StaleMate,
|
2020-12-04 13:17:00 +01:00
|
|
|
WhiteResign,
|
|
|
|
|
BlackResign,
|
2020-08-11 14:10:39 -06:00
|
|
|
FiftyMoveRule,
|
2020-08-12 14:41:35 -06:00
|
|
|
SeventyFiveMoveRule,
|
2020-10-02 22:14:37 +01:00
|
|
|
ThreeFoldRepetition,
|
|
|
|
|
FiveFoldRepetition,
|
2020-08-12 14:41:35 -06:00
|
|
|
InsufficientMaterial,
|
2020-08-11 14:10:39 -06:00
|
|
|
NotFinished,
|
|
|
|
|
};
|
|
|
|
|
|
2023-01-08 19:15:51 -05:00
|
|
|
static StringView result_to_string(Result, Color turn);
|
|
|
|
|
static StringView result_to_points_string(Result, Color turn);
|
2020-12-04 13:17:00 +01:00
|
|
|
|
2020-08-11 14:10:39 -06:00
|
|
|
template<typename Callback>
|
2021-01-09 13:44:11 +01:00
|
|
|
void generate_moves(Callback callback, Color color = Color::None) const;
|
|
|
|
|
Move random_move(Color color = Color::None) const;
|
2020-08-11 14:10:39 -06:00
|
|
|
Result game_result() const;
|
2021-01-09 13:44:11 +01:00
|
|
|
Color game_winner() const;
|
2020-08-20 17:04:08 -06:00
|
|
|
int game_score() const;
|
|
|
|
|
bool game_finished() const;
|
2021-01-09 13:44:11 +01:00
|
|
|
void set_resigned(Color);
|
2020-08-20 17:04:08 -06:00
|
|
|
int material_imbalance() const;
|
2020-08-10 12:54:32 -06:00
|
|
|
|
2021-01-09 13:44:11 +01:00
|
|
|
Color turn() const { return m_turn; }
|
2022-04-01 20:58:27 +03:00
|
|
|
Vector<Move> const& moves() const { return m_moves; }
|
2020-08-11 14:10:39 -06:00
|
|
|
|
2022-04-01 20:58:27 +03:00
|
|
|
bool operator==(Board const& other) const;
|
2020-08-12 14:41:35 -06:00
|
|
|
|
2020-08-10 12:54:32 -06:00
|
|
|
private:
|
2022-04-01 20:58:27 +03:00
|
|
|
bool is_legal_no_check(Move const&, Color color) const;
|
|
|
|
|
bool is_legal_promotion(Move const&, Color color) const;
|
|
|
|
|
bool apply_illegal_move(Move const&, Color color);
|
2020-08-11 14:09:20 -06:00
|
|
|
|
2020-08-10 12:54:32 -06:00
|
|
|
Piece m_board[8][8];
|
2020-08-11 21:13:48 -06:00
|
|
|
Optional<Move> m_last_move;
|
2021-06-19 16:33:20 -06:00
|
|
|
short m_moves_since_capture { 0 };
|
|
|
|
|
short m_moves_since_pawn_advance { 0 };
|
2020-08-11 14:09:20 -06:00
|
|
|
|
2021-06-19 16:33:20 -06:00
|
|
|
Color m_turn : 2 { Color::White };
|
|
|
|
|
Color m_resigned : 2 { Color::None };
|
|
|
|
|
|
|
|
|
|
bool m_white_can_castle_kingside : 1 { true };
|
|
|
|
|
bool m_white_can_castle_queenside : 1 { true };
|
|
|
|
|
bool m_black_can_castle_kingside : 1 { true };
|
|
|
|
|
bool m_black_can_castle_queenside : 1 { true };
|
2020-08-12 14:41:35 -06:00
|
|
|
|
2021-06-19 14:49:53 -06:00
|
|
|
// We trust that hash collisions will not happen to save lots of memory and time.
|
|
|
|
|
HashMap<unsigned, int> m_previous_states;
|
2020-08-19 17:53:50 -06:00
|
|
|
Vector<Move> m_moves;
|
2021-02-25 21:10:47 +01:00
|
|
|
friend struct Traits<Board>;
|
2020-08-12 14:41:35 -06:00
|
|
|
};
|
|
|
|
|
|
2020-08-11 14:10:39 -06:00
|
|
|
template<typename Callback>
|
2021-01-09 13:44:11 +01:00
|
|
|
void Board::generate_moves(Callback callback, Color color) const
|
2020-08-11 14:10:39 -06:00
|
|
|
{
|
2021-01-09 13:44:11 +01:00
|
|
|
if (color == Color::None)
|
|
|
|
|
color = turn();
|
2020-08-11 14:10:39 -06:00
|
|
|
|
|
|
|
|
auto try_move = [&](Move m) {
|
2021-01-09 13:44:11 +01:00
|
|
|
if (is_legal(m, color)) {
|
2020-08-11 14:10:39 -06:00
|
|
|
if (callback(m) == IterationDecision::Break)
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Square::for_each([&](Square sq) {
|
|
|
|
|
auto piece = get_piece(sq);
|
2021-01-09 13:44:11 +01:00
|
|
|
if (piece.color != color)
|
2020-08-11 14:10:39 -06:00
|
|
|
return IterationDecision::Continue;
|
|
|
|
|
|
|
|
|
|
bool keep_going = true;
|
|
|
|
|
if (piece.type == Type::Pawn) {
|
2020-08-18 15:02:53 -06:00
|
|
|
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 }, piece })
|
|
|
|
|
&& try_move({ sq, { sq.rank + 1, sq.file - 1 }, piece })
|
|
|
|
|
&& try_move({ sq, { sq.rank - 1, sq.file + 1 }, piece })
|
|
|
|
|
&& try_move({ sq, { sq.rank - 1, sq.file - 1 }, piece });
|
2020-08-17 15:40:55 -06:00
|
|
|
}
|
2020-08-11 14:10:39 -06:00
|
|
|
} else if (piece.type == Type::Knight) {
|
2020-08-18 15:02:53 -06:00
|
|
|
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 } });
|
2020-08-11 14:10:39 -06:00
|
|
|
} 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;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2020-08-18 15:02:53 -06:00
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<>
|
|
|
|
|
struct AK::Traits<Chess::Piece> : public GenericTraits<Chess::Piece> {
|
2021-07-30 18:18:25 +02:00
|
|
|
static unsigned hash(Chess::Piece const& piece)
|
2020-08-18 15:02:53 -06:00
|
|
|
{
|
2021-01-09 13:44:11 +01:00
|
|
|
return pair_int_hash(static_cast<u32>(piece.color), static_cast<u32>(piece.type));
|
2020-08-18 15:02:53 -06:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
template<>
|
|
|
|
|
struct AK::Traits<Chess::Board> : public GenericTraits<Chess::Board> {
|
2021-07-30 18:18:25 +02:00
|
|
|
static unsigned hash(Chess::Board const& chess)
|
2020-08-18 15:02:53 -06:00
|
|
|
{
|
|
|
|
|
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));
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
};
|