summaryrefslogtreecommitdiff
path: root/Userland
diff options
context:
space:
mode:
authorTim Ledbetter <timledbetter@gmail.com>2023-04-02 21:14:16 +0100
committerSam Atkins <atkinssj@gmail.com>2023-04-20 09:59:18 +0100
commit8b6c538f2a6c0d3b1597a1a2c86f634594cc2584 (patch)
treefa06c822eb46b84397d76bb74455dbf9070168d4 /Userland
parent55347ed6a59d51edd7a1b1867682cf751fb900be (diff)
downloadserenity-8b6c538f2a6c0d3b1597a1a2c86f634594cc2584.zip
Chess: Gracefully handle ChessEngine disconnections
The GUI now tracks when it becomes disconnected from ChessEngine. If not currently waiting for a move from ChessEngine, it will automatically reconnect on the next engine move. If a disconnection occurs while waiting for a move, the player is asked whether they want to try again or not.
Diffstat (limited to 'Userland')
-rw-r--r--Userland/Games/Chess/ChessWidget.cpp6
-rw-r--r--Userland/Games/Chess/Engine.cpp28
-rw-r--r--Userland/Games/Chess/Engine.h11
-rw-r--r--Userland/Games/Chess/main.cpp38
4 files changed, 63 insertions, 20 deletions
diff --git a/Userland/Games/Chess/ChessWidget.cpp b/Userland/Games/Chess/ChessWidget.cpp
index 753bb91f66..bb2bd05900 100644
--- a/Userland/Games/Chess/ChessWidget.cpp
+++ b/Userland/Games/Chess/ChessWidget.cpp
@@ -473,12 +473,14 @@ void ChessWidget::input_engine_move()
set_drag_enabled(false);
set_override_cursor(Gfx::StandardCursor::Wait);
- m_engine->get_best_move(board(), 4000, [this, drag_was_enabled](Chess::Move move) {
+ m_engine->get_best_move(board(), 4000, [this, drag_was_enabled](ErrorOr<Chess::Move> move) {
set_override_cursor(Gfx::StandardCursor::None);
if (!want_engine_move())
return;
set_drag_enabled(drag_was_enabled);
- VERIFY(board().apply_move(move));
+ if (!move.is_error())
+ VERIFY(board().apply_move(move.release_value()));
+
m_playback_move_number = m_board.moves().size();
m_playback = false;
m_board_markings.clear();
diff --git a/Userland/Games/Chess/Engine.cpp b/Userland/Games/Chess/Engine.cpp
index cbfcf40d1d..8f8b95227b 100644
--- a/Userland/Games/Chess/Engine.cpp
+++ b/Userland/Games/Chess/Engine.cpp
@@ -17,6 +17,11 @@ Engine::~Engine()
}
Engine::Engine(StringView command)
+ : m_command(command)
+{
+}
+
+void Engine::connect_to_engine_service()
{
int wpipefds[2];
int rpipefds[2];
@@ -35,9 +40,9 @@ Engine::Engine(StringView command)
posix_spawn_file_actions_adddup2(&file_actions, wpipefds[0], STDIN_FILENO);
posix_spawn_file_actions_adddup2(&file_actions, rpipefds[1], STDOUT_FILENO);
- DeprecatedString cstr(command);
- char const* argv[] = { cstr.characters(), nullptr };
- if (posix_spawnp(&m_pid, cstr.characters(), &file_actions, nullptr, const_cast<char**>(argv), environ) < 0) {
+ char const* argv[] = { m_command.characters(), nullptr };
+ pid_t pid = -1;
+ if (posix_spawnp(&pid, m_command.characters(), &file_actions, nullptr, const_cast<char**>(argv), environ) < 0) {
perror("posix_spawnp");
VERIFY_NOT_REACHED();
}
@@ -56,6 +61,7 @@ Engine::Engine(StringView command)
set_out(outfile);
send_command(Chess::UCI::UCICommand());
+ m_connected = true;
}
void Engine::handle_bestmove(Chess::UCI::BestMoveCommand const& command)
@@ -68,5 +74,21 @@ void Engine::handle_bestmove(Chess::UCI::BestMoveCommand const& command)
void Engine::quit()
{
+ if (!m_connected)
+ return;
+
send_command(Chess::UCI::QuitCommand());
+ m_connected = false;
+}
+
+void Engine::handle_unexpected_eof()
+{
+ m_connected = false;
+ if (m_bestmove_callback)
+ m_bestmove_callback(Error::from_errno(EPIPE));
+
+ m_bestmove_callback = nullptr;
+
+ if (on_connection_lost)
+ on_connection_lost();
}
diff --git a/Userland/Games/Chess/Engine.h b/Userland/Games/Chess/Engine.h
index dd5906cf84..efbfd5b463 100644
--- a/Userland/Games/Chess/Engine.h
+++ b/Userland/Games/Chess/Engine.h
@@ -20,11 +20,17 @@ public:
Engine(Engine const&) = delete;
Engine& operator=(Engine const&) = delete;
+ Function<void()> on_connection_lost;
+
virtual void handle_bestmove(Chess::UCI::BestMoveCommand const&) override;
+ virtual void handle_unexpected_eof() override;
template<typename Callback>
void get_best_move(Chess::Board const& board, int time_limit, Callback&& callback)
{
+ if (!m_connected)
+ connect_to_engine_service();
+
send_command(Chess::UCI::PositionCommand({}, board.moves()));
Chess::UCI::GoCommand go_command;
go_command.movetime = time_limit;
@@ -34,6 +40,9 @@ public:
private:
void quit();
+ void connect_to_engine_service();
- Function<void(Chess::Move)> m_bestmove_callback;
+ DeprecatedString m_command;
+ Function<void(ErrorOr<Chess::Move>)> m_bestmove_callback;
+ bool m_connected { false };
};
diff --git a/Userland/Games/Chess/main.cpp b/Userland/Games/Chess/main.cpp
index f777277598..62db52d1d2 100644
--- a/Userland/Games/Chess/main.cpp
+++ b/Userland/Games/Chess/main.cpp
@@ -128,21 +128,31 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
GUI::ActionGroup engines_action_group;
engines_action_group.set_exclusive(true);
auto engine_submenu = TRY(engine_menu->try_add_submenu("&Engine"_short_string));
- for (auto const& engine : { "Human", "ChessEngine" }) {
- auto action = GUI::Action::create_checkable(engine, [&](auto& action) {
- if (action.text() == "Human") {
- widget->set_engine(nullptr);
- } else {
- widget->set_engine(Engine::construct(action.text()));
+ auto human_engine_checkbox = GUI::Action::create_checkable("Human", [&](auto&) {
+ widget->set_engine(nullptr);
+ });
+ human_engine_checkbox->set_checked(true);
+ engines_action_group.add_action(human_engine_checkbox);
+ TRY(engine_submenu->try_add_action(human_engine_checkbox));
+
+ auto action = GUI::Action::create_checkable("ChessEngine", [&](auto& action) {
+ auto new_engine = Engine::construct(action.text());
+ new_engine->on_connection_lost = [&]() {
+ if (!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();
- }
- });
- engines_action_group.add_action(*action);
- if (engine == DeprecatedString("Human"))
- action->set_checked(true);
-
- TRY(engine_submenu->try_add_action(*action));
- }
+ else
+ human_engine_checkbox->activate();
+ };
+ widget->set_engine(move(new_engine));
+ widget->input_engine_move();
+ });
+ engines_action_group.add_action(*action);
+
+ TRY(engine_submenu->try_add_action(*action));
auto help_menu = TRY(window->try_add_menu("&Help"_short_string));
TRY(help_menu->try_add_action(GUI::CommonActions::make_command_palette_action(window)));