mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-09-17 15:02:24 +00:00
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: 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
|
DEPENDS ChessEngine
|
||||||
)
|
)
|
||||||
|
|
||||||
|
compile_gml(Chess.gml ChessGML.cpp chess_gml)
|
||||||
|
compile_gml(PromotionWidget.gml PromotionWidgetGML.cpp promotionWidget_gml)
|
||||||
|
|
||||||
set(SOURCES
|
set(SOURCES
|
||||||
main.cpp
|
main.cpp
|
||||||
ChessWidget.cpp
|
ChessWidget.cpp
|
||||||
PromotionDialog.cpp
|
PromotionDialog.cpp
|
||||||
Engine.cpp
|
Engine.cpp
|
||||||
|
ChessGML.cpp
|
||||||
|
PromotionWidgetGML.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
serenity_app(Chess ICON app-chess)
|
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() };
|
Chess::Move move = { m_moving_square, target_square.release_value() };
|
||||||
if (board().is_promotion_move(move)) {
|
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)
|
if (promotion_dialog->exec() == PromotionDialog::ExecResult::OK)
|
||||||
move.promote_to = promotion_dialog->selected_piece();
|
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 {
|
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())
|
: Dialog(chess_widget.window())
|
||||||
, m_selected_piece(Chess::Type::None)
|
, m_selected_piece(Chess::Type::None)
|
||||||
{
|
{
|
||||||
set_title("Choose piece to promote to");
|
set_title("Choose piece to promote to");
|
||||||
set_icon(chess_widget.window()->icon());
|
set_icon(chess_widget.window()->icon());
|
||||||
resize(70 * 4, 70);
|
set_main_widget(promotion_widget);
|
||||||
|
|
||||||
auto main_widget = set_main_widget<GUI::Frame>();
|
auto initialize_promotion_button = [&](StringView button_name, Chess::Type piece) {
|
||||||
main_widget->set_frame_style(Gfx::FrameStyle::SunkenContainer);
|
auto button = promotion_widget->find_descendant_of_type_named<GUI::Button>(button_name);
|
||||||
main_widget->set_fill_with_background_color(true);
|
button->set_icon(chess_widget.get_piece_graphic({ chess_widget.board().turn(), piece }));
|
||||||
main_widget->set_layout<GUI::HorizontalBoxLayout>();
|
button->on_click = [this, piece](auto) {
|
||||||
|
m_selected_piece = piece;
|
||||||
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;
|
|
||||||
done(ExecResult::OK);
|
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)
|
void PromotionDialog::event(Core::Event& event)
|
||||||
|
|
|
@ -7,17 +7,19 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ChessWidget.h"
|
#include "ChessWidget.h"
|
||||||
|
#include "PromotionWidget.h"
|
||||||
#include <LibGUI/Dialog.h>
|
#include <LibGUI/Dialog.h>
|
||||||
|
|
||||||
namespace Chess {
|
namespace Chess {
|
||||||
|
|
||||||
class PromotionDialog final : public GUI::Dialog {
|
class PromotionDialog final : public GUI::Dialog {
|
||||||
C_OBJECT(PromotionDialog)
|
C_OBJECT_ABSTRACT(PromotionDialog)
|
||||||
public:
|
public:
|
||||||
|
static ErrorOr<NonnullRefPtr<PromotionDialog>> try_create(ChessWidget& chess_widget);
|
||||||
Chess::Type selected_piece() const { return m_selected_piece; }
|
Chess::Type selected_piece() const { return m_selected_piece; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit PromotionDialog(ChessWidget& chess_widget);
|
PromotionDialog(NonnullRefPtr<Chess::PromotionWidget> promotion_widget, ChessWidget& chess_widget);
|
||||||
virtual void event(Core::Event&) override;
|
virtual void event(Core::Event&) override;
|
||||||
|
|
||||||
Chess::Type m_selected_piece;
|
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 "ChessWidget.h"
|
||||||
|
#include "MainWidget.h"
|
||||||
#include <LibConfig/Client.h>
|
#include <LibConfig/Client.h>
|
||||||
#include <LibCore/System.h>
|
#include <LibCore/System.h>
|
||||||
#include <LibDesktop/Launcher.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 app_icon = TRY(GUI::Icon::try_create_default_icon("app-chess"sv));
|
||||||
|
|
||||||
auto window = GUI::Window::construct();
|
auto window = GUI::Window::construct();
|
||||||
auto widget = TRY(Chess::ChessWidget::try_create());
|
auto main_widget = TRY(Chess::MainWidget::try_create());
|
||||||
window->set_main_widget(widget);
|
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());
|
auto engines = TRY(available_engines());
|
||||||
for (auto const& engine : 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));
|
window->set_icon(app_icon.bitmap_for_size(16));
|
||||||
|
|
||||||
widget->set_piece_set(Config::read_string("Games"sv, "Chess"sv, "PieceSet"sv, "Classic"sv));
|
chess_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));
|
chess_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));
|
chess_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));
|
chess_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_highlight_checks(Config::read_bool("Games"sv, "Chess"sv, "HighlightChecks"sv, true));
|
||||||
|
|
||||||
auto game_menu = window->add_menu("&Game"_string);
|
auto game_menu = window->add_menu("&Game"_string);
|
||||||
|
|
||||||
game_menu->add_action(GUI::Action::create("&Resign", { Mod_None, Key_F3 }, [&](auto&) {
|
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&) {
|
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();
|
game_menu->add_separator();
|
||||||
|
|
||||||
|
@ -112,7 +116,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||||
if (result.is_error())
|
if (result.is_error())
|
||||||
return;
|
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();
|
auto error_message = maybe_error.release_error().message();
|
||||||
dbgln("Failed to import PGN: {}", error_message);
|
dbgln("Failed to import PGN: {}", error_message);
|
||||||
GUI::MessageBox::show(window, error_message, "Import Error"sv, GUI::MessageBox::Type::Information);
|
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())
|
if (result.is_error())
|
||||||
return;
|
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());
|
dbgln("Failed to export PGN: {}", maybe_error.release_error());
|
||||||
else
|
else
|
||||||
dbgln("Exported PGN file to {}", result.value().filename());
|
dbgln("Exported PGN file to {}", result.value().filename());
|
||||||
}));
|
}));
|
||||||
game_menu->add_action(GUI::Action::create("&Copy FEN", { Mod_Ctrl, Key_C }, [&](auto&) {
|
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);
|
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_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&) {
|
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 (chess_widget.board().game_result() == Chess::Board::Result::NotFinished) {
|
||||||
if (widget->resign() < 0)
|
if (chess_widget.resign() < 0)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
widget->reset();
|
chess_widget.reset();
|
||||||
}));
|
}));
|
||||||
game_menu->add_separator();
|
game_menu->add_separator();
|
||||||
|
|
||||||
|
@ -154,11 +158,11 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||||
game_menu->add_action(settings_action);
|
game_menu->add_action(settings_action);
|
||||||
|
|
||||||
auto show_available_moves_action = GUI::Action::create_checkable("Show Available Moves", [&](auto& action) {
|
auto show_available_moves_action = GUI::Action::create_checkable("Show Available Moves", [&](auto& action) {
|
||||||
widget->set_show_available_moves(action.is_checked());
|
chess_widget.set_show_available_moves(action.is_checked());
|
||||||
widget->update();
|
chess_widget.update();
|
||||||
Config::write_bool("Games"sv, "Chess"sv, "ShowAvailableMoves"sv, action.is_checked());
|
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_action(show_available_moves_action);
|
||||||
game_menu->add_separator();
|
game_menu->add_separator();
|
||||||
|
|
||||||
|
@ -172,7 +176,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||||
engines_action_group.set_exclusive(true);
|
engines_action_group.set_exclusive(true);
|
||||||
auto engine_submenu = engine_menu->add_submenu("&Engine"_string);
|
auto engine_submenu = engine_menu->add_submenu("&Engine"_string);
|
||||||
auto human_engine_checkbox = GUI::Action::create_checkable("Human", [&](auto&) {
|
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);
|
human_engine_checkbox->set_checked(true);
|
||||||
engines_action_group.add_action(human_engine_checkbox);
|
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 action = GUI::Action::create_checkable(engine.name, [&](auto&) {
|
||||||
auto new_engine = Engine::construct(engine.path);
|
auto new_engine = Engine::construct(engine.path);
|
||||||
new_engine->on_connection_lost = [&]() {
|
new_engine->on_connection_lost = [&]() {
|
||||||
if (!widget->want_engine_move())
|
if (!chess_widget.want_engine_move())
|
||||||
return;
|
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);
|
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)
|
if (rc == GUI::Dialog::ExecResult::Yes)
|
||||||
widget->input_engine_move();
|
chess_widget.input_engine_move();
|
||||||
else
|
else
|
||||||
human_engine_checkbox->activate();
|
human_engine_checkbox->activate();
|
||||||
};
|
};
|
||||||
widget->set_engine(move(new_engine));
|
chess_widget.set_engine(move(new_engine));
|
||||||
widget->input_engine_move();
|
chess_widget.input_engine_move();
|
||||||
});
|
});
|
||||||
engines_action_group.add_action(*action);
|
engines_action_group.add_action(*action);
|
||||||
engine_submenu->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));
|
help_menu->add_action(GUI::CommonActions::make_about_action("Chess"_string, app_icon, window));
|
||||||
|
|
||||||
window->show();
|
window->show();
|
||||||
widget->reset();
|
chess_widget.reset();
|
||||||
|
|
||||||
return app->exec();
|
return app->exec();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue