summaryrefslogtreecommitdiff
path: root/Games
diff options
context:
space:
mode:
authorPeter Elliott <pelliott@ualberta.ca>2020-08-11 14:10:39 -0600
committerAndreas Kling <kling@serenityos.org>2020-08-15 20:54:02 +0200
commitf2c1782d86b831b03624ff7158b91a97b6a6fc4a (patch)
tree70ce2e699254db42ccd3fc842f3bcf7bea12e510 /Games
parente05372cee2ccbb63a9754799740439af103e09dc (diff)
downloadserenity-f2c1782d86b831b03624ff7158b91a97b6a6fc4a.zip
Chess: Add win/draw conditions, and display them.
Diffstat (limited to 'Games')
-rw-r--r--Games/Chess/Chess.cpp18
-rw-r--r--Games/Chess/Chess.h108
-rw-r--r--Games/Chess/ChessWidget.cpp32
-rw-r--r--Games/Chess/ChessWidget.h4
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 };
};