Chess: Add win/draw conditions, and display them.
This commit is contained in:
parent
e05372cee2
commit
f2c1782d86
Notes:
sideshowbarker
2024-07-19 03:35:47 +09:00
Author: https://github.com/petelliott Commit: https://github.com/SerenityOS/serenity/commit/f2c1782d86b Pull-request: https://github.com/SerenityOS/serenity/pull/3099 Reviewed-by: https://github.com/stelar7
4 changed files with 159 additions and 3 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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 };
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue