diff options
author | Peter Elliott <pelliott@ualberta.ca> | 2020-08-11 14:10:39 -0600 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2020-08-15 20:54:02 +0200 |
commit | f2c1782d86b831b03624ff7158b91a97b6a6fc4a (patch) | |
tree | 70ce2e699254db42ccd3fc842f3bcf7bea12e510 /Games | |
parent | e05372cee2ccbb63a9754799740439af103e09dc (diff) | |
download | serenity-f2c1782d86b831b03624ff7158b91a97b6a6fc4a.zip |
Chess: Add win/draw conditions, and display them.
Diffstat (limited to 'Games')
-rw-r--r-- | Games/Chess/Chess.cpp | 18 | ||||
-rw-r--r-- | Games/Chess/Chess.h | 108 | ||||
-rw-r--r-- | Games/Chess/ChessWidget.cpp | 32 | ||||
-rw-r--r-- | Games/Chess/ChessWidget.h | 4 |
4 files changed, 159 insertions, 3 deletions
diff --git a/Games/Chess/Chess.cpp b/Games/Chess/Chess.cpp index 1f04f1448e..ca81166fa3 100644 --- a/Games/Chess/Chess.cpp +++ b/Games/Chess/Chess.cpp @@ -352,3 +352,21 @@ bool Chess::apply_illegal_move(const Move& move, Colour colour) return true; } + +Chess::Result Chess::game_result() const +{ + bool are_legal_moves = false; + generate_moves([&](Move m) { + (void)m; + are_legal_moves = true; + return IterationDecision::Break; + }); + + if (are_legal_moves) + return Result::NotFinished; + + if (in_check(turn())) + return Result::CheckMate; + + return Result::StaleMate; +} diff --git a/Games/Chess/Chess.h b/Games/Chess/Chess.h index 3fbe0316df..fa023740ab 100644 --- a/Games/Chess/Chess.h +++ b/Games/Chess/Chess.h @@ -104,9 +104,20 @@ public: bool apply_move(const Move&, Colour colour = Colour::None); - Colour turn() const { return m_turn; }; + enum class Result { + CheckMate, + StaleMate, + FiftyMoveRule, + ThreeFoldRepitition, + NotFinished, + }; + + template<typename Callback> + void generate_moves(Callback callback, Colour colour = Colour::None) const; + Result game_result() const; Colour turn() const { return m_turn; }; + private: bool is_legal_no_check(const Move&, Colour colour) const; bool apply_illegal_move(const Move&, Colour colour); @@ -127,3 +138,98 @@ struct AK::Traits<Chess::Piece> : public GenericTraits<Chess::Piece> { return pair_int_hash(static_cast<u32>(piece.colour), static_cast<u32>(piece.type)); } }; + +template<typename Callback> +void Chess::generate_moves(Callback callback, Colour colour) const +{ + if (colour == Colour::None) + colour = turn(); + + auto try_move = [&](Move m) { + if (is_legal(m, colour)) { + if (callback(m) == IterationDecision::Break) + return false; + } + return true; + }; + + Square::for_each([&](Square sq) { + auto piece = get_piece(sq); + if (piece.colour != colour) + return IterationDecision::Continue; + + bool keep_going = true; + if (piece.type == Type::Pawn) { + keep_going = + try_move({sq, {sq.rank+1, sq.file}}) && + try_move({sq, {sq.rank+2, sq.file}}) && + try_move({sq, {sq.rank-1, sq.file}}) && + try_move({sq, {sq.rank-2, sq.file}}) && + 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}}); + } 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; + } + }); +} diff --git a/Games/Chess/ChessWidget.cpp b/Games/Chess/ChessWidget.cpp index aaf070170e..f1352851d3 100644 --- a/Games/Chess/ChessWidget.cpp +++ b/Games/Chess/ChessWidget.cpp @@ -26,6 +26,7 @@ #include "ChessWidget.h" #include <AK/String.h> +#include <LibGUI/MessageBox.h> #include <LibGUI/Painter.h> ChessWidget::ChessWidget(const StringView& set) @@ -92,7 +93,7 @@ void ChessWidget::mousedown_event(GUI::MouseEvent& event) GUI::Widget::mousedown_event(event); auto square = mouse_to_square(event); auto piece = board().get_piece(square); - if (piece.colour == board().turn()) { + if (drag_enabled() && piece.colour == board().turn()) { m_dragging_piece = true; m_drag_point = event.position(); m_moving_square = square; @@ -110,7 +111,34 @@ void ChessWidget::mouseup_event(GUI::MouseEvent& event) auto target_square = mouse_to_square(event); - board().apply_move({ m_moving_square, target_square }); + if (board().apply_move({ m_moving_square, target_square }) && board().game_result() != Chess::Result::NotFinished) { + set_drag_enabled(false); + update(); + + String msg; + switch (board().game_result()) { + case Chess::Result::CheckMate: + if (board().turn() == Chess::Colour::White) { + msg = "Black wins by Checkmate."; + } else { + msg = "White wins by Checkmate."; + } + break; + case Chess::Result::StaleMate: + msg = "Draw by Stalemate."; + break; + case Chess::Result::FiftyMoveRule: + msg = "Draw by 50 move rule."; + break; + case Chess::Result::ThreeFoldRepitition: + msg = "Draw by threefold repitition."; + break; + default: + ASSERT_NOT_REACHED(); + } + GUI::MessageBox::show(window(), msg, "Game Over"); + } + update(); } diff --git a/Games/Chess/ChessWidget.h b/Games/Chess/ChessWidget.h index 0cd06dd82c..f5ad69b017 100644 --- a/Games/Chess/ChessWidget.h +++ b/Games/Chess/ChessWidget.h @@ -56,6 +56,9 @@ public: Chess::Square mouse_to_square(GUI::MouseEvent& event) const; + bool drag_enabled() const { return m_drag_enabled; } + void set_drag_enabled(bool e) { m_drag_enabled = e; } + private: Chess m_board; Color m_dark_square_color { Color::from_rgb(0xb58863) }; @@ -66,4 +69,5 @@ private: Chess::Square m_moving_square { 50, 50 }; Gfx::IntPoint m_drag_point; bool m_dragging_piece { false }; + bool m_drag_enabled { true }; }; |