diff --git a/Games/Chess/CMakeLists.txt b/Games/Chess/CMakeLists.txt index f5a8746b60..110a5decd3 100644 --- a/Games/Chess/CMakeLists.txt +++ b/Games/Chess/CMakeLists.txt @@ -2,6 +2,7 @@ set(SOURCES main.cpp Chess.cpp ChessWidget.cpp + PromotionDialog.cpp ) serenity_bin(Chess) diff --git a/Games/Chess/Chess.cpp b/Games/Chess/Chess.cpp index 66d77849d5..88ca8162ec 100644 --- a/Games/Chess/Chess.cpp +++ b/Games/Chess/Chess.cpp @@ -153,6 +153,9 @@ bool Chess::is_legal_no_check(const Move& move, Colour colour) const if (move.to.rank > 7 || move.to.file > 7) return false; + if (move.promote_to == Type::Pawn || move.promote_to == Type::King || move.promote_to == Type::None) + return false; + if (piece.type == Type::Pawn) { int dir = (colour == Colour::White) ? +1 : -1; unsigned start_rank = (colour == Colour::White) ? 1 : 6; @@ -304,8 +307,6 @@ bool Chess::apply_illegal_move(const Move& move, Colour colour) { m_turn = opposing_colour(colour); - // FIXME: pawn promotion - m_last_move = move; if (move.from == Square("a1") || move.to == Square("a1") || move.from == Square("e1")) @@ -347,6 +348,13 @@ bool Chess::apply_illegal_move(const Move& move, Colour colour) } } + if (get_piece(move.from).type == Type::Pawn && ((colour == Colour::Black && move.to.rank == 0) || (colour == Colour::White && move.to.rank == 7))) { + // Pawn Promotion + set_piece(move.to, { colour, move.promote_to }); + set_piece(move.from, EmptyPiece); + return true; + } + if (get_piece(move.from).type == Type::Pawn && move.from.file != move.to.file && get_piece(move.to).type == Type::None) { // En passant. if (colour == Colour::White) { @@ -379,3 +387,17 @@ Chess::Result Chess::game_result() const return Result::StaleMate; } + +bool Chess::is_promotion_move(const Move& move, Colour colour) const +{ + if (colour == Colour::None) + colour = turn(); + + if (!is_legal(move, colour)) + return false; + + if (get_piece(move.from).type == Type::Pawn && ((colour == Colour::Black && move.to.rank == 0) || (colour == Colour::White && move.to.rank == 7))) + return true; + + return false; +} diff --git a/Games/Chess/Chess.h b/Games/Chess/Chess.h index 01f0e77a1e..078262cb84 100644 --- a/Games/Chess/Chess.h +++ b/Games/Chess/Chess.h @@ -87,13 +87,15 @@ public: struct Move { Square from; Square to; + Type promote_to; Move(const StringView& algebraic); - Move(const Square& from, const Square& to) + Move(const Square& from, const Square& to, const Type& promote_to = Type::Queen) : from(from) , to(to) + , promote_to(promote_to) { } - bool operator==(const Move& other) const { return from == other.from && to == other.to; } + bool operator==(const Move& other) const { return from == other.from && to == other.to && promote_to == other.promote_to; } }; Chess(); @@ -104,6 +106,8 @@ public: bool is_legal(const Move&, Colour colour = Colour::None) const; bool in_check(Colour colour) const; + bool is_promotion_move(const Move&, Colour colour = Colour::None) const; + bool apply_move(const Move&, Colour colour = Colour::None); const Optional& last_move() const { return m_last_move; } diff --git a/Games/Chess/ChessWidget.cpp b/Games/Chess/ChessWidget.cpp index 0801f07985..539d8979fa 100644 --- a/Games/Chess/ChessWidget.cpp +++ b/Games/Chess/ChessWidget.cpp @@ -25,6 +25,7 @@ */ #include "ChessWidget.h" +#include "PromotionDialog.h" #include #include #include @@ -114,7 +115,14 @@ void ChessWidget::mouseup_event(GUI::MouseEvent& event) auto target_square = mouse_to_square(event); - if (board().apply_move({ m_moving_square, target_square })) { + Chess::Move move = { m_moving_square, target_square }; + if (board().is_promotion_move(move)) { + auto promotion_dialog = PromotionDialog::construct(*this); + if (promotion_dialog->exec() == PromotionDialog::ExecOK) + move.promote_to = promotion_dialog->selected_piece(); + } + + if (board().apply_move(move)) { if (board().game_result() != Chess::Result::NotFinished) { set_drag_enabled(false); update(); @@ -198,6 +206,11 @@ Chess::Square ChessWidget::mouse_to_square(GUI::MouseEvent& event) const } } +RefPtr ChessWidget::get_piece_graphic(const Chess::Piece& piece) const +{ + return m_pieces.get(piece).value(); +} + void ChessWidget::reset() { m_board = Chess(); diff --git a/Games/Chess/ChessWidget.h b/Games/Chess/ChessWidget.h index 5762ea2e58..f8e0b3d624 100644 --- a/Games/Chess/ChessWidget.h +++ b/Games/Chess/ChessWidget.h @@ -59,6 +59,7 @@ public: bool drag_enabled() const { return m_drag_enabled; } void set_drag_enabled(bool e) { m_drag_enabled = e; } + RefPtr get_piece_graphic(const Chess::Piece& piece) const; void reset(); diff --git a/Games/Chess/PromotionDialog.cpp b/Games/Chess/PromotionDialog.cpp new file mode 100644 index 0000000000..b9416f6ed2 --- /dev/null +++ b/Games/Chess/PromotionDialog.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "PromotionDialog.h" +#include +#include +#include + +PromotionDialog::PromotionDialog(ChessWidget& chess_widget) + : Dialog(chess_widget.window()) +{ + set_title("Choose piece to promote to"); + resize(70 * 4, 70); + + auto& main_widget = set_main_widget(); + main_widget.set_frame_shape(Gfx::FrameShape::Container); + main_widget.set_fill_with_background_color(true); + main_widget.set_layout(); + + for (auto& type : Vector({ Chess::Type::Queen, Chess::Type::Knight, Chess::Type::Rook, Chess::Type::Bishop })) { + auto& button = main_widget.add(""); + button.set_icon(chess_widget.get_piece_graphic({ chess_widget.board().turn(), type })); + button.on_click = [this, type](auto) { + m_selected_piece = type; + done(ExecOK); + }; + } +} + +void PromotionDialog::event(Core::Event& event) +{ + Dialog::event(event); +} diff --git a/Games/Chess/PromotionDialog.h b/Games/Chess/PromotionDialog.h new file mode 100644 index 0000000000..78c0f3ad52 --- /dev/null +++ b/Games/Chess/PromotionDialog.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "ChessWidget.h" +#include + +class PromotionDialog final : public GUI::Dialog { + C_OBJECT(PromotionDialog) +public: + Chess::Type selected_piece() const { return m_selected_piece; } + +private: + explicit PromotionDialog(ChessWidget& chess_widget); + virtual void event(Core::Event&) override; + + Chess::Type m_selected_piece; +};