Browse Source

Chess: Refactor game logic into LibChess for use in engines

In the future UCI protocol stuff will also go into LibChess.
Peter Elliott 4 years ago
parent
commit
d2cb5e0f48

+ 1 - 2
Games/Chess/CMakeLists.txt

@@ -1,9 +1,8 @@
 set(SOURCES
 set(SOURCES
     main.cpp
     main.cpp
-    Chess.cpp
     ChessWidget.cpp
     ChessWidget.cpp
     PromotionDialog.cpp
     PromotionDialog.cpp
 )
 )
 
 
 serenity_bin(Chess)
 serenity_bin(Chess)
-target_link_libraries(Chess LibGUI)
+target_link_libraries(Chess LibChess LibGUI)

+ 9 - 9
Games/Chess/ChessWidget.cpp

@@ -123,22 +123,22 @@ void ChessWidget::mouseup_event(GUI::MouseEvent& event)
     }
     }
 
 
     if (board().apply_move(move)) {
     if (board().apply_move(move)) {
-        if (board().game_result() != Chess::Result::NotFinished) {
+        if (board().game_result() != Chess::Board::Result::NotFinished) {
 
 
             bool over = true;
             bool over = true;
             String msg;
             String msg;
             switch (board().game_result()) {
             switch (board().game_result()) {
-            case Chess::Result::CheckMate:
+            case Chess::Board::Result::CheckMate:
                 if (board().turn() == Chess::Colour::White) {
                 if (board().turn() == Chess::Colour::White) {
                     msg = "Black wins by Checkmate.";
                     msg = "Black wins by Checkmate.";
                 } else {
                 } else {
                     msg = "White wins by Checkmate.";
                     msg = "White wins by Checkmate.";
                 }
                 }
                 break;
                 break;
-            case Chess::Result::StaleMate:
+            case Chess::Board::Result::StaleMate:
                 msg = "Draw by Stalemate.";
                 msg = "Draw by Stalemate.";
                 break;
                 break;
-            case Chess::Result::FiftyMoveRule:
+            case Chess::Board::Result::FiftyMoveRule:
                 update();
                 update();
                 if (GUI::MessageBox::show(window(), "50 moves have elapsed without a capture. Claim Draw?", "Claim Draw?",
                 if (GUI::MessageBox::show(window(), "50 moves have elapsed without a capture. Claim Draw?", "Claim Draw?",
                         GUI::MessageBox::Type::Information, GUI::MessageBox::InputType::YesNo)
                         GUI::MessageBox::Type::Information, GUI::MessageBox::InputType::YesNo)
@@ -148,10 +148,10 @@ void ChessWidget::mouseup_event(GUI::MouseEvent& event)
                     over = false;
                     over = false;
                 }
                 }
                 break;
                 break;
-            case Chess::Result::SeventyFiveMoveRule:
+            case Chess::Board::Result::SeventyFiveMoveRule:
                 msg = "Draw by 75 move rule.";
                 msg = "Draw by 75 move rule.";
                 break;
                 break;
-            case Chess::Result::ThreeFoldRepitition:
+            case Chess::Board::Result::ThreeFoldRepitition:
                 update();
                 update();
                 if (GUI::MessageBox::show(window(), "The same board state has repeated three times. Claim Draw?", "Claim Draw?",
                 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)
                         GUI::MessageBox::Type::Information, GUI::MessageBox::InputType::YesNo)
@@ -161,10 +161,10 @@ void ChessWidget::mouseup_event(GUI::MouseEvent& event)
                     over = false;
                     over = false;
                 }
                 }
                 break;
                 break;
-            case Chess::Result::FiveFoldRepitition:
+            case Chess::Board::Result::FiveFoldRepitition:
                 msg = "Draw by fivefold repitition.";
                 msg = "Draw by fivefold repitition.";
                 break;
                 break;
-            case Chess::Result::InsufficientMaterial:
+            case Chess::Board::Result::InsufficientMaterial:
                 msg = "Draw by insufficient material.";
                 msg = "Draw by insufficient material.";
                 break;
                 break;
             default:
             default:
@@ -239,7 +239,7 @@ RefPtr<Gfx::Bitmap> ChessWidget::get_piece_graphic(const Chess::Piece& piece) co
 
 
 void ChessWidget::reset()
 void ChessWidget::reset()
 {
 {
-    m_board = Chess();
+    m_board = Chess::Board();
     m_drag_enabled = true;
     m_drag_enabled = true;
     update();
     update();
 }
 }

+ 3 - 3
Games/Chess/ChessWidget.h

@@ -26,11 +26,11 @@
 
 
 #pragma once
 #pragma once
 
 
-#include "Chess.h"
 #include <AK/HashMap.h>
 #include <AK/HashMap.h>
 #include <AK/NonnullRefPtr.h>
 #include <AK/NonnullRefPtr.h>
 #include <AK/Optional.h>
 #include <AK/Optional.h>
 #include <AK/StringView.h>
 #include <AK/StringView.h>
+#include <LibChess/Chess.h>
 #include <LibGUI/Widget.h>
 #include <LibGUI/Widget.h>
 #include <LibGfx/Bitmap.h>
 #include <LibGfx/Bitmap.h>
 
 
@@ -47,7 +47,7 @@ public:
     virtual void mouseup_event(GUI::MouseEvent&) override;
     virtual void mouseup_event(GUI::MouseEvent&) override;
     virtual void mousemove_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; };
     Chess::Colour side() const { return m_side; };
     void set_side(Chess::Colour side) { m_side = side; };
     void set_side(Chess::Colour side) { m_side = side; };
@@ -74,7 +74,7 @@ public:
     void set_board_theme(const StringView& name);
     void set_board_theme(const StringView& name);
 
 
 private:
 private:
-    Chess m_board;
+    Chess::Board m_board;
     BoardTheme m_board_theme { "Beige", Color::from_rgb(0xb58863), Color::from_rgb(0xf0d9b5) };
     BoardTheme m_board_theme { "Beige", Color::from_rgb(0xb58863), Color::from_rgb(0xf0d9b5) };
     Color m_move_highlight_color { Color::from_rgba(0x66ccee00) };
     Color m_move_highlight_color { Color::from_rgba(0x66ccee00) };
     Chess::Colour m_side { Chess::Colour::White };
     Chess::Colour m_side { Chess::Colour::White };

+ 1 - 0
Libraries/CMakeLists.txt

@@ -1,5 +1,6 @@
 add_subdirectory(LibAudio)
 add_subdirectory(LibAudio)
 add_subdirectory(LibC)
 add_subdirectory(LibC)
+add_subdirectory(LibChess)
 add_subdirectory(LibCore)
 add_subdirectory(LibCore)
 add_subdirectory(LibCrypt)
 add_subdirectory(LibCrypt)
 add_subdirectory(LibCrypto)
 add_subdirectory(LibCrypto)

+ 6 - 0
Libraries/LibChess/CMakeLists.txt

@@ -0,0 +1,6 @@
+set(SOURCES
+    Chess.cpp
+)
+
+serenity_lib(LibChess chess)
+target_link_libraries(LibChess LibC)

+ 28 - 19
Games/Chess/Chess.cpp → Libraries/LibChess/Chess.cpp

@@ -24,15 +24,17 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
  */
 
 
-#include "Chess.h"
 #include <AK/Assertions.h>
 #include <AK/Assertions.h>
 #include <AK/LogStream.h>
 #include <AK/LogStream.h>
 #include <AK/String.h>
 #include <AK/String.h>
 #include <AK/StringBuilder.h>
 #include <AK/StringBuilder.h>
 #include <AK/Vector.h>
 #include <AK/Vector.h>
+#include <LibChess/Chess.h>
 #include <stdlib.h>
 #include <stdlib.h>
 
 
-String Chess::char_for_piece(Chess::Type type)
+namespace Chess {
+
+String char_for_piece(Chess::Type type)
 {
 {
     switch (type) {
     switch (type) {
     case Type::Knight:
     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);
     ASSERT(name.length() == 2);
     char filec = name[0];
     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;
     StringBuilder builder;
     builder.append(file - 'a');
     builder.append(file - 'a');
@@ -80,7 +87,7 @@ String Chess::Square::to_algebraic() const
     return builder.build();
     return builder.build();
 }
 }
 
 
-String Chess::Move::to_long_algebraic() const
+String Move::to_long_algebraic() const
 {
 {
     StringBuilder builder;
     StringBuilder builder;
     builder.append(from.to_algebraic());
     builder.append(from.to_algebraic());
@@ -89,7 +96,7 @@ String Chess::Move::to_long_algebraic() const
     return builder.build();
     return builder.build();
 }
 }
 
 
-Chess::Chess()
+Board::Board()
 {
 {
     // Fill empty spaces.
     // Fill empty spaces.
     for (unsigned rank = 2; rank < 6; ++rank) {
     for (unsigned rank = 2; rank < 6; ++rank) {
@@ -129,21 +136,21 @@ Chess::Chess()
     set_piece(Square("h8"), { Colour::Black, Type::Rook });
     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.rank < 8);
     ASSERT(square.file < 8);
     ASSERT(square.file < 8);
     return m_board[square.rank][square.file];
     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.rank < 8);
     ASSERT(square.file < 8);
     ASSERT(square.file < 8);
     return m_board[square.rank][square.file] = piece;
     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)
     if (colour == Colour::None)
         colour = turn();
         colour = turn();
@@ -151,7 +158,7 @@ bool Chess::is_legal(const Move& move, Colour colour) const
     if (!is_legal_no_check(move, colour))
     if (!is_legal_no_check(move, colour))
         return false;
         return false;
 
 
-    Chess clone = *this;
+    Board clone = *this;
     clone.apply_illegal_move(move, colour);
     clone.apply_illegal_move(move, colour);
     if (clone.in_check(colour))
     if (clone.in_check(colour))
         return false;
         return false;
@@ -172,7 +179,7 @@ bool Chess::is_legal(const Move& move, Colour colour) const
         }
         }
     }
     }
     for (auto& square : check_squares) {
     for (auto& square : check_squares) {
-        Chess clone = *this;
+        Board clone = *this;
         clone.set_piece(move.from, EmptyPiece);
         clone.set_piece(move.from, EmptyPiece);
         clone.set_piece(square, { colour, Type::King });
         clone.set_piece(square, { colour, Type::King });
         if (clone.in_check(colour))
         if (clone.in_check(colour))
@@ -182,7 +189,7 @@ bool Chess::is_legal(const Move& move, Colour colour) const
     return true;
     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);
     auto piece = get_piece(move.from);
     if (piece.colour != colour)
     if (piece.colour != colour)
@@ -314,7 +321,7 @@ bool Chess::is_legal_no_check(const Move& move, Colour colour) const
     return false;
     return false;
 }
 }
 
 
-bool Chess::in_check(Colour colour) const
+bool Board::in_check(Colour colour) const
 {
 {
     Square king_square = { 50, 50 };
     Square king_square = { 50, 50 };
     Square::for_each([&](const Square& square) {
     Square::for_each([&](const Square& square) {
@@ -341,7 +348,7 @@ bool Chess::in_check(Colour colour) const
     return check;
     return check;
 }
 }
 
 
-bool Chess::apply_move(const Move& move, Colour colour)
+bool Board::apply_move(const Move& move, Colour colour)
 {
 {
     if (colour == Colour::None)
     if (colour == Colour::None)
         colour = turn();
         colour = turn();
@@ -352,9 +359,9 @@ bool Chess::apply_move(const Move& move, Colour colour)
     return apply_illegal_move(move, 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 = {};
     clone.m_previous_states = {};
     auto state_count = 0;
     auto state_count = 0;
     if (m_previous_states.contains(clone))
     if (m_previous_states.contains(clone))
@@ -431,7 +438,7 @@ bool Chess::apply_illegal_move(const Move& move, Colour colour)
     return true;
     return true;
 }
 }
 
 
-Chess::Result Chess::game_result() const
+Board::Result Board::game_result() const
 {
 {
     bool sufficient_material = false;
     bool sufficient_material = false;
     bool no_more_pieces_allowed = false;
     bool no_more_pieces_allowed = false;
@@ -501,7 +508,7 @@ Chess::Result Chess::game_result() const
     return Result::StaleMate;
     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)
     if (colour == Colour::None)
         colour = turn();
         colour = turn();
@@ -517,7 +524,7 @@ bool Chess::is_promotion_move(const Move& move, Colour colour) const
     return false;
     return false;
 }
 }
 
 
-bool Chess::operator==(const Chess& other) const
+bool Board::operator==(const Board& other) const
 {
 {
     bool equal_squares = true;
     bool equal_squares = true;
     Square::for_each([&](Square sq) {
     Square::for_each([&](Square sq) {
@@ -541,3 +548,5 @@ bool Chess::operator==(const Chess& other) const
 
 
     return turn() == other.turn();
     return turn() == other.turn();
 }
 }
+
+}

+ 119 - 117
Games/Chess/Chess.h → Libraries/LibChess/Chess.h

@@ -32,80 +32,82 @@
 #include <AK/StringView.h>
 #include <AK/StringView.h>
 #include <AK/Traits.h>
 #include <AK/Traits.h>
 
 
-class Chess {
-public:
-    enum class Type {
-        Pawn,
-        Knight,
-        Bishop,
-        Rook,
-        Queen,
-        King,
-        None,
-    };
-    static String char_for_piece(Type type);
+namespace Chess {
+
+enum class Type {
+    Pawn,
+    Knight,
+    Bishop,
+    Rook,
+    Queen,
+    King,
+    None,
+};
 
 
-    enum class Colour {
-        White,
-        Black,
-        None,
-    };
-    static Colour opposing_colour(Colour colour) { return (colour == Colour::White) ? Colour::Black : Colour::White; }
+String char_for_piece(Type type);
 
 
-    struct Piece {
-        Colour colour;
-        Type type;
-        bool operator==(const Piece& other) const { return colour == other.colour && type == other.type; }
+enum class Colour {
+    White,
+    Black,
+    None,
+};
 
 
-    };
-    static constexpr Piece EmptyPiece = { Colour::None, Type::None };
-
-    struct Square {
-        unsigned rank; // zero indexed;
-        unsigned file;
-        Square(const StringView& name);
-        Square(const unsigned& rank, const unsigned& file)
-            : rank(rank)
-            , file(file)
-        {
-        }
-        bool operator==(const Square& other) const { return rank == other.rank && file == other.file; }
-
-        template<typename Callback>
-        static void for_each(Callback callback)
-        {
-            for (int rank = 0; rank < 8; ++rank) {
-                for (int file = 0; file < 8; ++file) {
-                    if (callback(Square(rank, file)) == IterationDecision::Break) {
-                        goto exit;
-                    }
-                }
+Colour opposing_colour(Colour colour);
+
+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<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;
             }
             }
-        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 get_piece(const Square&) const;
     Piece set_piece(const Square&, const Piece&);
     Piece set_piece(const Square&, const Piece&);
@@ -135,7 +137,7 @@ public:
 
 
     Colour turn() const { return m_turn; };
     Colour turn() const { return m_turn; };
 
 
-    bool operator==(const Chess& other) const;
+    bool operator==(const Board& other) const;
 
 
 private:
 private:
     bool is_legal_no_check(const Move&, Colour colour) const;
     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_kingside { true };
     bool m_black_can_castle_queenside { true };
     bool m_black_can_castle_queenside { true };
 
 
-    HashMap<Chess, int> m_previous_states;
-    friend struct AK::Traits<Chess>;
-};
-
-template<>
-struct AK::Traits<Chess::Piece> : public GenericTraits<Chess::Piece> {
-    static unsigned hash(Chess::Piece piece)
-    {
-        return pair_int_hash(static_cast<u32>(piece.colour), static_cast<u32>(piece.type));
-    }
-};
-
-template<>
-struct AK::Traits<Chess> : public GenericTraits<Chess> {
-    static unsigned hash(Chess chess)
-    {
-        unsigned hash = 0;
-        hash = pair_int_hash(hash, static_cast<u32>(chess.m_white_can_castle_queenside));
-        hash = pair_int_hash(hash, static_cast<u32>(chess.m_white_can_castle_kingside));
-        hash = pair_int_hash(hash, static_cast<u32>(chess.m_black_can_castle_queenside));
-        hash = pair_int_hash(hash, static_cast<u32>(chess.m_black_can_castle_kingside));
-
-        hash = pair_int_hash(hash, static_cast<u32>(chess.m_black_can_castle_kingside));
-
-        Chess::Square::for_each([&](Chess::Square sq) {
-            hash = pair_int_hash(hash, Traits<Chess::Piece>::hash(chess.get_piece(sq)));
-            return IterationDecision::Continue;
-        });
-
-        return hash;
-    }
+    HashMap<Board, int> m_previous_states;
+    friend struct AK::Traits<Board>;
 };
 };
 
 
 template<typename Callback>
 template<typename Callback>
-void Chess::generate_moves(Callback callback, Colour colour) const
+void Board::generate_moves(Callback callback, Colour colour) const
 {
 {
     if (colour == Colour::None)
     if (colour == Colour::None)
         colour = turn();
         colour = turn();
@@ -205,27 +178,25 @@ void Chess::generate_moves(Callback callback, Colour colour) const
 
 
         bool keep_going = true;
         bool keep_going = true;
         if (piece.type == Type::Pawn) {
         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) {
         } 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) {
         } else if (piece.type == Type::Bishop) {
             for (int dr = -1; dr <= 1; dr += 2) {
             for (int dr = -1; dr <= 1; dr += 2) {
                 for (int df = -1; df <= 1; df += 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<Chess::Piece> : public GenericTraits<Chess::Piece> {
+    static unsigned hash(Chess::Piece piece)
+    {
+        return pair_int_hash(static_cast<u32>(piece.colour), static_cast<u32>(piece.type));
+    }
+};
+
+template<>
+struct AK::Traits<Chess::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;
+    }
+};