/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include "ucioption.h"
#include
#include
#include
#include
#include
#include
#include
namespace engine {
bool CaseInsensitiveLess::operator()(const std::string &s1,
const std::string &s2) const {
return std::lexicographical_compare(
s1.begin(), s1.end(), s2.begin(), s2.end(),
[](char c1, char c2) { return std::tolower(c1) < std::tolower(c2); });
}
void OptionsMap::add_info_listener(InfoListener &&message_func) {
info = std::move(message_func);
}
void OptionsMap::setoption(std::istringstream &is) {
std::string token, name, value;
is >> token; // Consume the "name" token
// Read the option name (can contain spaces)
while (is >> token && token != "value")
name += (name.empty() ? "" : " ") + token;
// Read the option value (can contain spaces)
while (is >> token)
value += (value.empty() ? "" : " ") + token;
if (options_map.count(name))
options_map[name] = value;
else
std::cerr << "No such option: " << name << std::endl;
}
const Option &OptionsMap::operator[](const std::string &name) const {
auto it = options_map.find(name);
assert(it != options_map.end());
return it->second;
}
// Inits options and assigns idx in the correct printing order
void OptionsMap::add(const std::string &name, const Option &option) {
if (!options_map.count(name)) {
static size_t insert_order = 0;
options_map[name] = option;
options_map[name].parent = this;
options_map[name].idx = insert_order++;
} else {
std::cerr << "Option \"" << name << "\" was already added!" << std::endl;
std::exit(EXIT_FAILURE);
}
}
std::size_t OptionsMap::count(const std::string &name) const {
return options_map.count(name);
}
Option::Option(const OptionsMap *map) : parent(map) {}
Option::Option(const char *v, OnChange f)
: type("string"), min(0), max(0), on_change(std::move(f)) {
defaultValue = currentValue = v;
}
Option::Option(bool v, OnChange f)
: type("check"), min(0), max(0), on_change(std::move(f)) {
defaultValue = currentValue = (v ? "true" : "false");
}
Option::Option(OnChange f)
: type("button"), min(0), max(0), on_change(std::move(f)) {}
Option::Option(int v, int minv, int maxv, OnChange f)
: type("spin"), min(minv), max(maxv), on_change(std::move(f)) {
defaultValue = currentValue = std::to_string(v);
}
Option::Option(const char *v, const char *cur, OnChange f)
: type("combo"), min(0), max(0), on_change(std::move(f)) {
defaultValue = v;
currentValue = cur;
}
Option::operator int() const {
assert(type == "check" || type == "spin");
return (type == "spin" ? std::stoi(currentValue) : currentValue == "true");
}
Option::operator std::string() const {
assert(type == "string");
return currentValue;
}
bool Option::operator==(const char *s) const {
assert(type == "combo");
return !CaseInsensitiveLess()(currentValue, s) &&
!CaseInsensitiveLess()(s, currentValue);
}
bool Option::operator!=(const char *s) const { return !(*this == s); }
// Updates currentValue and triggers on_change() action. It's up to
// the GUI to check for option's limits, but we could receive the new value
// from the user by console window, so let's check the bounds anyway.
Option &Option::operator=(const std::string &v) {
assert(!type.empty());
if ((type != "button" && type != "string" && v.empty()) ||
(type == "check" && v != "true" && v != "false") ||
(type == "spin" && (std::stoi(v) < min || std::stoi(v) > max)))
return *this;
if (type == "combo") {
OptionsMap comboMap; // To have case insensitive compare
std::string token;
std::istringstream ss(defaultValue);
while (ss >> token)
comboMap.add(token, Option());
if (!comboMap.count(v) || v == "var")
return *this;
}
if (type == "string")
currentValue = v == "" ? "" : v;
else if (type != "button")
currentValue = v;
if (on_change) {
const auto ret = on_change(*this);
if (ret && parent != nullptr && parent->info != nullptr)
parent->info(ret);
}
return *this;
}
std::ostream &operator<<(std::ostream &os, const OptionsMap &om) {
for (size_t idx = 0; idx < om.options_map.size(); ++idx)
for (const auto &it : om.options_map)
if (it.second.idx == idx) {
const Option &o = it.second;
os << "\noption name " << it.first << " type " << o.type;
if (o.type == "check" || o.type == "combo")
os << " default " << o.defaultValue;
else if (o.type == "string") {
std::string defaultValue =
o.defaultValue.empty() ? "" : o.defaultValue;
os << " default " << defaultValue;
}
else if (o.type == "spin")
os << " default " << stoi(o.defaultValue) << " min " << o.min
<< " max " << o.max;
break;
}
return os;
}
} // namespace engine