summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibChess
diff options
context:
space:
mode:
authorTim Ledbetter <timledbetter@gmail.com>2023-04-21 19:35:54 +0100
committerSam Atkins <atkinssj@gmail.com>2023-05-03 08:31:34 +0100
commitc885df98da42a9de814aecbf5c0e341b6213e670 (patch)
tree7ced9b57d96736823ad04287a292db3503c1a808 /Userland/Libraries/LibChess
parent77f3e9710beb2f33f6596ad6702d62b179f3cb4c (diff)
downloadserenity-c885df98da42a9de814aecbf5c0e341b6213e670.zip
LibChess: Flesh out InfoCommand implementation
UCI info commands can now be received and parsed. All info types from the UCI specification are supported. The info command is not currently used by our engine or GUI, but the GUI can now receive an info command from a 3rd party engine without complaining.
Diffstat (limited to 'Userland/Libraries/LibChess')
-rw-r--r--Userland/Libraries/LibChess/UCICommand.cpp173
-rw-r--r--Userland/Libraries/LibChess/UCICommand.h47
2 files changed, 203 insertions, 17 deletions
diff --git a/Userland/Libraries/LibChess/UCICommand.cpp b/Userland/Libraries/LibChess/UCICommand.cpp
index aa885acba9..90a84dd9e5 100644
--- a/Userland/Libraries/LibChess/UCICommand.cpp
+++ b/Userland/Libraries/LibChess/UCICommand.cpp
@@ -1,6 +1,7 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
* Copyright (c) 2023, Sam Atkins <atkinssj@serenityos.org>
+ * Copyright (c) 2023, Tim Ledbetter <timledbetter@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -323,16 +324,178 @@ ErrorOr<String> BestMoveCommand::to_string() const
return builder.to_string();
}
-ErrorOr<NonnullOwnPtr<InfoCommand>> InfoCommand::from_string([[maybe_unused]] StringView command)
+ErrorOr<NonnullOwnPtr<InfoCommand>> InfoCommand::from_string(StringView command)
{
- // FIXME: Implement this.
- VERIFY_NOT_REACHED();
+ auto tokens = command.split_view(' ');
+ VERIFY(tokens[0] == "info");
+
+ auto info_command = TRY(try_make<InfoCommand>());
+
+ auto parse_integer_token = [](StringView value_token) -> ErrorOr<int> {
+ auto value_as_integer = value_token.to_int();
+ if (!value_as_integer.has_value())
+ return Error::from_string_literal("Expected integer token");
+
+ return value_as_integer.release_value();
+ };
+
+ auto parse_line = [](auto const& move_tokens) -> ErrorOr<Vector<Chess::Move>> {
+ Vector<Chess::Move> moves;
+ TRY(moves.try_ensure_capacity(move_tokens.size()));
+ for (auto move_token : move_tokens)
+ moves.unchecked_append({ move_token });
+
+ return moves;
+ };
+
+ size_t i = 1;
+ while (i < tokens.size()) {
+ auto name = tokens[i++];
+ if (name == "depth"sv) {
+ info_command->m_depth = TRY(parse_integer_token(tokens[i++]));
+ } else if (name == "seldepth"sv) {
+ info_command->m_seldepth = TRY(parse_integer_token(tokens[i++]));
+ } else if (name == "time"sv) {
+ info_command->m_time = TRY(parse_integer_token(tokens[i++]));
+ } else if (name == "nodes"sv) {
+ info_command->m_nodes = TRY(parse_integer_token(tokens[i++]));
+ } else if (name == "multipv"sv) {
+ info_command->m_multipv = TRY(parse_integer_token(tokens[i++]));
+ } else if (name == "score"sv) {
+ auto score_type_string = tokens[i++];
+ ScoreType score_type;
+ if (score_type_string == "cp"sv) {
+ score_type = ScoreType::Centipawns;
+ } else if (score_type_string == "mate"sv) {
+ score_type = ScoreType::Mate;
+ } else {
+ return Error::from_string_literal("Invalid score type");
+ }
+ auto score_value = TRY(parse_integer_token(tokens[i++]));
+ auto maybe_score_bound_string = tokens[i];
+ auto score_bound = ScoreBound::None;
+ if (maybe_score_bound_string == "upperbound"sv)
+ score_bound = ScoreBound::Upper;
+ else if (maybe_score_bound_string == "lowerbound"sv)
+ score_bound = ScoreBound::Lower;
+
+ if (score_bound != ScoreBound::None)
+ i++;
+
+ info_command->m_score = Score { score_type, score_value, score_bound };
+ } else if (name == "currmove"sv) {
+ info_command->m_currmove = Chess::Move { tokens[i++] };
+ } else if (name == "currmovenumber"sv) {
+ info_command->m_currmovenumber = TRY(parse_integer_token(tokens[i++]));
+ } else if (name == "hashfull"sv) {
+ info_command->m_hashfull = TRY(parse_integer_token(tokens[i++]));
+ } else if (name == "nps"sv) {
+ info_command->m_nps = TRY(parse_integer_token(tokens[i++]));
+ } else if (name == "tbhits"sv) {
+ info_command->m_tbhits = TRY(parse_integer_token(tokens[i++]));
+ } else if (name == "cpuload"sv) {
+ info_command->m_cpuload = TRY(parse_integer_token(tokens[i++]));
+ }
+ // We assume the info types: pv, string, refutation, and currline, are the final info type in a command.
+ else if (name == "pv"sv) {
+ info_command->m_pv = TRY(parse_line(tokens.span().slice(i)));
+ break;
+ } else if (name == "string"sv) {
+ info_command->m_string = TRY(String::join(' ', tokens.span().slice(i)));
+ break;
+ } else if (name == "refutation"sv) {
+ info_command->m_refutation = TRY(parse_line(tokens.span().slice(i)));
+ break;
+ } else if (name == "currline"sv) {
+ info_command->m_currline = TRY(parse_line(tokens.span().slice(i)));
+ break;
+ } else {
+ return Error::from_string_literal("Unknown info type");
+ }
+ }
+
+ return info_command;
}
ErrorOr<String> InfoCommand::to_string() const
{
- // FIXME: Implement this.
- VERIFY_NOT_REACHED();
+ StringBuilder builder;
+
+ auto append_moves = [&](Vector<Chess::Move> const& moves) -> ErrorOr<void> {
+ bool first = true;
+ for (auto const& move : moves) {
+ if (!first)
+ TRY(builder.try_append(' '));
+
+ first = false;
+ TRY(builder.try_append(TRY(move.to_long_algebraic())));
+ }
+ return {};
+ };
+
+ TRY(builder.try_append("info"sv));
+ if (m_depth.has_value())
+ TRY(builder.try_appendff(" depth {}", m_depth.value()));
+ if (m_seldepth.has_value())
+ TRY(builder.try_appendff(" seldepth {}", m_seldepth.value()));
+ if (m_time.has_value())
+ TRY(builder.try_appendff(" time {}", m_time.value()));
+ if (m_nodes.has_value())
+ TRY(builder.try_appendff(" nodes {}", m_nodes.value()));
+ if (m_multipv.has_value())
+ TRY(builder.try_appendff(" multipv {}", m_multipv.value()));
+ if (m_score.has_value()) {
+ TRY(builder.try_append(" score"sv));
+ switch (m_score->type) {
+ case ScoreType::Centipawns:
+ TRY(builder.try_append(" cp"sv));
+ break;
+ case ScoreType::Mate:
+ TRY(builder.try_append(" mate"sv));
+ break;
+ }
+
+ TRY(builder.try_appendff(" {}", m_score->value));
+
+ switch (m_score->bound) {
+ case ScoreBound::None:
+ break;
+ case ScoreBound::Lower:
+ TRY(builder.try_append(" lowerbound"sv));
+ break;
+ case ScoreBound::Upper:
+ TRY(builder.try_append(" upperbound"sv));
+ break;
+ }
+ }
+ if (m_currmove.has_value())
+ TRY(builder.try_appendff(" currmove {}", TRY(m_currmove->to_long_algebraic())));
+ if (m_currmovenumber.has_value())
+ TRY(builder.try_appendff(" currmovenumber {}", m_currmovenumber.value()));
+ if (m_hashfull.has_value())
+ TRY(builder.try_appendff(" hashfull {}", m_hashfull.value()));
+ if (m_nps.has_value())
+ TRY(builder.try_appendff(" nps {}", m_nps.value()));
+ if (m_tbhits.has_value())
+ TRY(builder.try_appendff(" tbhits {}", m_tbhits.value()));
+ if (m_cpuload.has_value())
+ TRY(builder.try_appendff(" cpuload {}", m_cpuload.value()));
+ if (m_string.has_value())
+ TRY(builder.try_appendff(" string {}", m_string.value()));
+ if (m_pv.has_value()) {
+ TRY(builder.try_append(" pv "sv));
+ TRY(append_moves(m_pv.value()));
+ }
+ if (m_refutation.has_value()) {
+ TRY(builder.try_append(" refutation "sv));
+ TRY(append_moves(m_refutation.value()));
+ }
+ if (m_currline.has_value()) {
+ TRY(builder.try_append(" currline "sv));
+ TRY(append_moves(m_currline.value()));
+ }
+
+ return builder.to_string();
}
ErrorOr<NonnullOwnPtr<QuitCommand>> QuitCommand::from_string(StringView command)
diff --git a/Userland/Libraries/LibChess/UCICommand.h b/Userland/Libraries/LibChess/UCICommand.h
index e6e704d823..318fb58f2c 100644
--- a/Userland/Libraries/LibChess/UCICommand.h
+++ b/Userland/Libraries/LibChess/UCICommand.h
@@ -1,6 +1,7 @@
/*
* Copyright (c) 2020-2022, the SerenityOS developers.
* Copyright (c) 2023, Sam Atkins <atkinssj@serenityos.org>
+ * Copyright (c) 2023, Tim Ledbetter <timledbetter@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -246,6 +247,23 @@ private:
class InfoCommand : public Command {
public:
+ enum class ScoreType {
+ Centipawns,
+ Mate
+ };
+
+ enum class ScoreBound {
+ None,
+ Lower,
+ Upper
+ };
+
+ struct Score {
+ ScoreType type;
+ int value;
+ ScoreBound bound;
+ };
+
explicit InfoCommand()
: Command(Command::Type::Info)
{
@@ -255,18 +273,23 @@ public:
virtual ErrorOr<String> to_string() const override;
- Optional<int> depth;
- Optional<int> seldepth;
- Optional<int> time;
- Optional<int> nodes;
- Optional<Vector<Chess::Move>> pv;
- // FIXME: Add multipv.
- Optional<int> score_cp;
- Optional<int> score_mate;
- // FIXME: Add score bounds.
- Optional<Chess::Move> currmove;
- Optional<int> currmove_number;
- // FIXME: Add additional fields.
+private:
+ Optional<int> m_depth;
+ Optional<int> m_seldepth;
+ Optional<int> m_time;
+ Optional<int> m_nodes;
+ Optional<Vector<Chess::Move>> m_pv;
+ Optional<int> m_multipv;
+ Optional<Score> m_score;
+ Optional<Chess::Move> m_currmove;
+ Optional<int> m_currmovenumber;
+ Optional<int> m_hashfull;
+ Optional<int> m_nps;
+ Optional<int> m_tbhits;
+ Optional<int> m_cpuload;
+ Optional<String> m_string;
+ Optional<Vector<Chess::Move>> m_refutation;
+ Optional<Vector<Chess::Move>> m_currline;
};
class QuitCommand : public Command {