Chess: Port application to GML
This commit ports the chess application to GML in order to facilitate the addition of widgets in the future.
This commit is contained in:
parent
10dbadced8
commit
55fe04a6fa
Notes:
sideshowbarker
2024-07-17 11:33:34 +09:00
Author: https://github.com/d-gaston Commit: https://github.com/SerenityOS/serenity/commit/55fe04a6fa Pull-request: https://github.com/SerenityOS/serenity/pull/24329 Reviewed-by: https://github.com/AtkinsSJ Reviewed-by: https://github.com/LucasChollet ✅ Reviewed-by: https://github.com/tcl3
9 changed files with 141 additions and 41 deletions
|
@ -5,11 +5,16 @@ serenity_component(
|
|||
DEPENDS ChessEngine
|
||||
)
|
||||
|
||||
compile_gml(Chess.gml ChessGML.cpp chess_gml)
|
||||
compile_gml(PromotionWidget.gml PromotionWidgetGML.cpp promotionWidget_gml)
|
||||
|
||||
set(SOURCES
|
||||
main.cpp
|
||||
ChessWidget.cpp
|
||||
PromotionDialog.cpp
|
||||
Engine.cpp
|
||||
ChessGML.cpp
|
||||
PromotionWidgetGML.cpp
|
||||
)
|
||||
|
||||
serenity_app(Chess ICON app-chess)
|
||||
|
|
8
Userland/Games/Chess/Chess.gml
Normal file
8
Userland/Games/Chess/Chess.gml
Normal file
|
@ -0,0 +1,8 @@
|
|||
@Chess::MainWidget {
|
||||
fill_with_background_color: true
|
||||
layout: @GUI::VerticalBoxLayout {}
|
||||
|
||||
@Chess::ChessWidget {
|
||||
name: "chess_widget"
|
||||
}
|
||||
}
|
|
@ -289,7 +289,7 @@ void ChessWidget::mouseup_event(GUI::MouseEvent& event)
|
|||
|
||||
Chess::Move move = { m_moving_square, target_square.release_value() };
|
||||
if (board().is_promotion_move(move)) {
|
||||
auto promotion_dialog = PromotionDialog::construct(*this);
|
||||
auto promotion_dialog = MUST(PromotionDialog::try_create(*this));
|
||||
if (promotion_dialog->exec() == PromotionDialog::ExecResult::OK)
|
||||
move.promote_to = promotion_dialog->selected_piece();
|
||||
}
|
||||
|
|
23
Userland/Games/Chess/MainWidget.h
Normal file
23
Userland/Games/Chess/MainWidget.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright (c) 2024, the SerenityOS developers
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGUI/Widget.h>
|
||||
|
||||
namespace Chess {
|
||||
|
||||
class MainWidget : public GUI::Widget {
|
||||
C_OBJECT_ABSTRACT(MainWidget)
|
||||
public:
|
||||
static ErrorOr<NonnullRefPtr<MainWidget>> try_create();
|
||||
virtual ~MainWidget() override = default;
|
||||
|
||||
private:
|
||||
MainWidget() = default;
|
||||
};
|
||||
|
||||
}
|
|
@ -11,28 +11,34 @@
|
|||
|
||||
namespace Chess {
|
||||
|
||||
PromotionDialog::PromotionDialog(ChessWidget& chess_widget)
|
||||
ErrorOr<NonnullRefPtr<PromotionDialog>> PromotionDialog::try_create(ChessWidget& chess_widget)
|
||||
{
|
||||
auto promotion_widget = TRY(Chess::PromotionWidget::try_create());
|
||||
auto promotion_dialog = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) PromotionDialog(move(promotion_widget), chess_widget)));
|
||||
return promotion_dialog;
|
||||
}
|
||||
|
||||
PromotionDialog::PromotionDialog(NonnullRefPtr<Chess::PromotionWidget> promotion_widget, ChessWidget& chess_widget)
|
||||
: Dialog(chess_widget.window())
|
||||
, m_selected_piece(Chess::Type::None)
|
||||
{
|
||||
set_title("Choose piece to promote to");
|
||||
set_icon(chess_widget.window()->icon());
|
||||
resize(70 * 4, 70);
|
||||
set_main_widget(promotion_widget);
|
||||
|
||||
auto main_widget = set_main_widget<GUI::Frame>();
|
||||
main_widget->set_frame_style(Gfx::FrameStyle::SunkenContainer);
|
||||
main_widget->set_fill_with_background_color(true);
|
||||
main_widget->set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
for (auto const& type : { Chess::Type::Queen, Chess::Type::Knight, Chess::Type::Rook, Chess::Type::Bishop }) {
|
||||
auto& button = main_widget->add<GUI::Button>();
|
||||
button.set_fixed_height(70);
|
||||
button.set_icon(chess_widget.get_piece_graphic({ chess_widget.board().turn(), type }));
|
||||
button.on_click = [this, type](auto) {
|
||||
m_selected_piece = type;
|
||||
auto initialize_promotion_button = [&](StringView button_name, Chess::Type piece) {
|
||||
auto button = promotion_widget->find_descendant_of_type_named<GUI::Button>(button_name);
|
||||
button->set_icon(chess_widget.get_piece_graphic({ chess_widget.board().turn(), piece }));
|
||||
button->on_click = [this, piece](auto) {
|
||||
m_selected_piece = piece;
|
||||
done(ExecResult::OK);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
initialize_promotion_button("queen_button"sv, Type::Queen);
|
||||
initialize_promotion_button("knight_button"sv, Type::Knight);
|
||||
initialize_promotion_button("rook_button"sv, Type::Rook);
|
||||
initialize_promotion_button("bishop_button"sv, Type::Bishop);
|
||||
}
|
||||
|
||||
void PromotionDialog::event(Core::Event& event)
|
||||
|
|
|
@ -7,17 +7,19 @@
|
|||
#pragma once
|
||||
|
||||
#include "ChessWidget.h"
|
||||
#include "PromotionWidget.h"
|
||||
#include <LibGUI/Dialog.h>
|
||||
|
||||
namespace Chess {
|
||||
|
||||
class PromotionDialog final : public GUI::Dialog {
|
||||
C_OBJECT(PromotionDialog)
|
||||
C_OBJECT_ABSTRACT(PromotionDialog)
|
||||
public:
|
||||
static ErrorOr<NonnullRefPtr<PromotionDialog>> try_create(ChessWidget& chess_widget);
|
||||
Chess::Type selected_piece() const { return m_selected_piece; }
|
||||
|
||||
private:
|
||||
explicit PromotionDialog(ChessWidget& chess_widget);
|
||||
PromotionDialog(NonnullRefPtr<Chess::PromotionWidget> promotion_widget, ChessWidget& chess_widget);
|
||||
virtual void event(Core::Event&) override;
|
||||
|
||||
Chess::Type m_selected_piece;
|
||||
|
|
29
Userland/Games/Chess/PromotionWidget.gml
Normal file
29
Userland/Games/Chess/PromotionWidget.gml
Normal file
|
@ -0,0 +1,29 @@
|
|||
@Chess::PromotionWidget {
|
||||
fixed_height: 70
|
||||
fill_with_background_color: true
|
||||
layout: @GUI::HorizontalBoxLayout {}
|
||||
|
||||
@GUI::Button {
|
||||
fixed_width: 70
|
||||
fixed_height: 70
|
||||
name: "queen_button"
|
||||
}
|
||||
|
||||
@GUI::Button {
|
||||
fixed_width: 70
|
||||
fixed_height: 70
|
||||
name: "knight_button"
|
||||
}
|
||||
|
||||
@GUI::Button {
|
||||
fixed_width: 70
|
||||
fixed_height: 70
|
||||
name: "rook_button"
|
||||
}
|
||||
|
||||
@GUI::Button {
|
||||
fixed_width: 70
|
||||
fixed_height: 70
|
||||
name: "bishop_button"
|
||||
}
|
||||
}
|
23
Userland/Games/Chess/PromotionWidget.h
Normal file
23
Userland/Games/Chess/PromotionWidget.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright (c) 2024, the SerenityOS developers
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGUI/Widget.h>
|
||||
|
||||
namespace Chess {
|
||||
|
||||
class PromotionWidget : public GUI::Widget {
|
||||
C_OBJECT_ABSTRACT(PromotionWidget)
|
||||
public:
|
||||
static ErrorOr<NonnullRefPtr<PromotionWidget>> try_create();
|
||||
virtual ~PromotionWidget() override = default;
|
||||
|
||||
private:
|
||||
PromotionWidget() = default;
|
||||
};
|
||||
|
||||
}
|
|
@ -8,6 +8,7 @@
|
|||
*/
|
||||
|
||||
#include "ChessWidget.h"
|
||||
#include "MainWidget.h"
|
||||
#include <LibConfig/Client.h>
|
||||
#include <LibCore/System.h>
|
||||
#include <LibDesktop/Launcher.h>
|
||||
|
@ -64,8 +65,11 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|||
auto app_icon = TRY(GUI::Icon::try_create_default_icon("app-chess"sv));
|
||||
|
||||
auto window = GUI::Window::construct();
|
||||
auto widget = TRY(Chess::ChessWidget::try_create());
|
||||
window->set_main_widget(widget);
|
||||
auto main_widget = TRY(Chess::MainWidget::try_create());
|
||||
auto& chess_widget = *main_widget->find_descendant_of_type_named<Chess::ChessWidget>("chess_widget");
|
||||
|
||||
window->set_main_widget(main_widget);
|
||||
window->set_focused_widget(&chess_widget);
|
||||
|
||||
auto engines = TRY(available_engines());
|
||||
for (auto const& engine : engines)
|
||||
|
@ -85,19 +89,19 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|||
|
||||
window->set_icon(app_icon.bitmap_for_size(16));
|
||||
|
||||
widget->set_piece_set(Config::read_string("Games"sv, "Chess"sv, "PieceSet"sv, "Classic"sv));
|
||||
widget->set_board_theme(Config::read_string("Games"sv, "Chess"sv, "BoardTheme"sv, "Beige"sv));
|
||||
widget->set_coordinates(Config::read_bool("Games"sv, "Chess"sv, "ShowCoordinates"sv, true));
|
||||
widget->set_show_available_moves(Config::read_bool("Games"sv, "Chess"sv, "ShowAvailableMoves"sv, true));
|
||||
widget->set_highlight_checks(Config::read_bool("Games"sv, "Chess"sv, "HighlightChecks"sv, true));
|
||||
chess_widget.set_piece_set(Config::read_string("Games"sv, "Chess"sv, "PieceSet"sv, "Classic"sv));
|
||||
chess_widget.set_board_theme(Config::read_string("Games"sv, "Chess"sv, "BoardTheme"sv, "Beige"sv));
|
||||
chess_widget.set_coordinates(Config::read_bool("Games"sv, "Chess"sv, "ShowCoordinates"sv, true));
|
||||
chess_widget.set_show_available_moves(Config::read_bool("Games"sv, "Chess"sv, "ShowAvailableMoves"sv, true));
|
||||
chess_widget.set_highlight_checks(Config::read_bool("Games"sv, "Chess"sv, "HighlightChecks"sv, true));
|
||||
|
||||
auto game_menu = window->add_menu("&Game"_string);
|
||||
|
||||
game_menu->add_action(GUI::Action::create("&Resign", { Mod_None, Key_F3 }, [&](auto&) {
|
||||
widget->resign();
|
||||
chess_widget.resign();
|
||||
}));
|
||||
game_menu->add_action(GUI::Action::create("&Flip Board", { Mod_Ctrl, Key_F }, [&](auto&) {
|
||||
widget->flip_board();
|
||||
chess_widget.flip_board();
|
||||
}));
|
||||
game_menu->add_separator();
|
||||
|
||||
|
@ -112,7 +116,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|||
if (result.is_error())
|
||||
return;
|
||||
|
||||
if (auto maybe_error = widget->import_pgn(*result.value().release_stream()); maybe_error.is_error()) {
|
||||
if (auto maybe_error = chess_widget.import_pgn(*result.value().release_stream()); maybe_error.is_error()) {
|
||||
auto error_message = maybe_error.release_error().message();
|
||||
dbgln("Failed to import PGN: {}", error_message);
|
||||
GUI::MessageBox::show(window, error_message, "Import Error"sv, GUI::MessageBox::Type::Information);
|
||||
|
@ -125,23 +129,23 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|||
if (result.is_error())
|
||||
return;
|
||||
|
||||
if (auto maybe_error = widget->export_pgn(*result.value().release_stream()); maybe_error.is_error())
|
||||
if (auto maybe_error = chess_widget.export_pgn(*result.value().release_stream()); maybe_error.is_error())
|
||||
dbgln("Failed to export PGN: {}", maybe_error.release_error());
|
||||
else
|
||||
dbgln("Exported PGN file to {}", result.value().filename());
|
||||
}));
|
||||
game_menu->add_action(GUI::Action::create("&Copy FEN", { Mod_Ctrl, Key_C }, [&](auto&) {
|
||||
GUI::Clipboard::the().set_data(widget->get_fen().release_value_but_fixme_should_propagate_errors().bytes());
|
||||
GUI::Clipboard::the().set_data(chess_widget.get_fen().release_value_but_fixme_should_propagate_errors().bytes());
|
||||
GUI::MessageBox::show(window, "Board state copied to clipboard as FEN."sv, "Copy FEN"sv, GUI::MessageBox::Type::Information);
|
||||
}));
|
||||
game_menu->add_separator();
|
||||
|
||||
game_menu->add_action(GUI::Action::create("&New Game", { Mod_None, Key_F2 }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/reload.png"sv)), [&](auto&) {
|
||||
if (widget->board().game_result() == Chess::Board::Result::NotFinished) {
|
||||
if (widget->resign() < 0)
|
||||
if (chess_widget.board().game_result() == Chess::Board::Result::NotFinished) {
|
||||
if (chess_widget.resign() < 0)
|
||||
return;
|
||||
}
|
||||
widget->reset();
|
||||
chess_widget.reset();
|
||||
}));
|
||||
game_menu->add_separator();
|
||||
|
||||
|
@ -154,11 +158,11 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|||
game_menu->add_action(settings_action);
|
||||
|
||||
auto show_available_moves_action = GUI::Action::create_checkable("Show Available Moves", [&](auto& action) {
|
||||
widget->set_show_available_moves(action.is_checked());
|
||||
widget->update();
|
||||
chess_widget.set_show_available_moves(action.is_checked());
|
||||
chess_widget.update();
|
||||
Config::write_bool("Games"sv, "Chess"sv, "ShowAvailableMoves"sv, action.is_checked());
|
||||
});
|
||||
show_available_moves_action->set_checked(widget->show_available_moves());
|
||||
show_available_moves_action->set_checked(chess_widget.show_available_moves());
|
||||
game_menu->add_action(show_available_moves_action);
|
||||
game_menu->add_separator();
|
||||
|
||||
|
@ -172,7 +176,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|||
engines_action_group.set_exclusive(true);
|
||||
auto engine_submenu = engine_menu->add_submenu("&Engine"_string);
|
||||
auto human_engine_checkbox = GUI::Action::create_checkable("Human", [&](auto&) {
|
||||
widget->set_engine(nullptr);
|
||||
chess_widget.set_engine(nullptr);
|
||||
});
|
||||
human_engine_checkbox->set_checked(true);
|
||||
engines_action_group.add_action(human_engine_checkbox);
|
||||
|
@ -182,17 +186,17 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|||
auto action = GUI::Action::create_checkable(engine.name, [&](auto&) {
|
||||
auto new_engine = Engine::construct(engine.path);
|
||||
new_engine->on_connection_lost = [&]() {
|
||||
if (!widget->want_engine_move())
|
||||
if (!chess_widget.want_engine_move())
|
||||
return;
|
||||
|
||||
auto rc = GUI::MessageBox::show(window, "Connection to the chess engine was lost while waiting for a move. Do you want to try again?"sv, "Chess"sv, GUI::MessageBox::Type::Question, GUI::MessageBox::InputType::YesNo);
|
||||
if (rc == GUI::Dialog::ExecResult::Yes)
|
||||
widget->input_engine_move();
|
||||
chess_widget.input_engine_move();
|
||||
else
|
||||
human_engine_checkbox->activate();
|
||||
};
|
||||
widget->set_engine(move(new_engine));
|
||||
widget->input_engine_move();
|
||||
chess_widget.set_engine(move(new_engine));
|
||||
chess_widget.input_engine_move();
|
||||
});
|
||||
engines_action_group.add_action(*action);
|
||||
engine_submenu->add_action(*action);
|
||||
|
@ -211,7 +215,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|||
help_menu->add_action(GUI::CommonActions::make_about_action("Chess"_string, app_icon, window));
|
||||
|
||||
window->show();
|
||||
widget->reset();
|
||||
chess_widget.reset();
|
||||
|
||||
return app->exec();
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue