Browse Source

Chess: Add new ways to draw.

new ways:
Insufficent material
Threefold/Fivefold repitition
50 move/75 move rule
Peter Elliott 4 years ago
parent
commit
d90f8abe9d
3 changed files with 154 additions and 7 deletions
  1. 90 1
      Games/Chess/Chess.cpp
  2. 32 0
      Games/Chess/Chess.h
  3. 32 6
      Games/Chess/ChessWidget.cpp

+ 90 - 1
Games/Chess/Chess.cpp

@@ -305,9 +305,17 @@ bool Chess::apply_move(const Move& move, Colour colour)
 
 
 bool Chess::apply_illegal_move(const Move& move, Colour colour)
 bool Chess::apply_illegal_move(const Move& move, Colour colour)
 {
 {
+    Chess clone = *this;
+    clone.m_previous_states = {};
+    auto state_count = 0;
+    if (m_previous_states.contains(clone))
+        state_count = m_previous_states.get(clone).value();
+    m_previous_states.set(clone, state_count + 1);
+
     m_turn = opposing_colour(colour);
     m_turn = opposing_colour(colour);
 
 
     m_last_move = move;
     m_last_move = move;
+    m_moves_since_capture++;
 
 
     if (move.from == Square("a1") || move.to == Square("a1") || move.from == Square("e1"))
     if (move.from == Square("a1") || move.to == Square("a1") || move.from == Square("e1"))
         m_white_can_castle_queenside = false;
         m_white_can_castle_queenside = false;
@@ -362,8 +370,12 @@ bool Chess::apply_illegal_move(const Move& move, Colour colour)
         } else {
         } else {
             set_piece({ move.to.rank + 1, move.to.file }, EmptyPiece);
             set_piece({ move.to.rank + 1, move.to.file }, EmptyPiece);
         }
         }
+        m_moves_since_capture = 0;
     }
     }
 
 
+    if (get_piece(move.to).colour != Colour::None)
+        m_moves_since_capture = 0;
+
     set_piece(move.to, get_piece(move.from));
     set_piece(move.to, get_piece(move.from));
     set_piece(move.from, EmptyPiece);
     set_piece(move.from, EmptyPiece);
 
 
@@ -372,6 +384,44 @@ bool Chess::apply_illegal_move(const Move& move, Colour colour)
 
 
 Chess::Result Chess::game_result() const
 Chess::Result Chess::game_result() const
 {
 {
+    bool sufficient_material = false;
+    bool no_more_pieces_allowed = false;
+    Optional<Square> bishop;
+    Square::for_each([&](Square sq) {
+        if (get_piece(sq).type == Type::Queen || get_piece(sq).type == Type::Rook || get_piece(sq).type == Type::Pawn) {
+            sufficient_material = true;
+            return IterationDecision::Break;
+        }
+
+        if (get_piece(sq).type != Type::None && get_piece(sq).type != Type::King && no_more_pieces_allowed) {
+            sufficient_material = true;
+            return IterationDecision::Break;
+        }
+
+        if (get_piece(sq).type == Type::Knight)
+            no_more_pieces_allowed = true;
+
+        if (get_piece(sq).type == Type::Bishop) {
+            if (bishop.has_value()) {
+                if (get_piece(sq).colour == get_piece(bishop.value()).colour) {
+                    sufficient_material = true;
+                    return IterationDecision::Break;
+                } else if (sq.is_light() != bishop.value().is_light()) {
+                    sufficient_material = true;
+                    return IterationDecision::Break;
+                }
+                no_more_pieces_allowed = true;
+            } else {
+                bishop = sq;
+            }
+        }
+
+        return IterationDecision::Continue;
+    });
+
+    if (!sufficient_material)
+        return Result::InsufficientMaterial;
+
     bool are_legal_moves = false;
     bool are_legal_moves = false;
     generate_moves([&](Move m) {
     generate_moves([&](Move m) {
         (void)m;
         (void)m;
@@ -379,8 +429,22 @@ Chess::Result Chess::game_result() const
         return IterationDecision::Break;
         return IterationDecision::Break;
     });
     });
 
 
-    if (are_legal_moves)
+    if (are_legal_moves) {
+        if (m_moves_since_capture >= 75 * 2)
+            return Result::SeventyFiveMoveRule;
+        if (m_moves_since_capture == 50 * 2)
+            return Result::FiftyMoveRule;
+
+        auto repeats = m_previous_states.get(*this);
+        if (repeats.has_value()) {
+            if (repeats.value() == 3)
+                return Result::ThreeFoldRepitition;
+            if (repeats.value() >= 5)
+                return Result::FiveFoldRepitition;
+        }
+
         return Result::NotFinished;
         return Result::NotFinished;
+    }
 
 
     if (in_check(turn()))
     if (in_check(turn()))
         return Result::CheckMate;
         return Result::CheckMate;
@@ -401,3 +465,28 @@ bool Chess::is_promotion_move(const Move& move, Colour colour) const
 
 
     return false;
     return false;
 }
 }
+
+bool Chess::operator==(const Chess& other) const
+{
+    bool equal_squares = true;
+    Square::for_each([&](Square sq) {
+        if (get_piece(sq) != other.get_piece(sq)) {
+            equal_squares = false;
+            return IterationDecision::Break;
+        }
+        return IterationDecision::Continue;
+    });
+    if (!equal_squares)
+        return false;
+
+    if (m_white_can_castle_queenside != other.m_white_can_castle_queenside)
+        return false;
+    if (m_white_can_castle_kingside != other.m_white_can_castle_kingside)
+        return false;
+    if (m_black_can_castle_queenside != other.m_black_can_castle_queenside)
+        return false;
+    if (m_black_can_castle_kingside != other.m_black_can_castle_kingside)
+        return false;
+
+    return turn() == other.turn();
+}

+ 32 - 0
Games/Chess/Chess.h

@@ -26,6 +26,7 @@
 
 
 #pragma once
 #pragma once
 
 
+#include <AK/HashMap.h>
 #include <AK/IterationDecision.h>
 #include <AK/IterationDecision.h>
 #include <AK/Optional.h>
 #include <AK/Optional.h>
 #include <AK/StringView.h>
 #include <AK/StringView.h>
@@ -82,6 +83,7 @@ public:
         }
         }
 
 
         bool in_bounds() const { return rank < 8 && file < 8; }
         bool in_bounds() const { return rank < 8 && file < 8; }
+        bool is_light() const { return (rank % 2) != (file % 2); }
     };
     };
 
 
     struct Move {
     struct Move {
@@ -115,7 +117,10 @@ public:
         CheckMate,
         CheckMate,
         StaleMate,
         StaleMate,
         FiftyMoveRule,
         FiftyMoveRule,
+        SeventyFiveMoveRule,
         ThreeFoldRepitition,
         ThreeFoldRepitition,
+        FiveFoldRepitition,
+        InsufficientMaterial,
         NotFinished,
         NotFinished,
     };
     };
 
 
@@ -125,6 +130,8 @@ public:
 
 
     Colour turn() const { return m_turn; };
     Colour turn() const { return m_turn; };
 
 
+    bool operator==(const Chess& other) const;
+
 private:
 private:
     bool is_legal_no_check(const Move&, Colour colour) const;
     bool is_legal_no_check(const Move&, Colour colour) const;
     bool apply_illegal_move(const Move&, Colour colour);
     bool apply_illegal_move(const Move&, Colour colour);
@@ -132,11 +139,15 @@ private:
     Piece m_board[8][8];
     Piece m_board[8][8];
     Colour m_turn { Colour::White };
     Colour m_turn { Colour::White };
     Optional<Move> m_last_move;
     Optional<Move> m_last_move;
+    int m_moves_since_capture { 0 };
 
 
     bool m_white_can_castle_kingside { true };
     bool m_white_can_castle_kingside { true };
     bool m_white_can_castle_queenside { true };
     bool m_white_can_castle_queenside { true };
     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<>
 template<>
@@ -147,6 +158,27 @@ struct AK::Traits<Chess::Piece> : public GenericTraits<Chess::Piece> {
     }
     }
 };
 };
 
 
+template<>
+struct AK::Traits<Chess> : public GenericTraits<Chess> {
+    static unsigned hash(Chess chess)
+    {
+        unsigned hash = 0;
+        hash = pair_int_hash(hash, static_cast<u32>(chess.m_white_can_castle_queenside));
+        hash = pair_int_hash(hash, static_cast<u32>(chess.m_white_can_castle_kingside));
+        hash = pair_int_hash(hash, static_cast<u32>(chess.m_black_can_castle_queenside));
+        hash = pair_int_hash(hash, static_cast<u32>(chess.m_black_can_castle_kingside));
+
+        hash = pair_int_hash(hash, static_cast<u32>(chess.m_black_can_castle_kingside));
+
+        Chess::Square::for_each([&](Chess::Square sq) {
+            hash = pair_int_hash(hash, Traits<Chess::Piece>::hash(chess.get_piece(sq)));
+            return IterationDecision::Continue;
+        });
+
+        return hash;
+    }
+};
+
 template<typename Callback>
 template<typename Callback>
 void Chess::generate_moves(Callback callback, Colour colour) const
 void Chess::generate_moves(Callback callback, Colour colour) const
 {
 {

+ 32 - 6
Games/Chess/ChessWidget.cpp

@@ -62,7 +62,7 @@ void ChessWidget::paint_event(GUI::PaintEvent& event)
             tile_rect = { (7 - sq.file) * tile_width, sq.rank * tile_height, tile_width, tile_height };
             tile_rect = { (7 - sq.file) * tile_width, sq.rank * tile_height, tile_width, tile_height };
         }
         }
 
 
-        painter.fill_rect(tile_rect, ((sq.rank % 2) == (sq.file % 2)) ? board_theme().dark_square_color : board_theme().light_square_color);
+        painter.fill_rect(tile_rect, (sq.is_light()) ? board_theme().light_square_color : board_theme().dark_square_color);
 
 
         if (board().last_move().has_value() && (board().last_move().value().to == sq || board().last_move().value().from == sq)) {
         if (board().last_move().has_value() && (board().last_move().value().to == sq || board().last_move().value().from == sq)) {
             painter.fill_rect(tile_rect, m_move_highlight_color);
             painter.fill_rect(tile_rect, m_move_highlight_color);
@@ -124,9 +124,8 @@ 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::Result::NotFinished) {
-            set_drag_enabled(false);
-            update();
 
 
+            bool over = true;
             String msg;
             String msg;
             switch (board().game_result()) {
             switch (board().game_result()) {
             case Chess::Result::CheckMate:
             case Chess::Result::CheckMate:
@@ -140,15 +139,42 @@ void ChessWidget::mouseup_event(GUI::MouseEvent& event)
                 msg = "Draw by Stalemate.";
                 msg = "Draw by Stalemate.";
                 break;
                 break;
             case Chess::Result::FiftyMoveRule:
             case Chess::Result::FiftyMoveRule:
-                msg = "Draw by 50 move rule.";
+                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)
+                    == GUI::Dialog::ExecYes) {
+                    msg = "Draw by 50 move rule.";
+                } else {
+                    over = false;
+                }
+                break;
+            case Chess::Result::SeventyFiveMoveRule:
+                msg = "Draw by 75 move rule.";
                 break;
                 break;
             case Chess::Result::ThreeFoldRepitition:
             case Chess::Result::ThreeFoldRepitition:
-                msg = "Draw by threefold repitition.";
+                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)
+                    == GUI::Dialog::ExecYes) {
+                    msg = "Draw by threefold repitition.";
+                } else {
+                    over = false;
+                }
+                break;
+            case Chess::Result::FiveFoldRepitition:
+                msg = "Draw by fivefold repitition.";
+                break;
+            case Chess::Result::InsufficientMaterial:
+                msg = "Draw by insufficient material.";
                 break;
                 break;
             default:
             default:
                 ASSERT_NOT_REACHED();
                 ASSERT_NOT_REACHED();
             }
             }
-            GUI::MessageBox::show(window(), msg, "Game Over", GUI::MessageBox::Type::Information);
+            if (over) {
+                set_drag_enabled(false);
+                update();
+                GUI::MessageBox::show(window(), msg, "Game Over", GUI::MessageBox::Type::Information);
+            }
         }
         }
     }
     }