mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 09:57:35 +00:00
Libraries: Move to Userland/Libraries/
This commit is contained in:
parent
dc28c07fa5
commit
13d7c09125
1857 changed files with 266 additions and 274 deletions
8
Userland/Libraries/LibChess/CMakeLists.txt
Normal file
8
Userland/Libraries/LibChess/CMakeLists.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
set(SOURCES
|
||||
Chess.cpp
|
||||
UCICommand.cpp
|
||||
UCIEndpoint.cpp
|
||||
)
|
||||
|
||||
serenity_lib(LibChess chess)
|
||||
target_link_libraries(LibChess LibC LibCore)
|
970
Userland/Libraries/LibChess/Chess.cpp
Normal file
970
Userland/Libraries/LibChess/Chess.cpp
Normal file
|
@ -0,0 +1,970 @@
|
|||
/*
|
||||
* 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 <AK/Assertions.h>
|
||||
#include <AK/LogStream.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibChess/Chess.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
namespace Chess {
|
||||
|
||||
String 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::Type piece_for_char_promotion(const StringView& str)
|
||||
{
|
||||
String string = String(str).to_lowercase();
|
||||
if (string == "")
|
||||
return Type::None;
|
||||
if (string == "n")
|
||||
return Type::Knight;
|
||||
if (string == "b")
|
||||
return Type::Bishop;
|
||||
if (string == "r")
|
||||
return Type::Rook;
|
||||
if (string == "q")
|
||||
return Type::Queen;
|
||||
if (string == "k")
|
||||
return Type::King;
|
||||
|
||||
return Type::None;
|
||||
}
|
||||
|
||||
Color opposing_color(Color color)
|
||||
{
|
||||
return (color == Color::White) ? Color::Black : Color::White;
|
||||
}
|
||||
|
||||
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 Square::to_algebraic() const
|
||||
{
|
||||
StringBuilder builder;
|
||||
builder.append(file + 'a');
|
||||
builder.append(rank + '1');
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
Move::Move(const StringView& long_algebraic)
|
||||
: from(long_algebraic.substring_view(0, 2))
|
||||
, to(long_algebraic.substring_view(2, 2))
|
||||
, promote_to(piece_for_char_promotion((long_algebraic.length() >= 5) ? long_algebraic.substring_view(4, 1) : ""))
|
||||
{
|
||||
}
|
||||
|
||||
String 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();
|
||||
}
|
||||
|
||||
Move Move::from_algebraic(const StringView& algebraic, const Color turn, const Board& board)
|
||||
{
|
||||
String move_string = algebraic;
|
||||
Move move({ 50, 50 }, { 50, 50 });
|
||||
|
||||
if (move_string.contains("-")) {
|
||||
move.from = Square(turn == Color::White ? 0 : 7, 4);
|
||||
move.to = Square(turn == Color::White ? 0 : 7, move_string == "O-O" ? 6 : 2);
|
||||
move.promote_to = Type::None;
|
||||
move.piece = { turn, Type::King };
|
||||
|
||||
return move;
|
||||
}
|
||||
|
||||
if (algebraic.contains("#")) {
|
||||
move.is_mate = true;
|
||||
move_string = move_string.substring(0, move_string.length() - 1);
|
||||
} else if (algebraic.contains("+")) {
|
||||
move.is_check = true;
|
||||
move_string = move_string.substring(0, move_string.length() - 1);
|
||||
}
|
||||
|
||||
if (algebraic.contains("=")) {
|
||||
move.promote_to = piece_for_char_promotion(move_string.split('=').at(1).substring(0, 1));
|
||||
move_string = move_string.split('=').at(0);
|
||||
}
|
||||
|
||||
move.to = Square(move_string.substring(move_string.length() - 2, 2));
|
||||
move_string = move_string.substring(0, move_string.length() - 2);
|
||||
|
||||
if (move_string.contains("x")) {
|
||||
move.is_capture = true;
|
||||
move_string = move_string.substring(0, move_string.length() - 1);
|
||||
}
|
||||
|
||||
if (move_string.is_empty() || move_string.characters()[0] >= 'a') {
|
||||
move.piece = Piece(turn, Type::Pawn);
|
||||
} else {
|
||||
move.piece = Piece(turn, piece_for_char_promotion(move_string.substring(0, 1)));
|
||||
move_string = move_string.substring(1, move_string.length() - 1);
|
||||
}
|
||||
|
||||
Square::for_each([&](const Square& square) {
|
||||
if (!move_string.is_empty()) {
|
||||
if (board.get_piece(square).type == move.piece.type && board.is_legal(Move(square, move.to), turn)) {
|
||||
if (move_string.length() >= 2) {
|
||||
if (square == Square(move_string.substring(0, 2))) {
|
||||
move.from = square;
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
} else if (move_string.characters()[0] <= 57) {
|
||||
if (square.rank == (unsigned)(move_string.characters()[0] - '0')) {
|
||||
move.from = square;
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
} else {
|
||||
if (square.file == (unsigned)(move_string.characters()[0] - 'a')) {
|
||||
move.from = square;
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
} else {
|
||||
if (board.get_piece(square).type == move.piece.type && board.is_legal(Move(square, move.to), turn)) {
|
||||
move.from = square;
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
}
|
||||
});
|
||||
|
||||
return move;
|
||||
}
|
||||
|
||||
String Move::to_algebraic() const
|
||||
{
|
||||
if (piece.type == Type::King && from.file == 4) {
|
||||
if (to.file == 2)
|
||||
return "O-O-O";
|
||||
if (to.file == 6)
|
||||
return "O-O";
|
||||
}
|
||||
|
||||
StringBuilder builder;
|
||||
|
||||
builder.append(char_for_piece(piece.type));
|
||||
|
||||
if (is_ambiguous) {
|
||||
if (from.file != ambiguous.file)
|
||||
builder.append(from.to_algebraic().substring(0, 1));
|
||||
else if (from.rank != ambiguous.rank)
|
||||
builder.append(from.to_algebraic().substring(1, 1));
|
||||
else
|
||||
builder.append(from.to_algebraic());
|
||||
}
|
||||
|
||||
if (is_capture) {
|
||||
if (piece.type == Type::Pawn)
|
||||
builder.append(from.to_algebraic().substring(0, 1));
|
||||
builder.append("x");
|
||||
}
|
||||
|
||||
builder.append(to.to_algebraic());
|
||||
|
||||
if (promote_to != Type::None) {
|
||||
builder.append("=");
|
||||
builder.append(char_for_piece(promote_to));
|
||||
}
|
||||
|
||||
if (is_mate)
|
||||
builder.append("#");
|
||||
else if (is_check)
|
||||
builder.append("+");
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
Board::Board()
|
||||
{
|
||||
// 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 }, { Color::White, Type::Pawn });
|
||||
}
|
||||
|
||||
// Fill black pawns.
|
||||
for (unsigned file = 0; file < 8; ++file) {
|
||||
set_piece({ 6, file }, { Color::Black, Type::Pawn });
|
||||
}
|
||||
|
||||
// Fill while pieces.
|
||||
set_piece(Square("a1"), { Color::White, Type::Rook });
|
||||
set_piece(Square("b1"), { Color::White, Type::Knight });
|
||||
set_piece(Square("c1"), { Color::White, Type::Bishop });
|
||||
set_piece(Square("d1"), { Color::White, Type::Queen });
|
||||
set_piece(Square("e1"), { Color::White, Type::King });
|
||||
set_piece(Square("f1"), { Color::White, Type::Bishop });
|
||||
set_piece(Square("g1"), { Color::White, Type::Knight });
|
||||
set_piece(Square("h1"), { Color::White, Type::Rook });
|
||||
|
||||
// Fill black pieces.
|
||||
set_piece(Square("a8"), { Color::Black, Type::Rook });
|
||||
set_piece(Square("b8"), { Color::Black, Type::Knight });
|
||||
set_piece(Square("c8"), { Color::Black, Type::Bishop });
|
||||
set_piece(Square("d8"), { Color::Black, Type::Queen });
|
||||
set_piece(Square("e8"), { Color::Black, Type::King });
|
||||
set_piece(Square("f8"), { Color::Black, Type::Bishop });
|
||||
set_piece(Square("g8"), { Color::Black, Type::Knight });
|
||||
set_piece(Square("h8"), { Color::Black, Type::Rook });
|
||||
}
|
||||
|
||||
String Board::to_fen() const
|
||||
{
|
||||
StringBuilder builder;
|
||||
|
||||
// 1. Piece placement
|
||||
int empty = 0;
|
||||
for (unsigned rank = 0; rank < 8; rank++) {
|
||||
for (unsigned file = 0; file < 8; file++) {
|
||||
const Piece p(get_piece({ 7 - rank, file }));
|
||||
if (p.type == Type::None) {
|
||||
empty++;
|
||||
continue;
|
||||
}
|
||||
if (empty > 0) {
|
||||
builder.append(String::number(empty));
|
||||
empty = 0;
|
||||
}
|
||||
String piece = char_for_piece(p.type);
|
||||
if (piece == "")
|
||||
piece = "P";
|
||||
|
||||
builder.append(p.color == Color::Black ? piece.to_lowercase() : piece);
|
||||
}
|
||||
if (empty > 0) {
|
||||
builder.append(String::number(empty));
|
||||
empty = 0;
|
||||
}
|
||||
if (rank < 7)
|
||||
builder.append("/");
|
||||
}
|
||||
|
||||
// 2. Active color
|
||||
ASSERT(m_turn != Color::None);
|
||||
builder.append(m_turn == Color::White ? " w " : " b ");
|
||||
|
||||
// 3. Castling availability
|
||||
builder.append(m_white_can_castle_kingside ? "K" : "");
|
||||
builder.append(m_white_can_castle_queenside ? "Q" : "");
|
||||
builder.append(m_black_can_castle_kingside ? "k" : "");
|
||||
builder.append(m_black_can_castle_queenside ? "q" : "");
|
||||
builder.append(" ");
|
||||
|
||||
// 4. En passant target square
|
||||
if (!m_last_move.has_value())
|
||||
builder.append("-");
|
||||
else if (m_last_move.value().piece.type == Type::Pawn) {
|
||||
if (m_last_move.value().from.rank == 1 && m_last_move.value().to.rank == 3)
|
||||
builder.append(Square(m_last_move.value().to.rank - 1, m_last_move.value().to.file).to_algebraic());
|
||||
else if (m_last_move.value().from.rank == 6 && m_last_move.value().to.rank == 4)
|
||||
builder.append(Square(m_last_move.value().to.rank + 1, m_last_move.value().to.file).to_algebraic());
|
||||
else
|
||||
builder.append("-");
|
||||
} else {
|
||||
builder.append("-");
|
||||
}
|
||||
builder.append(" ");
|
||||
|
||||
// 5. Halfmove clock
|
||||
builder.append(String::number(min(m_moves_since_capture, m_moves_since_pawn_advance)));
|
||||
builder.append(" ");
|
||||
|
||||
// 6. Fullmove number
|
||||
builder.append(String::number(1 + m_moves.size() / 2));
|
||||
|
||||
return builder.to_string();
|
||||
}
|
||||
|
||||
Piece Board::get_piece(const Square& square) const
|
||||
{
|
||||
ASSERT(square.rank < 8);
|
||||
ASSERT(square.file < 8);
|
||||
return m_board[square.rank][square.file];
|
||||
}
|
||||
|
||||
Piece Board::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 Board::is_legal_promotion(const Move& move, Color color) const
|
||||
{
|
||||
auto piece = get_piece(move.from);
|
||||
|
||||
if (move.promote_to == Type::Pawn || move.promote_to == Type::King) {
|
||||
// attempted promotion to invalid piece
|
||||
return false;
|
||||
}
|
||||
|
||||
if (piece.type != Type::Pawn && move.promote_to != Type::None) {
|
||||
// attempted promotion from invalid piece
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned promotion_rank = (color == Color::White) ? 7 : 0;
|
||||
|
||||
if (move.to.rank != promotion_rank && move.promote_to != Type::None) {
|
||||
// attempted promotion from invalid rank
|
||||
return false;
|
||||
}
|
||||
|
||||
if (piece.type == Type::Pawn && move.to.rank == promotion_rank && move.promote_to == Type::None) {
|
||||
// attempted move to promotion rank without promoting
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Board::is_legal(const Move& move, Color color) const
|
||||
{
|
||||
if (color == Color::None)
|
||||
color = turn();
|
||||
|
||||
if (!is_legal_no_check(move, color))
|
||||
return false;
|
||||
|
||||
if (!is_legal_promotion(move, color))
|
||||
return false;
|
||||
|
||||
Board clone = *this;
|
||||
clone.apply_illegal_move(move, color);
|
||||
if (clone.in_check(color))
|
||||
return false;
|
||||
|
||||
// Don't allow castling through check or out of check.
|
||||
Vector<Square> check_squares;
|
||||
if (color == Color::White && move.from == Square("e1") && get_piece(Square("e1")) == Piece(Color::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 (color == Color::Black && move.from == Square("e8") && get_piece(Square("e8")) == Piece(Color::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) {
|
||||
Board clone = *this;
|
||||
clone.set_piece(move.from, EmptyPiece);
|
||||
clone.set_piece(square, { color, Type::King });
|
||||
if (clone.in_check(color))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Board::is_legal_no_check(const Move& move, Color color) const
|
||||
{
|
||||
auto piece = get_piece(move.from);
|
||||
|
||||
if (piece.color != color)
|
||||
// attempted move of opponent's piece
|
||||
return false;
|
||||
|
||||
if (move.to.rank > 7 || move.to.file > 7)
|
||||
// attempted move outside of board
|
||||
return false;
|
||||
|
||||
if (piece.type == Type::Pawn) {
|
||||
int dir = (color == Color::White) ? +1 : -1;
|
||||
unsigned start_rank = (color == Color::White) ? 1 : 6;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (move.to.rank != move.from.rank + dir)
|
||||
// attempted backwards or sideways move
|
||||
return false;
|
||||
|
||||
if (move.to.file == move.from.file && get_piece(move.to).type == Type::None) {
|
||||
// Regular pawn move.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (move.to.file == move.from.file + 1 || move.to.file == move.from.file - 1) {
|
||||
unsigned other_start_rank = (color == Color::White) ? 6 : 1;
|
||||
unsigned en_passant_rank = (color == Color::White) ? 4 : 3;
|
||||
Move en_passant_last_move = { { other_start_rank, move.to.file }, { en_passant_rank, move.to.file } };
|
||||
if (get_piece(move.to).color == opposing_color(color)) {
|
||||
// Pawn capture.
|
||||
return true;
|
||||
}
|
||||
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_color(color), Type::Pawn)) {
|
||||
// En passant.
|
||||
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).color != color && 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).color != color) {
|
||||
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).color != color) {
|
||||
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).color != color) {
|
||||
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).color != color) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (color == Color::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 Board::in_check(Color color) const
|
||||
{
|
||||
Square king_square = { 50, 50 };
|
||||
Square::for_each([&](const Square& square) {
|
||||
if (get_piece(square) == Piece(color, Type::King)) {
|
||||
king_square = square;
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
bool check = false;
|
||||
Square::for_each([&](const Square& square) {
|
||||
if (is_legal_no_check({ square, king_square }, opposing_color(color))) {
|
||||
check = true;
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
return check;
|
||||
}
|
||||
|
||||
bool Board::apply_move(const Move& move, Color color)
|
||||
{
|
||||
if (color == Color::None)
|
||||
color = turn();
|
||||
|
||||
if (!is_legal(move, color))
|
||||
return false;
|
||||
|
||||
const_cast<Move&>(move).piece = get_piece(move.from);
|
||||
|
||||
return apply_illegal_move(move, color);
|
||||
}
|
||||
|
||||
bool Board::apply_illegal_move(const Move& move, Color color)
|
||||
{
|
||||
Board clone = *this;
|
||||
clone.m_previous_states = {};
|
||||
clone.m_moves = {};
|
||||
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_moves.append(move);
|
||||
|
||||
m_turn = opposing_color(color);
|
||||
|
||||
m_last_move = move;
|
||||
m_moves_since_capture++;
|
||||
m_moves_since_pawn_advance++;
|
||||
|
||||
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 (color == Color::White && move.from == Square("e1") && get_piece(Square("e1")) == Piece(Color::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"), { Color::White, Type::King });
|
||||
set_piece(Square("d1"), { Color::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"), { Color::White, Type::King });
|
||||
set_piece(Square("f1"), { Color::White, Type::Rook });
|
||||
return true;
|
||||
}
|
||||
} else if (color == Color::Black && move.from == Square("e8") && get_piece(Square("e8")) == Piece(Color::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"), { Color::Black, Type::King });
|
||||
set_piece(Square("d8"), { Color::Black, 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"), { Color::Black, Type::King });
|
||||
set_piece(Square("f8"), { Color::Black, Type::Rook });
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (move.piece.type == Type::Pawn)
|
||||
m_moves_since_pawn_advance = 0;
|
||||
|
||||
if (get_piece(move.to).color != Color::None) {
|
||||
const_cast<Move&>(move).is_capture = true;
|
||||
m_moves_since_capture = 0;
|
||||
}
|
||||
|
||||
if (get_piece(move.from).type == Type::Pawn && ((color == Color::Black && move.to.rank == 0) || (color == Color::White && move.to.rank == 7))) {
|
||||
// Pawn Promotion
|
||||
set_piece(move.to, { color, move.promote_to });
|
||||
set_piece(move.from, EmptyPiece);
|
||||
|
||||
if (in_check(m_turn))
|
||||
const_cast<Move&>(move).is_check = true;
|
||||
|
||||
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 (color == Color::White) {
|
||||
set_piece({ move.to.rank - 1, move.to.file }, EmptyPiece);
|
||||
} else {
|
||||
set_piece({ move.to.rank + 1, move.to.file }, EmptyPiece);
|
||||
}
|
||||
const_cast<Move&>(move).is_capture = true;
|
||||
m_moves_since_capture = 0;
|
||||
}
|
||||
|
||||
Square::for_each([&](Square sq) {
|
||||
// Ambiguous Move
|
||||
if (sq != move.from && get_piece(sq).type == move.piece.type && get_piece(sq).color == move.piece.color) {
|
||||
if (is_legal(Move(sq, move.to), get_piece(sq).color)) {
|
||||
m_moves.last().is_ambiguous = true;
|
||||
m_moves.last().ambiguous = sq;
|
||||
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
set_piece(move.to, get_piece(move.from));
|
||||
set_piece(move.from, EmptyPiece);
|
||||
|
||||
if (in_check(m_turn))
|
||||
const_cast<Move&>(move).is_check = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Move Board::random_move(Color color) const
|
||||
{
|
||||
if (color == Color::None)
|
||||
color = turn();
|
||||
|
||||
Move move = { { 50, 50 }, { 50, 50 } };
|
||||
int probability = 1;
|
||||
generate_moves([&](Move m) {
|
||||
if (rand() % probability == 0)
|
||||
move = m;
|
||||
++probability;
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
return move;
|
||||
}
|
||||
|
||||
Board::Result Board::game_result() const
|
||||
{
|
||||
if (m_resigned != Color::None)
|
||||
return (m_resigned == Color::White) ? Result::WhiteResign : Result::BlackResign;
|
||||
|
||||
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).color == get_piece(bishop.value()).color) {
|
||||
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([&]([[maybe_unused]] Move 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::ThreeFoldRepetition;
|
||||
if (repeats.value() >= 5)
|
||||
return Result::FiveFoldRepetition;
|
||||
}
|
||||
|
||||
return Result::NotFinished;
|
||||
}
|
||||
|
||||
if (in_check(turn())) {
|
||||
const_cast<Vector<Move>&>(m_moves).last().is_mate = true;
|
||||
return Result::CheckMate;
|
||||
}
|
||||
|
||||
return Result::StaleMate;
|
||||
}
|
||||
|
||||
Color Board::game_winner() const
|
||||
{
|
||||
if (game_result() == Result::CheckMate)
|
||||
return opposing_color(turn());
|
||||
|
||||
return Color::None;
|
||||
}
|
||||
|
||||
int Board::game_score() const
|
||||
{
|
||||
switch (game_winner()) {
|
||||
case Color::White:
|
||||
return +1;
|
||||
case Color::Black:
|
||||
return -1;
|
||||
case Color::None:
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Board::game_finished() const
|
||||
{
|
||||
return game_result() != Result::NotFinished;
|
||||
}
|
||||
|
||||
int Board::material_imbalance() const
|
||||
{
|
||||
int imbalance = 0;
|
||||
Square::for_each([&](Square square) {
|
||||
int value = 0;
|
||||
switch (get_piece(square).type) {
|
||||
case Type::Pawn:
|
||||
value = 1;
|
||||
break;
|
||||
case Type::Knight:
|
||||
case Type::Bishop:
|
||||
value = 3;
|
||||
break;
|
||||
case Type::Rook:
|
||||
value = 5;
|
||||
break;
|
||||
case Type::Queen:
|
||||
value = 9;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (get_piece(square).color == Color::White) {
|
||||
imbalance += value;
|
||||
} else {
|
||||
imbalance -= value;
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
return imbalance;
|
||||
}
|
||||
|
||||
bool Board::is_promotion_move(const Move& move, Color color) const
|
||||
{
|
||||
if (color == Color::None)
|
||||
color = turn();
|
||||
|
||||
unsigned promotion_rank = (color == Color::White) ? 7 : 0;
|
||||
if (move.to.rank != promotion_rank)
|
||||
return false;
|
||||
|
||||
if (get_piece(move.from).type != Type::Pawn)
|
||||
return false;
|
||||
|
||||
Move queen_move = move;
|
||||
queen_move.promote_to = Type::Queen;
|
||||
if (!is_legal(queen_move, color))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Board::operator==(const Board& 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();
|
||||
}
|
||||
|
||||
void Board::set_resigned(Chess::Color c)
|
||||
{
|
||||
m_resigned = c;
|
||||
}
|
||||
|
||||
String Board::result_to_string(Result result, Color turn)
|
||||
{
|
||||
switch (result) {
|
||||
case Result::CheckMate:
|
||||
ASSERT(turn != Chess::Color::None);
|
||||
return turn == Chess::Color::White ? "Black wins by Checkmate" : "White wins by Checkmate";
|
||||
case Result::WhiteResign:
|
||||
return "Black wins by Resignation";
|
||||
case Result::BlackResign:
|
||||
return "White wins by Resignation";
|
||||
case Result::StaleMate:
|
||||
return "Draw by Stalemate";
|
||||
case Chess::Board::Result::FiftyMoveRule:
|
||||
return "Draw by 50 move rule";
|
||||
case Chess::Board::Result::SeventyFiveMoveRule:
|
||||
return "Draw by 75 move rule";
|
||||
case Chess::Board::Result::ThreeFoldRepetition:
|
||||
return "Draw by threefold repetition";
|
||||
case Chess::Board::Result::FiveFoldRepetition:
|
||||
return "Draw by fivefold repetition";
|
||||
case Chess::Board::Result::InsufficientMaterial:
|
||||
return "Draw by insufficient material";
|
||||
case Chess::Board::Result::NotFinished:
|
||||
return "Game not finished";
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
String Board::result_to_points(Result result, Color turn)
|
||||
{
|
||||
switch (result) {
|
||||
case Result::CheckMate:
|
||||
ASSERT(turn != Chess::Color::None);
|
||||
return turn == Chess::Color::White ? "0-1" : "1-0";
|
||||
case Result::WhiteResign:
|
||||
return "0-1";
|
||||
case Result::BlackResign:
|
||||
return "1-0";
|
||||
case Result::StaleMate:
|
||||
return "1/2-1/2";
|
||||
case Chess::Board::Result::FiftyMoveRule:
|
||||
return "1/2-1/2";
|
||||
case Chess::Board::Result::SeventyFiveMoveRule:
|
||||
return "1/2-1/2";
|
||||
case Chess::Board::Result::ThreeFoldRepetition:
|
||||
return "1/2-1/2";
|
||||
case Chess::Board::Result::FiveFoldRepetition:
|
||||
return "1/2-1/2";
|
||||
case Chess::Board::Result::InsufficientMaterial:
|
||||
return "1/2-1/2";
|
||||
case Chess::Board::Result::NotFinished:
|
||||
return "*";
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
324
Userland/Libraries/LibChess/Chess.h
Normal file
324
Userland/Libraries/LibChess/Chess.h
Normal file
|
@ -0,0 +1,324 @@
|
|||
/*
|
||||
* 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>
|
||||
#include <AK/Vector.h>
|
||||
|
||||
namespace Chess {
|
||||
|
||||
enum class Type {
|
||||
Pawn,
|
||||
Knight,
|
||||
Bishop,
|
||||
Rook,
|
||||
Queen,
|
||||
King,
|
||||
None,
|
||||
};
|
||||
|
||||
String char_for_piece(Type type);
|
||||
Chess::Type piece_for_char_promotion(const StringView& str);
|
||||
|
||||
enum class Color {
|
||||
White,
|
||||
Black,
|
||||
None,
|
||||
};
|
||||
|
||||
Color opposing_color(Color color);
|
||||
|
||||
struct Piece {
|
||||
constexpr Piece()
|
||||
: color(Color::None)
|
||||
, type(Type::None)
|
||||
{
|
||||
}
|
||||
constexpr Piece(Color c, Type t)
|
||||
: color(c)
|
||||
, type(t)
|
||||
{
|
||||
}
|
||||
Color color : 4;
|
||||
Type type : 4;
|
||||
bool operator==(const Piece& other) const { return color == other.color && type == other.type; }
|
||||
};
|
||||
|
||||
constexpr Piece EmptyPiece = { Color::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)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool in_bounds() const { return rank < 8 && file < 8; }
|
||||
bool is_light() const { return (rank % 2) != (file % 2); }
|
||||
String to_algebraic() const;
|
||||
};
|
||||
|
||||
class Board;
|
||||
|
||||
struct Move {
|
||||
Square from;
|
||||
Square to;
|
||||
Type promote_to;
|
||||
Piece piece;
|
||||
bool is_check = false;
|
||||
bool is_mate = false;
|
||||
bool is_capture = false;
|
||||
bool is_ambiguous = false;
|
||||
Square ambiguous { 50, 50 };
|
||||
Move(const StringView& long_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; }
|
||||
|
||||
static Move from_algebraic(const StringView& algebraic, const Color turn, const Board& board);
|
||||
String to_long_algebraic() const;
|
||||
String to_algebraic() const;
|
||||
};
|
||||
|
||||
class Board {
|
||||
public:
|
||||
Board();
|
||||
|
||||
Piece get_piece(const Square&) const;
|
||||
Piece set_piece(const Square&, const Piece&);
|
||||
|
||||
bool is_legal(const Move&, Color color = Color::None) const;
|
||||
bool in_check(Color color) const;
|
||||
|
||||
bool is_promotion_move(const Move&, Color color = Color::None) const;
|
||||
|
||||
bool apply_move(const Move&, Color color = Color::None);
|
||||
const Optional<Move>& last_move() const { return m_last_move; }
|
||||
|
||||
String to_fen() const;
|
||||
|
||||
enum class Result {
|
||||
CheckMate,
|
||||
StaleMate,
|
||||
WhiteResign,
|
||||
BlackResign,
|
||||
FiftyMoveRule,
|
||||
SeventyFiveMoveRule,
|
||||
ThreeFoldRepetition,
|
||||
FiveFoldRepetition,
|
||||
InsufficientMaterial,
|
||||
NotFinished,
|
||||
};
|
||||
|
||||
static String result_to_string(Result, Color turn);
|
||||
static String result_to_points(Result, Color turn);
|
||||
|
||||
template<typename Callback>
|
||||
void generate_moves(Callback callback, Color color = Color::None) const;
|
||||
Move random_move(Color color = Color::None) const;
|
||||
Result game_result() const;
|
||||
Color game_winner() const;
|
||||
int game_score() const;
|
||||
bool game_finished() const;
|
||||
void set_resigned(Color);
|
||||
int material_imbalance() const;
|
||||
|
||||
Color turn() const { return m_turn; }
|
||||
const Vector<Move>& moves() const { return m_moves; }
|
||||
|
||||
bool operator==(const Board& other) const;
|
||||
|
||||
private:
|
||||
bool is_legal_no_check(const Move&, Color color) const;
|
||||
bool is_legal_promotion(const Move&, Color color) const;
|
||||
bool apply_illegal_move(const Move&, Color color);
|
||||
|
||||
Piece m_board[8][8];
|
||||
Color m_turn { Color::White };
|
||||
Color m_resigned { Color::None };
|
||||
Optional<Move> m_last_move;
|
||||
int m_moves_since_capture { 0 };
|
||||
int m_moves_since_pawn_advance { 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<Board, int> m_previous_states;
|
||||
Vector<Move> m_moves;
|
||||
friend struct AK::Traits<Board>;
|
||||
};
|
||||
|
||||
template<typename Callback>
|
||||
void Board::generate_moves(Callback callback, Color color) const
|
||||
{
|
||||
if (color == Color::None)
|
||||
color = turn();
|
||||
|
||||
auto try_move = [&](Move m) {
|
||||
if (is_legal(m, color)) {
|
||||
if (callback(m) == IterationDecision::Break)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
Square::for_each([&](Square sq) {
|
||||
auto piece = get_piece(sq);
|
||||
if (piece.color != color)
|
||||
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 }, 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 });
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template<>
|
||||
struct AK::Traits<Chess::Piece> : public GenericTraits<Chess::Piece> {
|
||||
static unsigned hash(Chess::Piece piece)
|
||||
{
|
||||
return pair_int_hash(static_cast<u32>(piece.color), static_cast<u32>(piece.type));
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct AK::Traits<Chess::Board> : public GenericTraits<Chess::Board> {
|
||||
static unsigned hash(Chess::Board 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;
|
||||
}
|
||||
};
|
332
Userland/Libraries/LibChess/UCICommand.cpp
Normal file
332
Userland/Libraries/LibChess/UCICommand.cpp
Normal file
|
@ -0,0 +1,332 @@
|
|||
/*
|
||||
* 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 "UCICommand.h"
|
||||
#include <AK/StringBuilder.h>
|
||||
|
||||
namespace Chess::UCI {
|
||||
|
||||
UCICommand UCICommand::from_string(const StringView& command)
|
||||
{
|
||||
auto tokens = command.split_view(' ');
|
||||
ASSERT(tokens[0] == "uci");
|
||||
ASSERT(tokens.size() == 1);
|
||||
return UCICommand();
|
||||
}
|
||||
|
||||
String UCICommand::to_string() const
|
||||
{
|
||||
return "uci\n";
|
||||
}
|
||||
|
||||
DebugCommand DebugCommand::from_string(const StringView& command)
|
||||
{
|
||||
auto tokens = command.split_view(' ');
|
||||
ASSERT(tokens[0] == "debug");
|
||||
ASSERT(tokens.size() == 2);
|
||||
if (tokens[1] == "on")
|
||||
return DebugCommand(Flag::On);
|
||||
if (tokens[1] == "off")
|
||||
return DebugCommand(Flag::On);
|
||||
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
String DebugCommand::to_string() const
|
||||
{
|
||||
if (flag() == Flag::On) {
|
||||
return "debug on\n";
|
||||
} else {
|
||||
return "debug off\n";
|
||||
}
|
||||
}
|
||||
|
||||
IsReadyCommand IsReadyCommand::from_string(const StringView& command)
|
||||
{
|
||||
auto tokens = command.split_view(' ');
|
||||
ASSERT(tokens[0] == "isready");
|
||||
ASSERT(tokens.size() == 1);
|
||||
return IsReadyCommand();
|
||||
}
|
||||
|
||||
String IsReadyCommand::to_string() const
|
||||
{
|
||||
return "isready\n";
|
||||
}
|
||||
|
||||
SetOptionCommand SetOptionCommand::from_string(const StringView& command)
|
||||
{
|
||||
auto tokens = command.split_view(' ');
|
||||
ASSERT(tokens[0] == "setoption");
|
||||
ASSERT(tokens[1] == "name");
|
||||
if (tokens.size() == 3) {
|
||||
return SetOptionCommand(tokens[1]);
|
||||
} else if (tokens.size() == 4) {
|
||||
ASSERT(tokens[2] == "value");
|
||||
return SetOptionCommand(tokens[1], tokens[3]);
|
||||
}
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
String SetOptionCommand::to_string() const
|
||||
{
|
||||
StringBuilder builder;
|
||||
builder.append("setoption name ");
|
||||
builder.append(name());
|
||||
if (value().has_value()) {
|
||||
builder.append(" value ");
|
||||
builder.append(value().value());
|
||||
}
|
||||
builder.append('\n');
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
PositionCommand PositionCommand::from_string(const StringView& command)
|
||||
{
|
||||
auto tokens = command.split_view(' ');
|
||||
ASSERT(tokens.size() >= 3);
|
||||
ASSERT(tokens[0] == "position");
|
||||
ASSERT(tokens[2] == "moves");
|
||||
|
||||
Optional<String> fen;
|
||||
if (tokens[1] != "startpos")
|
||||
fen = tokens[1];
|
||||
|
||||
Vector<Move> moves;
|
||||
for (size_t i = 3; i < tokens.size(); ++i) {
|
||||
moves.append(Move(tokens[i]));
|
||||
}
|
||||
return PositionCommand(fen, moves);
|
||||
}
|
||||
|
||||
String PositionCommand::to_string() const
|
||||
{
|
||||
StringBuilder builder;
|
||||
builder.append("position ");
|
||||
if (fen().has_value()) {
|
||||
builder.append(fen().value());
|
||||
} else {
|
||||
builder.append("startpos ");
|
||||
}
|
||||
builder.append("moves");
|
||||
for (auto& move : moves()) {
|
||||
builder.append(' ');
|
||||
builder.append(move.to_long_algebraic());
|
||||
}
|
||||
builder.append('\n');
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
GoCommand GoCommand::from_string(const StringView& command)
|
||||
{
|
||||
auto tokens = command.split_view(' ');
|
||||
ASSERT(tokens[0] == "go");
|
||||
|
||||
GoCommand go_command;
|
||||
for (size_t i = 1; i < tokens.size(); ++i) {
|
||||
if (tokens[i] == "searchmoves") {
|
||||
ASSERT_NOT_REACHED();
|
||||
} else if (tokens[i] == "ponder") {
|
||||
go_command.ponder = true;
|
||||
} else if (tokens[i] == "wtime") {
|
||||
ASSERT(i++ < tokens.size());
|
||||
go_command.wtime = tokens[i].to_int().value();
|
||||
} else if (tokens[i] == "btime") {
|
||||
ASSERT(i++ < tokens.size());
|
||||
go_command.btime = tokens[i].to_int().value();
|
||||
} else if (tokens[i] == "winc") {
|
||||
ASSERT(i++ < tokens.size());
|
||||
go_command.winc = tokens[i].to_int().value();
|
||||
} else if (tokens[i] == "binc") {
|
||||
ASSERT(i++ < tokens.size());
|
||||
go_command.binc = tokens[i].to_int().value();
|
||||
} else if (tokens[i] == "movestogo") {
|
||||
ASSERT(i++ < tokens.size());
|
||||
go_command.movestogo = tokens[i].to_int().value();
|
||||
} else if (tokens[i] == "depth") {
|
||||
ASSERT(i++ < tokens.size());
|
||||
go_command.depth = tokens[i].to_int().value();
|
||||
} else if (tokens[i] == "nodes") {
|
||||
ASSERT(i++ < tokens.size());
|
||||
go_command.nodes = tokens[i].to_int().value();
|
||||
} else if (tokens[i] == "mate") {
|
||||
ASSERT(i++ < tokens.size());
|
||||
go_command.mate = tokens[i].to_int().value();
|
||||
} else if (tokens[i] == "movetime") {
|
||||
ASSERT(i++ < tokens.size());
|
||||
go_command.movetime = tokens[i].to_int().value();
|
||||
} else if (tokens[i] == "infinite") {
|
||||
go_command.infinite = true;
|
||||
}
|
||||
}
|
||||
|
||||
return go_command;
|
||||
}
|
||||
|
||||
String GoCommand::to_string() const
|
||||
{
|
||||
StringBuilder builder;
|
||||
builder.append("go");
|
||||
|
||||
if (searchmoves.has_value()) {
|
||||
builder.append(" searchmoves");
|
||||
for (auto& move : searchmoves.value()) {
|
||||
builder.append(' ');
|
||||
builder.append(move.to_long_algebraic());
|
||||
}
|
||||
}
|
||||
|
||||
if (ponder)
|
||||
builder.append(" ponder");
|
||||
if (wtime.has_value())
|
||||
builder.appendff(" wtime {}", wtime.value());
|
||||
if (btime.has_value())
|
||||
builder.appendff(" btime {}", btime.value());
|
||||
if (winc.has_value())
|
||||
builder.appendff(" winc {}", winc.value());
|
||||
if (binc.has_value())
|
||||
builder.appendff(" binc {}", binc.value());
|
||||
if (movestogo.has_value())
|
||||
builder.appendff(" movestogo {}", movestogo.value());
|
||||
if (depth.has_value())
|
||||
builder.appendff(" depth {}", depth.value());
|
||||
if (nodes.has_value())
|
||||
builder.appendff(" nodes {}", nodes.value());
|
||||
if (mate.has_value())
|
||||
builder.appendff(" mate {}", mate.value());
|
||||
if (movetime.has_value())
|
||||
builder.appendff(" movetime {}", movetime.value());
|
||||
if (infinite)
|
||||
builder.append(" infinite");
|
||||
|
||||
builder.append('\n');
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
StopCommand StopCommand::from_string(const StringView& command)
|
||||
{
|
||||
auto tokens = command.split_view(' ');
|
||||
ASSERT(tokens[0] == "stop");
|
||||
ASSERT(tokens.size() == 1);
|
||||
return StopCommand();
|
||||
}
|
||||
|
||||
String StopCommand::to_string() const
|
||||
{
|
||||
return "stop\n";
|
||||
}
|
||||
|
||||
IdCommand IdCommand::from_string(const StringView& command)
|
||||
{
|
||||
auto tokens = command.split_view(' ');
|
||||
ASSERT(tokens[0] == "id");
|
||||
StringBuilder value;
|
||||
for (size_t i = 2; i < tokens.size(); ++i) {
|
||||
if (i != 2)
|
||||
value.append(' ');
|
||||
|
||||
value.append(tokens[i]);
|
||||
}
|
||||
|
||||
if (tokens[1] == "name") {
|
||||
return IdCommand(Type::Name, value.build());
|
||||
} else if (tokens[1] == "author") {
|
||||
return IdCommand(Type::Author, value.build());
|
||||
}
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
String IdCommand::to_string() const
|
||||
{
|
||||
StringBuilder builder;
|
||||
builder.append("id ");
|
||||
if (field_type() == Type::Name) {
|
||||
builder.append("name ");
|
||||
} else {
|
||||
builder.append("author ");
|
||||
}
|
||||
builder.append(value());
|
||||
builder.append('\n');
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
UCIOkCommand UCIOkCommand::from_string(const StringView& command)
|
||||
{
|
||||
auto tokens = command.split_view(' ');
|
||||
ASSERT(tokens[0] == "uciok");
|
||||
ASSERT(tokens.size() == 1);
|
||||
return UCIOkCommand();
|
||||
}
|
||||
|
||||
String UCIOkCommand::to_string() const
|
||||
{
|
||||
return "uciok\n";
|
||||
}
|
||||
|
||||
ReadyOkCommand ReadyOkCommand::from_string(const StringView& command)
|
||||
{
|
||||
auto tokens = command.split_view(' ');
|
||||
ASSERT(tokens[0] == "readyok");
|
||||
ASSERT(tokens.size() == 1);
|
||||
return ReadyOkCommand();
|
||||
}
|
||||
|
||||
String ReadyOkCommand::to_string() const
|
||||
{
|
||||
return "readyok\n";
|
||||
}
|
||||
|
||||
BestMoveCommand BestMoveCommand::from_string(const StringView& command)
|
||||
{
|
||||
auto tokens = command.split_view(' ');
|
||||
ASSERT(tokens[0] == "bestmove");
|
||||
ASSERT(tokens.size() == 2);
|
||||
return BestMoveCommand(Move(tokens[1]));
|
||||
}
|
||||
|
||||
String BestMoveCommand::to_string() const
|
||||
{
|
||||
StringBuilder builder;
|
||||
builder.append("bestmove ");
|
||||
builder.append(move().to_long_algebraic());
|
||||
builder.append('\n');
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
InfoCommand InfoCommand::from_string([[maybe_unused]] const StringView& command)
|
||||
{
|
||||
// FIXME: Implement this.
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
String InfoCommand::to_string() const
|
||||
{
|
||||
// FIXME: Implement this.
|
||||
ASSERT_NOT_REACHED();
|
||||
return "info";
|
||||
}
|
||||
|
||||
}
|
291
Userland/Libraries/LibChess/UCICommand.h
Normal file
291
Userland/Libraries/LibChess/UCICommand.h
Normal file
|
@ -0,0 +1,291 @@
|
|||
/*
|
||||
* 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/Optional.h>
|
||||
#include <AK/String.h>
|
||||
#include <LibChess/Chess.h>
|
||||
#include <LibCore/Event.h>
|
||||
|
||||
namespace Chess::UCI {
|
||||
|
||||
class Command : public Core::Event {
|
||||
public:
|
||||
enum Type {
|
||||
// GUI to engine commands.
|
||||
UCI = 12000,
|
||||
Debug,
|
||||
IsReady,
|
||||
SetOption,
|
||||
Register,
|
||||
UCINewGame,
|
||||
Position,
|
||||
Go,
|
||||
Stop,
|
||||
PonderHit,
|
||||
Quit,
|
||||
// Engine to GUI commands.
|
||||
Id,
|
||||
UCIOk,
|
||||
ReadyOk,
|
||||
BestMove,
|
||||
CopyProtection,
|
||||
Registration,
|
||||
Info,
|
||||
Option,
|
||||
};
|
||||
|
||||
explicit Command(Type type)
|
||||
: Core::Event(type)
|
||||
{
|
||||
}
|
||||
|
||||
virtual String to_string() const = 0;
|
||||
|
||||
virtual ~Command() { }
|
||||
};
|
||||
|
||||
class UCICommand : public Command {
|
||||
public:
|
||||
explicit UCICommand()
|
||||
: Command(Command::Type::UCI)
|
||||
{
|
||||
}
|
||||
|
||||
static UCICommand from_string(const StringView& command);
|
||||
|
||||
virtual String to_string() const;
|
||||
};
|
||||
|
||||
class DebugCommand : public Command {
|
||||
public:
|
||||
enum class Flag {
|
||||
On,
|
||||
Off
|
||||
};
|
||||
|
||||
explicit DebugCommand(Flag flag)
|
||||
: Command(Command::Type::Debug)
|
||||
, m_flag(flag)
|
||||
{
|
||||
}
|
||||
|
||||
static DebugCommand from_string(const StringView& command);
|
||||
|
||||
virtual String to_string() const;
|
||||
|
||||
Flag flag() const { return m_flag; }
|
||||
|
||||
private:
|
||||
Flag m_flag;
|
||||
};
|
||||
|
||||
class IsReadyCommand : public Command {
|
||||
public:
|
||||
explicit IsReadyCommand()
|
||||
: Command(Command::Type::IsReady)
|
||||
{
|
||||
}
|
||||
|
||||
static IsReadyCommand from_string(const StringView& command);
|
||||
|
||||
virtual String to_string() const;
|
||||
};
|
||||
|
||||
class SetOptionCommand : public Command {
|
||||
public:
|
||||
explicit SetOptionCommand(const StringView& name, Optional<String> value = {})
|
||||
: Command(Command::Type::SetOption)
|
||||
, m_name(name)
|
||||
, m_value(value)
|
||||
{
|
||||
}
|
||||
|
||||
static SetOptionCommand from_string(const StringView& command);
|
||||
|
||||
virtual String to_string() const;
|
||||
|
||||
const String& name() const { return m_name; }
|
||||
const Optional<String>& value() const { return m_value; }
|
||||
|
||||
private:
|
||||
String m_name;
|
||||
Optional<String> m_value;
|
||||
};
|
||||
|
||||
class PositionCommand : public Command {
|
||||
public:
|
||||
explicit PositionCommand(const Optional<String>& fen, const Vector<Chess::Move>& moves)
|
||||
: Command(Command::Type::Position)
|
||||
, m_fen(fen)
|
||||
, m_moves(moves)
|
||||
{
|
||||
}
|
||||
|
||||
static PositionCommand from_string(const StringView& command);
|
||||
|
||||
virtual String to_string() const;
|
||||
|
||||
const Optional<String>& fen() const { return m_fen; }
|
||||
const Vector<Chess::Move>& moves() const { return m_moves; }
|
||||
|
||||
private:
|
||||
Optional<String> m_fen;
|
||||
Vector<Chess::Move> m_moves;
|
||||
};
|
||||
|
||||
class GoCommand : public Command {
|
||||
public:
|
||||
explicit GoCommand()
|
||||
: Command(Command::Type::Go)
|
||||
{
|
||||
}
|
||||
|
||||
static GoCommand from_string(const StringView& command);
|
||||
|
||||
virtual String to_string() const;
|
||||
|
||||
Optional<Vector<Chess::Move>> searchmoves;
|
||||
bool ponder { false };
|
||||
Optional<int> wtime;
|
||||
Optional<int> btime;
|
||||
Optional<int> winc;
|
||||
Optional<int> binc;
|
||||
Optional<int> movestogo;
|
||||
Optional<int> depth;
|
||||
Optional<int> nodes;
|
||||
Optional<int> mate;
|
||||
Optional<int> movetime;
|
||||
bool infinite { false };
|
||||
};
|
||||
|
||||
class StopCommand : public Command {
|
||||
public:
|
||||
explicit StopCommand()
|
||||
: Command(Command::Type::Stop)
|
||||
{
|
||||
}
|
||||
|
||||
static StopCommand from_string(const StringView& command);
|
||||
|
||||
virtual String to_string() const;
|
||||
};
|
||||
|
||||
class IdCommand : public Command {
|
||||
public:
|
||||
enum class Type {
|
||||
Name,
|
||||
Author,
|
||||
};
|
||||
|
||||
explicit IdCommand(Type field_type, const StringView& value)
|
||||
: Command(Command::Type::Id)
|
||||
, m_field_type(field_type)
|
||||
, m_value(value)
|
||||
{
|
||||
}
|
||||
|
||||
static IdCommand from_string(const StringView& command);
|
||||
|
||||
virtual String to_string() const;
|
||||
|
||||
Type field_type() const { return m_field_type; }
|
||||
const String& value() const { return m_value; }
|
||||
|
||||
private:
|
||||
Type m_field_type;
|
||||
String m_value;
|
||||
};
|
||||
|
||||
class UCIOkCommand : public Command {
|
||||
public:
|
||||
explicit UCIOkCommand()
|
||||
: Command(Command::Type::UCIOk)
|
||||
{
|
||||
}
|
||||
|
||||
static UCIOkCommand from_string(const StringView& command);
|
||||
|
||||
virtual String to_string() const;
|
||||
};
|
||||
|
||||
class ReadyOkCommand : public Command {
|
||||
public:
|
||||
explicit ReadyOkCommand()
|
||||
: Command(Command::Type::ReadyOk)
|
||||
{
|
||||
}
|
||||
|
||||
static ReadyOkCommand from_string(const StringView& command);
|
||||
|
||||
virtual String to_string() const;
|
||||
};
|
||||
|
||||
class BestMoveCommand : public Command {
|
||||
public:
|
||||
explicit BestMoveCommand(const Chess::Move& move)
|
||||
: Command(Command::Type::BestMove)
|
||||
, m_move(move)
|
||||
{
|
||||
}
|
||||
|
||||
static BestMoveCommand from_string(const StringView& command);
|
||||
|
||||
virtual String to_string() const;
|
||||
|
||||
Chess::Move move() const { return m_move; }
|
||||
|
||||
private:
|
||||
Chess::Move m_move;
|
||||
};
|
||||
|
||||
class InfoCommand : public Command {
|
||||
public:
|
||||
explicit InfoCommand()
|
||||
: Command(Command::Type::BestMove)
|
||||
{
|
||||
}
|
||||
|
||||
static InfoCommand from_string(const StringView& command);
|
||||
|
||||
virtual String to_string() const;
|
||||
|
||||
Optional<int> depth;
|
||||
Optional<int> seldepth;
|
||||
Optional<int> time;
|
||||
Optional<int> nodes;
|
||||
Optional<Vector<Chess::Move>> pv;
|
||||
// FIXME: Add multipv.
|
||||
Optional<int> score_cp;
|
||||
Optional<int> score_mate;
|
||||
// FIXME: Add score bounds.
|
||||
Optional<Chess::Move> currmove;
|
||||
Optional<int> currmove_number;
|
||||
// FIXME: Add additional fields.
|
||||
};
|
||||
|
||||
}
|
132
Userland/Libraries/LibChess/UCIEndpoint.cpp
Normal file
132
Userland/Libraries/LibChess/UCIEndpoint.cpp
Normal file
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* 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 "UCIEndpoint.h"
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/String.h>
|
||||
#include <LibCore/EventLoop.h>
|
||||
#include <LibCore/File.h>
|
||||
|
||||
// #define UCI_DEBUG
|
||||
|
||||
namespace Chess::UCI {
|
||||
|
||||
Endpoint::Endpoint(NonnullRefPtr<Core::IODevice> in, NonnullRefPtr<Core::IODevice> out)
|
||||
: m_in(in)
|
||||
, m_out(out)
|
||||
, m_in_notifier(Core::Notifier::construct(in->fd(), Core::Notifier::Read))
|
||||
{
|
||||
set_in_notifier();
|
||||
}
|
||||
|
||||
void Endpoint::send_command(const Command& command)
|
||||
{
|
||||
#ifdef UCI_DEBUG
|
||||
dbgln("{} Sent UCI Command: {}", class_name(), String(command.to_string().characters(), Chomp));
|
||||
#endif
|
||||
m_out->write(command.to_string());
|
||||
}
|
||||
|
||||
void Endpoint::event(Core::Event& event)
|
||||
{
|
||||
switch (event.type()) {
|
||||
case Command::Type::UCI:
|
||||
return handle_uci();
|
||||
case Command::Type::Debug:
|
||||
return handle_debug(static_cast<const DebugCommand&>(event));
|
||||
case Command::Type::IsReady:
|
||||
return handle_uci();
|
||||
case Command::Type::SetOption:
|
||||
return handle_setoption(static_cast<const SetOptionCommand&>(event));
|
||||
case Command::Type::Position:
|
||||
return handle_position(static_cast<const PositionCommand&>(event));
|
||||
case Command::Type::Go:
|
||||
return handle_go(static_cast<const GoCommand&>(event));
|
||||
case Command::Type::Stop:
|
||||
return handle_stop();
|
||||
case Command::Type::Id:
|
||||
return handle_id(static_cast<const IdCommand&>(event));
|
||||
case Command::Type::UCIOk:
|
||||
return handle_uciok();
|
||||
case Command::Type::ReadyOk:
|
||||
return handle_readyok();
|
||||
case Command::Type::BestMove:
|
||||
return handle_bestmove(static_cast<const BestMoveCommand&>(event));
|
||||
case Command::Type::Info:
|
||||
return handle_info(static_cast<const InfoCommand&>(event));
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Endpoint::set_in_notifier()
|
||||
{
|
||||
m_in_notifier = Core::Notifier::construct(m_in->fd(), Core::Notifier::Read);
|
||||
m_in_notifier->on_ready_to_read = [this] {
|
||||
while (m_in->can_read_line())
|
||||
Core::EventLoop::current().post_event(*this, read_command());
|
||||
};
|
||||
}
|
||||
|
||||
NonnullOwnPtr<Command> Endpoint::read_command()
|
||||
{
|
||||
String line(ReadonlyBytes(m_in->read_line(4096).bytes()), Chomp);
|
||||
|
||||
#ifdef UCI_DEBUG
|
||||
dbgln("{} Received UCI Command: {}", class_name(), line);
|
||||
#endif
|
||||
|
||||
if (line == "uci") {
|
||||
return make<UCICommand>(UCICommand::from_string(line));
|
||||
} else if (line.starts_with("debug")) {
|
||||
return make<DebugCommand>(DebugCommand::from_string(line));
|
||||
} else if (line.starts_with("isready")) {
|
||||
return make<IsReadyCommand>(IsReadyCommand::from_string(line));
|
||||
} else if (line.starts_with("setoption")) {
|
||||
return make<SetOptionCommand>(SetOptionCommand::from_string(line));
|
||||
} else if (line.starts_with("position")) {
|
||||
return make<PositionCommand>(PositionCommand::from_string(line));
|
||||
} else if (line.starts_with("go")) {
|
||||
return make<GoCommand>(GoCommand::from_string(line));
|
||||
} else if (line.starts_with("stop")) {
|
||||
return make<StopCommand>(StopCommand::from_string(line));
|
||||
} else if (line.starts_with("id")) {
|
||||
return make<IdCommand>(IdCommand::from_string(line));
|
||||
} else if (line.starts_with("uciok")) {
|
||||
return make<UCIOkCommand>(UCIOkCommand::from_string(line));
|
||||
} else if (line.starts_with("readyok")) {
|
||||
return make<ReadyOkCommand>(ReadyOkCommand::from_string(line));
|
||||
} else if (line.starts_with("bestmove")) {
|
||||
return make<BestMoveCommand>(BestMoveCommand::from_string(line));
|
||||
} else if (line.starts_with("info")) {
|
||||
return make<InfoCommand>(InfoCommand::from_string(line));
|
||||
}
|
||||
|
||||
dbgln("command line: {}", line);
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
};
|
80
Userland/Libraries/LibChess/UCIEndpoint.h
Normal file
80
Userland/Libraries/LibChess/UCIEndpoint.h
Normal file
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* 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 <LibChess/UCICommand.h>
|
||||
#include <LibCore/IODevice.h>
|
||||
#include <LibCore/Notifier.h>
|
||||
#include <LibCore/Object.h>
|
||||
|
||||
namespace Chess::UCI {
|
||||
|
||||
class Endpoint : public Core::Object {
|
||||
C_OBJECT(Endpoint)
|
||||
public:
|
||||
virtual ~Endpoint() override { }
|
||||
|
||||
Endpoint() { }
|
||||
Endpoint(NonnullRefPtr<Core::IODevice> in, NonnullRefPtr<Core::IODevice> out);
|
||||
|
||||
virtual void handle_uci() { }
|
||||
virtual void handle_debug(const DebugCommand&) { }
|
||||
virtual void handle_isready() { }
|
||||
virtual void handle_setoption(const SetOptionCommand&) { }
|
||||
virtual void handle_position(const PositionCommand&) { }
|
||||
virtual void handle_go(const GoCommand&) { }
|
||||
virtual void handle_stop() { }
|
||||
virtual void handle_id(const IdCommand&) { }
|
||||
virtual void handle_uciok() { }
|
||||
virtual void handle_readyok() { }
|
||||
virtual void handle_bestmove(const BestMoveCommand&) { }
|
||||
virtual void handle_info(const InfoCommand&) { }
|
||||
|
||||
void send_command(const Command&);
|
||||
|
||||
virtual void event(Core::Event&);
|
||||
|
||||
Core::IODevice& in() { return *m_in; }
|
||||
Core::IODevice& out() { return *m_out; }
|
||||
|
||||
void set_in(RefPtr<Core::IODevice> in)
|
||||
{
|
||||
m_in = in;
|
||||
set_in_notifier();
|
||||
}
|
||||
void set_out(RefPtr<Core::IODevice> out) { m_out = out; }
|
||||
|
||||
private:
|
||||
void set_in_notifier();
|
||||
NonnullOwnPtr<Command> read_command();
|
||||
|
||||
RefPtr<Core::IODevice> m_in;
|
||||
RefPtr<Core::IODevice> m_out;
|
||||
RefPtr<Core::Notifier> m_in_notifier;
|
||||
};
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue