diff --git a/Games/Chess/CMakeLists.txt b/Games/Chess/CMakeLists.txt index 110a5decd37..1f2fe123e83 100644 --- a/Games/Chess/CMakeLists.txt +++ b/Games/Chess/CMakeLists.txt @@ -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) diff --git a/Games/Chess/ChessWidget.cpp b/Games/Chess/ChessWidget.cpp index 5b671ce8d88..5c9a944e89e 100644 --- a/Games/Chess/ChessWidget.cpp +++ b/Games/Chess/ChessWidget.cpp @@ -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 ChessWidget::get_piece_graphic(const Chess::Piece& piece) co void ChessWidget::reset() { - m_board = Chess(); + m_board = Chess::Board(); m_drag_enabled = true; update(); } diff --git a/Games/Chess/ChessWidget.h b/Games/Chess/ChessWidget.h index f8e0b3d6244..893187f3604 100644 --- a/Games/Chess/ChessWidget.h +++ b/Games/Chess/ChessWidget.h @@ -26,11 +26,11 @@ #pragma once -#include "Chess.h" #include #include #include #include +#include #include #include @@ -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 }; diff --git a/Libraries/CMakeLists.txt b/Libraries/CMakeLists.txt index e0180cf3baa..23aa1451c87 100644 --- a/Libraries/CMakeLists.txt +++ b/Libraries/CMakeLists.txt @@ -1,5 +1,6 @@ add_subdirectory(LibAudio) add_subdirectory(LibC) +add_subdirectory(LibChess) add_subdirectory(LibCore) add_subdirectory(LibCrypt) add_subdirectory(LibCrypto) diff --git a/Libraries/LibChess/CMakeLists.txt b/Libraries/LibChess/CMakeLists.txt new file mode 100644 index 00000000000..166829bc7c2 --- /dev/null +++ b/Libraries/LibChess/CMakeLists.txt @@ -0,0 +1,6 @@ +set(SOURCES + Chess.cpp +) + +serenity_lib(LibChess chess) +target_link_libraries(LibChess LibC) diff --git a/Games/Chess/Chess.cpp b/Libraries/LibChess/Chess.cpp similarity index 95% rename from Games/Chess/Chess.cpp rename to Libraries/LibChess/Chess.cpp index 86eccc0daf5..7220d7b6d56 100644 --- a/Games/Chess/Chess.cpp +++ b/Libraries/LibChess/Chess.cpp @@ -24,15 +24,17 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "Chess.h" #include #include #include #include #include +#include #include -String Chess::char_for_piece(Chess::Type type) +namespace Chess { + +String char_for_piece(Chess::Type type) { switch (type) { case Type::Knight: @@ -51,7 +53,12 @@ String Chess::char_for_piece(Chess::Type type) } } -Chess::Square::Square(const StringView& name) +Colour opposing_colour(Colour colour) +{ + return (colour == Colour::White) ? Colour::Black : Colour::White; +} + +Square::Square(const StringView& name) { ASSERT(name.length() == 2); char filec = name[0]; @@ -72,7 +79,7 @@ Chess::Square::Square(const StringView& name) } } -String Chess::Square::to_algebraic() const +String Square::to_algebraic() const { StringBuilder builder; builder.append(file - 'a'); @@ -80,7 +87,7 @@ String Chess::Square::to_algebraic() const return builder.build(); } -String Chess::Move::to_long_algebraic() const +String Move::to_long_algebraic() const { StringBuilder builder; builder.append(from.to_algebraic()); @@ -89,7 +96,7 @@ String Chess::Move::to_long_algebraic() const return builder.build(); } -Chess::Chess() +Board::Board() { // Fill empty spaces. for (unsigned rank = 2; rank < 6; ++rank) { @@ -129,21 +136,21 @@ Chess::Chess() set_piece(Square("h8"), { Colour::Black, Type::Rook }); } -Chess::Piece Chess::get_piece(const Square& square) const +Piece Board::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) +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 Chess::is_legal(const Move& move, Colour colour) const +bool Board::is_legal(const Move& move, Colour colour) const { if (colour == Colour::None) colour = turn(); @@ -151,7 +158,7 @@ bool Chess::is_legal(const Move& move, Colour colour) const if (!is_legal_no_check(move, colour)) return false; - Chess clone = *this; + Board clone = *this; clone.apply_illegal_move(move, colour); if (clone.in_check(colour)) return false; @@ -172,7 +179,7 @@ bool Chess::is_legal(const Move& move, Colour colour) const } } for (auto& square : check_squares) { - Chess clone = *this; + Board clone = *this; clone.set_piece(move.from, EmptyPiece); clone.set_piece(square, { colour, Type::King }); if (clone.in_check(colour)) @@ -182,7 +189,7 @@ bool Chess::is_legal(const Move& move, Colour colour) const return true; } -bool Chess::is_legal_no_check(const Move& move, Colour colour) const +bool Board::is_legal_no_check(const Move& move, Colour colour) const { auto piece = get_piece(move.from); if (piece.colour != colour) @@ -314,7 +321,7 @@ bool Chess::is_legal_no_check(const Move& move, Colour colour) const return false; } -bool Chess::in_check(Colour colour) const +bool Board::in_check(Colour colour) const { Square king_square = { 50, 50 }; Square::for_each([&](const Square& square) { @@ -341,7 +348,7 @@ bool Chess::in_check(Colour colour) const return check; } -bool Chess::apply_move(const Move& move, Colour colour) +bool Board::apply_move(const Move& move, Colour colour) { if (colour == Colour::None) colour = turn(); @@ -352,9 +359,9 @@ bool Chess::apply_move(const Move& move, Colour colour) return apply_illegal_move(move, colour); } -bool Chess::apply_illegal_move(const Move& move, Colour colour) +bool Board::apply_illegal_move(const Move& move, Colour colour) { - Chess clone = *this; + Board clone = *this; clone.m_previous_states = {}; auto state_count = 0; if (m_previous_states.contains(clone)) @@ -431,7 +438,7 @@ bool Chess::apply_illegal_move(const Move& move, Colour colour) return true; } -Chess::Result Chess::game_result() const +Board::Result Board::game_result() const { bool sufficient_material = false; bool no_more_pieces_allowed = false; @@ -501,7 +508,7 @@ Chess::Result Chess::game_result() const return Result::StaleMate; } -bool Chess::is_promotion_move(const Move& move, Colour colour) const +bool Board::is_promotion_move(const Move& move, Colour colour) const { if (colour == Colour::None) colour = turn(); @@ -517,7 +524,7 @@ bool Chess::is_promotion_move(const Move& move, Colour colour) const return false; } -bool Chess::operator==(const Chess& other) const +bool Board::operator==(const Board& other) const { bool equal_squares = true; Square::for_each([&](Square sq) { @@ -541,3 +548,5 @@ bool Chess::operator==(const Chess& other) const return turn() == other.turn(); } + +} diff --git a/Games/Chess/Chess.h b/Libraries/LibChess/Chess.h similarity index 65% rename from Games/Chess/Chess.h rename to Libraries/LibChess/Chess.h index b4c0fd520ad..1848326f07c 100644 --- a/Games/Chess/Chess.h +++ b/Libraries/LibChess/Chess.h @@ -32,80 +32,82 @@ #include #include -class Chess { -public: - enum class Type { - Pawn, - Knight, - Bishop, - Rook, - Queen, - King, - None, - }; - static String char_for_piece(Type type); +namespace Chess { - enum class Colour { - White, - Black, - None, - }; - static Colour opposing_colour(Colour colour) { return (colour == Colour::White) ? Colour::Black : Colour::White; } +enum class Type { + Pawn, + Knight, + Bishop, + Rook, + Queen, + King, + None, +}; - struct Piece { - Colour colour; - Type type; - bool operator==(const Piece& other) const { return colour == other.colour && type == other.type; } +String char_for_piece(Type type); - }; - static constexpr Piece EmptyPiece = { Colour::None, Type::None }; +enum class Colour { + White, + Black, + 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; } +Colour opposing_colour(Colour colour); - template - 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; - } - } +struct Piece { + Colour colour; + Type type; + bool operator==(const Piece& other) const { return colour == other.colour && type == other.type; } +}; + +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 + 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; } - exit:; } + } - bool in_bounds() const { return rank < 8 && file < 8; } - bool is_light() const { return (rank % 2) != (file % 2); } - String to_algebraic() const; - }; + 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; } +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; - }; + String to_long_algebraic() const; +}; - Chess(); +class Board { +public: + Board(); Piece get_piece(const Square&) const; Piece set_piece(const Square&, const Piece&); @@ -135,7 +137,7 @@ public: Colour turn() const { return m_turn; }; - bool operator==(const Chess& other) const; + bool operator==(const Board& other) const; private: bool is_legal_no_check(const Move&, Colour colour) const; @@ -151,41 +153,12 @@ private: bool m_black_can_castle_kingside { true }; bool m_black_can_castle_queenside { true }; - HashMap m_previous_states; - friend struct AK::Traits; -}; - -template<> -struct AK::Traits : public GenericTraits { - static unsigned hash(Chess::Piece piece) - { - return pair_int_hash(static_cast(piece.colour), static_cast(piece.type)); - } -}; - -template<> -struct AK::Traits : public GenericTraits { - static unsigned hash(Chess chess) - { - unsigned hash = 0; - hash = pair_int_hash(hash, static_cast(chess.m_white_can_castle_queenside)); - hash = pair_int_hash(hash, static_cast(chess.m_white_can_castle_kingside)); - hash = pair_int_hash(hash, static_cast(chess.m_black_can_castle_queenside)); - hash = pair_int_hash(hash, static_cast(chess.m_black_can_castle_kingside)); - - hash = pair_int_hash(hash, static_cast(chess.m_black_can_castle_kingside)); - - Chess::Square::for_each([&](Chess::Square sq) { - hash = pair_int_hash(hash, Traits::hash(chess.get_piece(sq))); - return IterationDecision::Continue; - }); - - return hash; - } + HashMap m_previous_states; + friend struct AK::Traits; }; template -void Chess::generate_moves(Callback callback, Colour colour) const +void Board::generate_moves(Callback callback, Colour colour) const { if (colour == Colour::None) colour = turn(); @@ -205,27 +178,25 @@ void Chess::generate_moves(Callback callback, Colour colour) const 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}}); + 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}}); + 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) { @@ -280,3 +251,34 @@ void Chess::generate_moves(Callback callback, Colour colour) const } }); } + +} + +template<> +struct AK::Traits : public GenericTraits { + static unsigned hash(Chess::Piece piece) + { + return pair_int_hash(static_cast(piece.colour), static_cast(piece.type)); + } +}; + +template<> +struct AK::Traits : public GenericTraits { + static unsigned hash(Chess::Board chess) + { + unsigned hash = 0; + hash = pair_int_hash(hash, static_cast(chess.m_white_can_castle_queenside)); + hash = pair_int_hash(hash, static_cast(chess.m_white_can_castle_kingside)); + hash = pair_int_hash(hash, static_cast(chess.m_black_can_castle_queenside)); + hash = pair_int_hash(hash, static_cast(chess.m_black_can_castle_kingside)); + + hash = pair_int_hash(hash, static_cast(chess.m_black_can_castle_kingside)); + + Chess::Square::for_each([&](Chess::Square sq) { + hash = pair_int_hash(hash, Traits::hash(chess.get_piece(sq))); + return IterationDecision::Continue; + }); + + return hash; + } +};