mirror of
https://github.com/sockspls/badfish
synced 2025-04-30 08:43:09 +00:00

print `<empty>` and accept `<empty>` for UCI string options, accepting empty strings as well. Internally use empty strings (`""`). closes https://github.com/official-stockfish/Stockfish/pull/5474 No functional change
211 lines
5.9 KiB
C++
211 lines
5.9 KiB
C++
/*
|
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
|
Copyright (C) 2004-2024 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "ucioption.h"
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <cctype>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <utility>
|
|
|
|
#include "misc.h"
|
|
|
|
namespace Stockfish {
|
|
|
|
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
|
|
sync_cout << "No such option: " << name << sync_endl;
|
|
}
|
|
|
|
Option OptionsMap::operator[](const std::string& name) const {
|
|
auto it = options_map.find(name);
|
|
return it != options_map.end() ? it->second : Option(this);
|
|
}
|
|
|
|
Option& OptionsMap::operator[](const std::string& name) {
|
|
if (!options_map.count(name))
|
|
options_map[name] = Option(this);
|
|
return options_map[name];
|
|
}
|
|
|
|
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(double 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); }
|
|
|
|
|
|
// Inits options and assigns idx in the correct printing order
|
|
|
|
void Option::operator<<(const Option& o) {
|
|
|
|
static size_t insert_order = 0;
|
|
|
|
auto p = this->parent;
|
|
*this = o;
|
|
|
|
this->parent = p;
|
|
idx = insert_order++;
|
|
}
|
|
|
|
// 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::stof(v) < min || std::stof(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[token] << Option();
|
|
if (!comboMap.count(v) || v == "var")
|
|
return *this;
|
|
}
|
|
|
|
if (type == "string")
|
|
currentValue = v == "<empty>" ? "" : 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() ? "<empty>" : o.defaultValue;
|
|
os << " default " << defaultValue;
|
|
}
|
|
|
|
else if (o.type == "spin")
|
|
os << " default " << int(stof(o.defaultValue)) << " min " << o.min << " max "
|
|
<< o.max;
|
|
|
|
break;
|
|
}
|
|
|
|
return os;
|
|
}
|
|
}
|