diff --git a/Libraries/LibChess/CMakeLists.txt b/Libraries/LibChess/CMakeLists.txt index 166829bc7c..e35744070a 100644 --- a/Libraries/LibChess/CMakeLists.txt +++ b/Libraries/LibChess/CMakeLists.txt @@ -1,5 +1,7 @@ set(SOURCES Chess.cpp + UCICommand.cpp + UCIEndpoint.cpp ) serenity_lib(LibChess chess) diff --git a/Libraries/LibChess/Chess.cpp b/Libraries/LibChess/Chess.cpp index 58f8985670..28249c6956 100644 --- a/Libraries/LibChess/Chess.cpp +++ b/Libraries/LibChess/Chess.cpp @@ -53,6 +53,25 @@ String char_for_piece(Chess::Type type) } } +Chess::Type piece_for_char_promotion(const StringView& str) +{ + String string = String(str).to_lowercase(); + if (string == "") + return Type::None; + if (string == "n") + return Type::Knight; + if (string == "b") + return Type::Bishop; + if (string == "r") + return Type::Rook; + if (string == "q") + return Type::Queen; + if (string == "k") + return Type::King; + + return Type::None; +} + Colour opposing_colour(Colour colour) { return (colour == Colour::White) ? Colour::Black : Colour::White; @@ -82,11 +101,18 @@ Square::Square(const StringView& name) String Square::to_algebraic() const { StringBuilder builder; - builder.append(file - 'a'); - builder.append(rank - '1'); + builder.append(file + 'a'); + builder.append(rank + '1'); return builder.build(); } +Move::Move(const StringView& algebraic) + : from(algebraic.substring_view(0, 2)) + , to(algebraic.substring_view(2, 2)) + , promote_to(piece_for_char_promotion((algebraic.length() >= 5) ? algebraic.substring_view(4, 1) : "")) +{ +} + String Move::to_long_algebraic() const { StringBuilder builder; diff --git a/Libraries/LibChess/Chess.h b/Libraries/LibChess/Chess.h index 1848326f07..efb43d3315 100644 --- a/Libraries/LibChess/Chess.h +++ b/Libraries/LibChess/Chess.h @@ -45,6 +45,7 @@ enum class Type { }; String char_for_piece(Type type); +Chess::Type piece_for_char_promotion(const StringView& str); enum class Colour { White, diff --git a/Libraries/LibChess/UCICommand.cpp b/Libraries/LibChess/UCICommand.cpp new file mode 100644 index 0000000000..82fcbd1f71 --- /dev/null +++ b/Libraries/LibChess/UCICommand.cpp @@ -0,0 +1,333 @@ +/* + * 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 "UCICommand.h" +#include + +namespace Chess::UCI { + +UCICommand UCICommand::from_string(const StringView& command) +{ + auto tokens = command.split_view(' '); + ASSERT(tokens[0] == "uci"); + ASSERT(tokens.size() == 1); + return UCICommand(); +} + +String UCICommand::to_string() const +{ + return "uci\n"; +} + +DebugCommand DebugCommand::from_string(const StringView& command) +{ + auto tokens = command.split_view(' '); + ASSERT(tokens[0] == "debug"); + ASSERT(tokens.size() == 2); + if (tokens[1] == "on") + return DebugCommand(Flag::On); + if (tokens[1] == "off") + return DebugCommand(Flag::On); + + ASSERT_NOT_REACHED(); +} + +String DebugCommand::to_string() const +{ + if (flag() == Flag::On) { + return "debug on\n"; + } else { + return "debug off\n"; + } +} + +IsReadyCommand IsReadyCommand::from_string(const StringView& command) +{ + auto tokens = command.split_view(' '); + ASSERT(tokens[0] == "isready"); + ASSERT(tokens.size() == 1); + return IsReadyCommand(); +} + +String IsReadyCommand::to_string() const +{ + return "isready\n"; +} + +SetOptionCommand SetOptionCommand::from_string(const StringView& command) +{ + auto tokens = command.split_view(' '); + ASSERT(tokens[0] == "setoption"); + ASSERT(tokens[1] == "name"); + if (tokens.size() == 3) { + return SetOptionCommand(tokens[1]); + } else if (tokens.size() == 4) { + ASSERT(tokens[2] == "value"); + return SetOptionCommand(tokens[1], tokens[3]); + } + ASSERT_NOT_REACHED(); +} + +String SetOptionCommand::to_string() const +{ + StringBuilder builder; + builder.append("setoption name "); + builder.append(name()); + if (value().has_value()) { + builder.append(" value "); + builder.append(value().value()); + } + builder.append('\n'); + return builder.build(); +} + +PositionCommand PositionCommand::from_string(const StringView& command) +{ + auto tokens = command.split_view(' '); + ASSERT(tokens.size() >= 3); + ASSERT(tokens[0] == "position"); + ASSERT(tokens[2] == "moves"); + + Optional fen; + if (tokens[1] != "startpos") + fen = tokens[1]; + + Vector moves; + for (size_t i = 3; i < tokens.size(); ++i) { + moves.append(Move(tokens[i])); + } + return PositionCommand(fen, moves); +} + +String PositionCommand::to_string() const +{ + StringBuilder builder; + builder.append("position "); + if (fen().has_value()) { + builder.append(fen().value()); + } else { + builder.append("startpos "); + } + builder.append("moves"); + for (auto& move : moves()) { + builder.append(' '); + builder.append(move.to_long_algebraic()); + } + builder.append('\n'); + return builder.build(); +} + +GoCommand GoCommand::from_string(const StringView& command) +{ + auto tokens = command.split_view(' '); + ASSERT(tokens[0] == "go"); + + GoCommand go_command; + for (size_t i = 1; i < tokens.size(); ++i) { + if (tokens[i] == "searchmoves") { + ASSERT_NOT_REACHED(); + } else if (tokens[i] == "ponder") { + go_command.ponder = true; + } else if (tokens[i] == "wtime") { + ASSERT(i++ < tokens.size()); + go_command.wtime = tokens[i].to_int().value(); + } else if (tokens[i] == "btime") { + ASSERT(i++ < tokens.size()); + go_command.btime = tokens[i].to_int().value(); + } else if (tokens[i] == "winc") { + ASSERT(i++ < tokens.size()); + go_command.winc = tokens[i].to_int().value(); + } else if (tokens[i] == "binc") { + ASSERT(i++ < tokens.size()); + go_command.binc = tokens[i].to_int().value(); + } else if (tokens[i] == "movestogo") { + ASSERT(i++ < tokens.size()); + go_command.movestogo = tokens[i].to_int().value(); + } else if (tokens[i] == "depth") { + ASSERT(i++ < tokens.size()); + go_command.depth = tokens[i].to_int().value(); + } else if (tokens[i] == "nodes") { + ASSERT(i++ < tokens.size()); + go_command.nodes = tokens[i].to_int().value(); + } else if (tokens[i] == "mate") { + ASSERT(i++ < tokens.size()); + go_command.mate = tokens[i].to_int().value(); + } else if (tokens[i] == "movetime") { + ASSERT(i++ < tokens.size()); + go_command.movetime = tokens[i].to_int().value(); + } else if (tokens[i] == "infinite") { + go_command.infinite = true; + } + } + + return go_command; +} + +String GoCommand::to_string() const +{ + StringBuilder builder; + builder.append("go"); + + if (searchmoves.has_value()) { + builder.append(" searchmoves"); + for (auto& move : searchmoves.value()) { + builder.append(' '); + builder.append(move.to_long_algebraic()); + } + } + + if (ponder) + builder.append(" ponder"); + if (wtime.has_value()) + builder.appendf(" wtime %i", wtime.value()); + if (btime.has_value()) + builder.appendf(" btime %i", btime.value()); + if (winc.has_value()) + builder.appendf(" winc %i", winc.value()); + if (binc.has_value()) + builder.appendf(" binc %i", binc.value()); + if (movestogo.has_value()) + builder.appendf(" movestogo %i", movestogo.value()); + if (depth.has_value()) + builder.appendf(" depth %i", depth.value()); + if (nodes.has_value()) + builder.appendf(" nodes %i", nodes.value()); + if (mate.has_value()) + builder.appendf(" mate %i", mate.value()); + if (movetime.has_value()) + builder.appendf(" movetime %i", movetime.value()); + if (infinite) + builder.append(" infinite"); + + builder.append('\n'); + return builder.build(); +} + +StopCommand StopCommand::from_string(const StringView& command) +{ + auto tokens = command.split_view(' '); + ASSERT(tokens[0] == "stop"); + ASSERT(tokens.size() == 1); + return StopCommand(); +} + +String StopCommand::to_string() const +{ + return "stop\n"; +} + +IdCommand IdCommand::from_string(const StringView& command) +{ + auto tokens = command.split_view(' '); + ASSERT(tokens[0] == "id"); + StringBuilder value; + for (size_t i = 2; i < tokens.size(); ++i) { + if (i != 2) + value.append(' '); + + value.append(tokens[i]); + } + + if (tokens[1] == "name") { + return IdCommand(Type::Name, value.build()); + } else if (tokens[1] == "author") { + return IdCommand(Type::Author, value.build()); + } + ASSERT_NOT_REACHED(); +} + +String IdCommand::to_string() const +{ + StringBuilder builder; + builder.append("id "); + if (field_type() == Type::Name) { + builder.append("name "); + } else { + builder.append("author "); + } + builder.append(value()); + builder.append('\n'); + return builder.build(); +} + +UCIOkCommand UCIOkCommand::from_string(const StringView& command) +{ + auto tokens = command.split_view(' '); + ASSERT(tokens[0] == "uciok"); + ASSERT(tokens.size() == 1); + return UCIOkCommand(); +} + +String UCIOkCommand::to_string() const +{ + return "uciok\n"; +} + +ReadyOkCommand ReadyOkCommand::from_string(const StringView& command) +{ + auto tokens = command.split_view(' '); + ASSERT(tokens[0] == "readyok"); + ASSERT(tokens.size() == 1); + return ReadyOkCommand(); +} + +String ReadyOkCommand::to_string() const +{ + return "readyok\n"; +} + +BestMoveCommand BestMoveCommand::from_string(const StringView& command) +{ + auto tokens = command.split_view(' '); + ASSERT(tokens[0] == "bestmove"); + ASSERT(tokens.size() == 2); + return BestMoveCommand(Move(tokens[1])); +} + +String BestMoveCommand::to_string() const +{ + StringBuilder builder; + builder.append("bestmove "); + builder.append(move().to_long_algebraic()); + builder.append('\n'); + return builder.build(); +} + +InfoCommand InfoCommand::from_string(const StringView& command) +{ + (void)command; + // FIXME: Implement this. + ASSERT_NOT_REACHED(); +} + +String InfoCommand::to_string() const +{ + // FIXME: Implement this. + ASSERT_NOT_REACHED(); + return "info"; +} + +} diff --git a/Libraries/LibChess/UCICommand.h b/Libraries/LibChess/UCICommand.h new file mode 100644 index 0000000000..cd79751d98 --- /dev/null +++ b/Libraries/LibChess/UCICommand.h @@ -0,0 +1,291 @@ +/* + * 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 +#include +#include +#include + +namespace Chess::UCI { + +class Command : public Core::Event { +public: + enum Type { + // GUI to engine commands. + UCI = 12000, + Debug, + IsReady, + SetOption, + Register, + UCINewGame, + Position, + Go, + Stop, + PonderHit, + Quit, + // Engine to GUI commands. + Id, + UCIOk, + ReadyOk, + BestMove, + CopyProtection, + Registration, + Info, + Option, + }; + + explicit Command(Type type) + : Core::Event(type) + { + } + + virtual String to_string() const = 0; + + virtual ~Command() { } +}; + +class UCICommand : public Command { +public: + explicit UCICommand() + : Command(Command::Type::UCI) + { + } + + static UCICommand from_string(const StringView& command); + + virtual String to_string() const; +}; + +class DebugCommand : public Command { +public: + enum class Flag { + On, + Off + }; + + explicit DebugCommand(Flag flag) + : Command(Command::Type::Debug) + , m_flag(flag) + { + } + + static DebugCommand from_string(const StringView& command); + + virtual String to_string() const; + + Flag flag() const { return m_flag; } + +private: + Flag m_flag; +}; + +class IsReadyCommand : public Command { +public: + explicit IsReadyCommand() + : Command(Command::Type::IsReady) + { + } + + static IsReadyCommand from_string(const StringView& command); + + virtual String to_string() const; +}; + +class SetOptionCommand : public Command { +public: + explicit SetOptionCommand(const StringView& name, Optional value = {}) + : Command(Command::Type::SetOption) + , m_name(name) + , m_value(value) + { + } + + static SetOptionCommand from_string(const StringView& command); + + virtual String to_string() const; + + const String& name() const { return m_name; } + const Optional& value() const { return m_value; } + +private: + String m_name; + Optional m_value; +}; + +class PositionCommand : public Command { +public: + explicit PositionCommand(const Optional& fen, const Vector& moves) + : Command(Command::Type::Position) + , m_fen(fen) + , m_moves(moves) + { + } + + static PositionCommand from_string(const StringView& command); + + virtual String to_string() const; + + const Optional& fen() const { return m_fen; } + const Vector& moves() const { return m_moves; } + +private: + Optional m_fen; + Vector m_moves; +}; + +class GoCommand : public Command { +public: + explicit GoCommand() + : Command(Command::Type::Go) + { + } + + static GoCommand from_string(const StringView& command); + + virtual String to_string() const; + + Optional> searchmoves; + bool ponder { false }; + Optional wtime; + Optional btime; + Optional winc; + Optional binc; + Optional movestogo; + Optional depth; + Optional nodes; + Optional mate; + Optional movetime; + bool infinite { false }; +}; + +class StopCommand : public Command { +public: + explicit StopCommand() + : Command(Command::Type::Stop) + { + } + + static StopCommand from_string(const StringView& command); + + virtual String to_string() const; +}; + +class IdCommand : public Command { +public: + enum class Type { + Name, + Author, + }; + + explicit IdCommand(Type field_type, const StringView& value) + : Command(Command::Type::Id) + , m_field_type(field_type) + , m_value(value) + { + } + + static IdCommand from_string(const StringView& command); + + virtual String to_string() const; + + Type field_type() const { return m_field_type; } + const String& value() const { return m_value; } + +private: + Type m_field_type; + String m_value; +}; + +class UCIOkCommand : public Command { +public: + explicit UCIOkCommand() + : Command(Command::Type::UCIOk) + { + } + + static UCIOkCommand from_string(const StringView& command); + + virtual String to_string() const; +}; + +class ReadyOkCommand : public Command { +public: + explicit ReadyOkCommand() + : Command(Command::Type::ReadyOk) + { + } + + static ReadyOkCommand from_string(const StringView& command); + + virtual String to_string() const; +}; + +class BestMoveCommand : public Command { +public: + explicit BestMoveCommand(const Chess::Move& move) + : Command(Command::Type::BestMove) + , m_move(move) + { + } + + static BestMoveCommand from_string(const StringView& command); + + virtual String to_string() const; + + Chess::Move move() const { return m_move; } + +private: + Chess::Move m_move; +}; + +class InfoCommand : public Command { +public: + explicit InfoCommand() + : Command(Command::Type::BestMove) + { + } + + static InfoCommand from_string(const StringView& command); + + virtual String to_string() const; + + Optional depth; + Optional seldepth; + Optional time; + Optional nodes; + Optional> pv; + // FIXME: Add multipv. + Optional score_cp; + Optional score_mate; + // FIXME: Add score bounds. + Optional currmove; + Optional currmove_number; + // FIXME: Add additional fields. +}; + +} diff --git a/Libraries/LibChess/UCIEndpoint.cpp b/Libraries/LibChess/UCIEndpoint.cpp new file mode 100644 index 0000000000..1c0def8b35 --- /dev/null +++ b/Libraries/LibChess/UCIEndpoint.cpp @@ -0,0 +1,132 @@ +/* + * 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 "UCIEndpoint.h" +#include +#include +#include +#include + +// #define UCI_DEBUG + +namespace Chess::UCI { + +Endpoint::Endpoint(NonnullRefPtr in, NonnullRefPtr out) + : m_in(in) + , m_out(out) + , m_in_notifier(Core::Notifier::construct(in->fd(), Core::Notifier::Read)) +{ + set_in_notifier(); +} + +void Endpoint::send_command(const Command& command) +{ +#ifdef UCI_DEBUG + dbg() << class_name() << " Sent UCI Command: " << String(command.to_string().characters(), Chomp); +#endif + m_out->write(command.to_string()); +} + +void Endpoint::event(Core::Event& event) +{ + switch (event.type()) { + case Command::Type::UCI: + return handle_uci(); + case Command::Type::Debug: + return handle_debug(static_cast(event)); + case Command::Type::IsReady: + return handle_uci(); + case Command::Type::SetOption: + return handle_setoption(static_cast(event)); + case Command::Type::Position: + return handle_position(static_cast(event)); + case Command::Type::Go: + return handle_go(static_cast(event)); + case Command::Type::Stop: + return handle_stop(); + case Command::Type::Id: + return handle_id(static_cast(event)); + case Command::Type::UCIOk: + return handle_uciok(); + case Command::Type::ReadyOk: + return handle_readyok(); + case Command::Type::BestMove: + return handle_bestmove(static_cast(event)); + case Command::Type::Info: + return handle_info(static_cast(event)); + default: + break; + } +} + +void Endpoint::set_in_notifier() +{ + m_in_notifier = Core::Notifier::construct(m_in->fd(), Core::Notifier::Read); + m_in_notifier->on_ready_to_read = [this] { + while (m_in->can_read_line()) + Core::EventLoop::current().post_event(*this, read_command()); + }; +} + +NonnullOwnPtr Endpoint::read_command() +{ + String line(ReadonlyBytes(m_in->read_line(4096).bytes()), Chomp); + +#ifdef UCI_DEBUG + dbg() << class_name() << " Recieved UCI Command: " << line; +#endif + + if (line == "uci") { + return make(UCICommand::from_string(line)); + } else if (line.starts_with("debug")) { + return make(DebugCommand::from_string(line)); + } else if (line.starts_with("isready")) { + return make(IsReadyCommand::from_string(line)); + } else if (line.starts_with("setoption")) { + return make(SetOptionCommand::from_string(line)); + } else if (line.starts_with("position")) { + return make(PositionCommand::from_string(line)); + } else if (line.starts_with("go")) { + return make(GoCommand::from_string(line)); + } else if (line.starts_with("stop")) { + return make(StopCommand::from_string(line)); + } else if (line.starts_with("id")) { + return make(IdCommand::from_string(line)); + } else if (line.starts_with("uciok")) { + return make(UCIOkCommand::from_string(line)); + } else if (line.starts_with("readyok")) { + return make(ReadyOkCommand::from_string(line)); + } else if (line.starts_with("bestmove")) { + return make(BestMoveCommand::from_string(line)); + } else if (line.starts_with("info")) { + return make(InfoCommand::from_string(line)); + } + + dbg() << "command line: " << line; + ASSERT_NOT_REACHED(); +} + +}; diff --git a/Libraries/LibChess/UCIEndpoint.h b/Libraries/LibChess/UCIEndpoint.h new file mode 100644 index 0000000000..0913c12b35 --- /dev/null +++ b/Libraries/LibChess/UCIEndpoint.h @@ -0,0 +1,80 @@ +/* + * 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 +#include +#include +#include + +namespace Chess::UCI { + +class Endpoint : public Core::Object { + C_OBJECT(Endpoint) +public: + virtual ~Endpoint() override { } + + Endpoint() { } + Endpoint(NonnullRefPtr in, NonnullRefPtr out); + + virtual void handle_uci() { } + virtual void handle_debug(const DebugCommand&) { } + virtual void handle_isready() { } + virtual void handle_setoption(const SetOptionCommand&) { } + virtual void handle_position(const PositionCommand&) { } + virtual void handle_go(const GoCommand&) { } + virtual void handle_stop() { } + virtual void handle_id(const IdCommand&) { } + virtual void handle_uciok() { } + virtual void handle_readyok() { } + virtual void handle_bestmove(const BestMoveCommand&) { } + virtual void handle_info(const InfoCommand&) { } + + void send_command(const Command&); + + virtual void event(Core::Event&); + + Core::IODevice& in() { return *m_in; } + Core::IODevice& out() { return *m_out; } + + void set_in(RefPtr in) + { + m_in = in; + set_in_notifier(); + } + void set_out(RefPtr out) { m_out = out; } + +private: + void set_in_notifier(); + NonnullOwnPtr read_command(); + + RefPtr m_in; + RefPtr m_out; + RefPtr m_in_notifier; +}; + +}