From: Fabian Fichter Date: Fri, 30 Aug 2019 17:22:30 +0000 (+0200) Subject: Support reading variants from configuration file X-Git-Url: http://winboard.nl/cgi-bin?a=commitdiff_plain;h=772eb6caef808de9a53bb1a6a952afba87bceb91;p=fairystockfish.git Support reading variants from configuration file Read variants from INI-style configuration file in order to support user-defined variants without requiring recompilation. --- diff --git a/Readme.md b/Readme.md index 5733f03..ad0cb0e 100644 --- a/Readme.md +++ b/Readme.md @@ -1,25 +1,29 @@ -## Fairy-Stockfish +# Fairy-Stockfish + +## Overview [![Build Status](https://travis-ci.org/ianfab/Fairy-Stockfish.svg?branch=master)](https://travis-ci.org/ianfab/Fairy-Stockfish) [![Build Status](https://ci.appveyor.com/api/projects/status/github/ianfab/Fairy-Stockfish?branch=master&svg=true)](https://ci.appveyor.com/project/ianfab/Fairy-Stockfish/branch/master) Fairy-Stockfish is a Stockfish fork designed for the support of (fairy) chess variants and to make the addition/configuration of new variants as simple and flexible as possible. The goal of the project is to create an engine supporting a large variety of chess-like games, equipped with the powerful search of Stockfish. It is complementary to Stockfish forks more specialized for certain chess variants, such as [multi-variant Stockfish](https://github.com/ddugovic/Stockfish), [Seirawan-Stockfish](https://github.com/ianfab/Seirawan-Stockfish), [Makruk-Stockfish](https://github.com/ianfab/Makruk-Stockfish), etc., supporting more variants with the tradeoff of slightly lower performance. -Besides chess, the currently supported games are: +## Supported games + +The games that are currently supported besides chess are listed below. Fairy-Stockfish can also play user-defined variants loaded via a variant configuration file, see the file `src/variants.ini`. -**Regional and historical games** +### Regional and historical games - [Shatranj](https://en.wikipedia.org/wiki/Shatranj), [Courier](https://en.wikipedia.org/wiki/Courier_chess) - [Makruk](https://en.wikipedia.org/wiki/Makruk), [ASEAN](http://hgm.nubati.net/rules/ASEAN.html), Ai-Wok - [Sittuyin](https://en.wikipedia.org/wiki/Sittuyin) - [Shatar](https://en.wikipedia.org/wiki/Shatar), [Jeson Mor](https://en.wikipedia.org/wiki/Jeson_Mor) - [Shogi](https://en.wikipedia.org/wiki/Shogi) -**Chess variants** +### Chess variants - [Capablanca](https://en.wikipedia.org/wiki/Capablanca_Chess), [Janus](https://en.wikipedia.org/wiki/Janus_Chess), [Modern](https://en.wikipedia.org/wiki/Modern_Chess_(chess_variant)), [Chancellor](https://en.wikipedia.org/wiki/Chancellor_Chess), [Embassy](https://en.wikipedia.org/wiki/Embassy_Chess), [Gothic](https://www.chessvariants.com/large.dir/gothicchess.html), [Capablanca random chess](https://en.wikipedia.org/wiki/Capablanca_Random_Chess) - [Grand](https://en.wikipedia.org/wiki/Grand_Chess), [Shako](https://www.chessvariants.com/large.dir/shako.html) - [Chess960](https://en.wikipedia.org/wiki/Chess960), [Placement/Pre-Chess](https://www.chessvariants.com/link/placement-chess) -- [Crazyhouse](https://en.wikipedia.org/wiki/Crazyhouse), [Loop](https://en.wikipedia.org/wiki/Crazyhouse#Variations), [Chessgi](https://en.wikipedia.org/wiki/Crazyhouse#Variations), [Pocket Knight](http://www.chessvariants.com/other.dir/pocket.html) -- [Seirawan](https://en.wikipedia.org/wiki/Seirawan_chess) +- [Crazyhouse](https://en.wikipedia.org/wiki/Crazyhouse), [Loop](https://en.wikipedia.org/wiki/Crazyhouse#Variations), [Chessgi](https://en.wikipedia.org/wiki/Crazyhouse#Variations), [Pocket Knight](http://www.chessvariants.com/other.dir/pocket.html), Capablanca-Crazyhouse +- [Seirawan](https://en.wikipedia.org/wiki/Seirawan_chess), Seirawan-Crazyhouse - [Amazon](https://en.wikipedia.org/wiki/Amazon_(chess)), [Chigorin](https://en.wikipedia.org/wiki/Chigorin_Chess), [Almost chess](https://en.wikipedia.org/wiki/Almost_Chess), [Hoppel-Poppel](http://www.chessvariants.com/diffmove.dir/hoppel-poppel.html) - [Antichess](https://lichess.org/variant/antichess), [Giveaway](http://www.chessvariants.com/diffobjective.dir/giveaway.old.html), [Losers](https://www.chessclub.com/help/Wild17), [Codrus](http://www.binnewirtz.com/Schlagschach1.htm) - [Extinction](https://en.wikipedia.org/wiki/Extinction_chess), [Kinglet](https://en.wikipedia.org/wiki/V._R._Parton#Kinglet_Chess) @@ -28,20 +32,44 @@ Besides chess, the currently supported games are: - [Los Alamos](https://en.wikipedia.org/wiki/Los_Alamos_chess) - [Horde](https://en.wikipedia.org/wiki/Dunsany%27s_Chess#Horde_Chess) -**Shogi variants** +### Shogi variants - [Minishogi](https://en.wikipedia.org/wiki/Minishogi), [EuroShogi](https://en.wikipedia.org/wiki/EuroShogi), [Judkins shogi](https://en.wikipedia.org/wiki/Judkins_shogi) - [Kyoto shogi](https://en.wikipedia.org/wiki/Kyoto_shogi), [Microshogi](https://en.wikipedia.org/wiki/Micro_shogi) - [Dobutsu shogi](https://en.wikipedia.org/wiki/Dōbutsu_shōgi), [Goro goro shogi](https://en.wikipedia.org/wiki/D%C5%8Dbutsu_sh%C5%8Dgi#Variation) -**Related games** +### Related games - [Breakthrough](https://en.wikipedia.org/wiki/Breakthrough_(board_game)) - [Clobber](https://en.wikipedia.org/wiki/Clobber) -- [Connect4](https://en.wikipedia.org/wiki/Connect_Four), [Tic-Tac-Toe](https://en.wikipedia.org/wiki/Tic-tac-toe) -See the [Fairy-Stockfish Wiki](https://github.com/ianfab/Fairy-Stockfish/wiki) for more info. +## UCI parameters + +The following UCI options are added or changed compared to official Stockfish: + + * #### UCI_Variant + The most important option, since it allows to set the variant that is going to be played. + Most GUIs for chess variants set this option automatically if you select a variant in the GUI, + but in case of usage via CLI or via Shogi GUIs the option needs to be set manually. + + * #### UCI_Chess960 + Used as a universal flag to switch between Chess960-style shuffling variants (such as Crazyhouse960, + S-Chess960/2880, Capablanca Random Chess, etc.) and the respective normal variant. + For variants without castling, this option does not have any effect. + + * #### Protocol + Can be used to switch between dialects of the UCI protocol, namely UCI and USI (for Shogi). + This option is automatically set to the respective protocol if a GUI starts communication + with the "uci" or "usi" command (as required by the protocols). + + * #### VariantPath + The path to the configuration file for user-defined variants. + Alternatively, the "load" command can be used. + +## Help + +See the [Fairy-Stockfish Wiki](https://github.com/ianfab/Fairy-Stockfish/wiki) for more info, or if the required information is not available, open an [issue](https://github.com/ianfab/Fairy-Stockfish/issues). -## Stockfish -### Overview +# Stockfish +## Overview [![Build Status](https://travis-ci.org/official-stockfish/Stockfish.svg?branch=master)](https://travis-ci.org/official-stockfish/Stockfish) [![Build Status](https://ci.appveyor.com/api/projects/status/github/official-stockfish/Stockfish?branch=master&svg=true)](https://ci.appveyor.com/project/mcostalba/stockfish/branch/master) diff --git a/src/Makefile b/src/Makefile index 4411293..ce35fbc 100644 --- a/src/Makefile +++ b/src/Makefile @@ -37,7 +37,7 @@ PGOBENCH = ./$(EXE) bench ### Object files OBJS = benchmark.o bitbase.o bitboard.o endgame.o evaluate.o main.o \ - material.o misc.o movegen.o movepick.o pawns.o piece.o position.o psqt.o \ + material.o misc.o movegen.o movepick.o parser.o pawns.o piece.o position.o psqt.o \ search.o thread.o timeman.o tt.o uci.o ucioption.o variant.o syzygy/tbprobe.o ### Establish the operating system name diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 08ab3c5..7e0acb4 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -525,7 +525,7 @@ namespace { } } - if (pos.max_check_count()) + if (pos.check_counting()) kingDanger *= 2; // Unsafe or occupied checking squares will also be considered, as long as @@ -543,13 +543,13 @@ namespace { int kingFlankAttacks = popcount(b1) + popcount(b2); kingDanger += kingAttackersCount[Them] * kingAttackersWeight[Them] - + 69 * kingAttacksCount[Them] * (2 + 8 * !!pos.max_check_count() + pos.captures_to_hand()) / 2 - + 185 * popcount(kingRing[Us] & weak) * (1 + pos.captures_to_hand() + !!pos.max_check_count()) + + 69 * kingAttacksCount[Them] * (2 + 8 * pos.check_counting() + pos.captures_to_hand()) / 2 + + 185 * popcount(kingRing[Us] & weak) * (1 + pos.captures_to_hand() + pos.check_counting()) - 100 * bool(attackedBy[Us][KNIGHT] & attackedBy[Us][KING]) - 35 * bool(attackedBy[Us][BISHOP] & attackedBy[Us][KING]) - 10 * bool(attackedBy2[Us] & attackedBy[Us][KING]) * pos.captures_to_hand() + 150 * popcount(pos.blockers_for_king(Us) | unsafeChecks) - - 873 * !(pos.major_pieces(Them) || pos.captures_to_hand()) / (1 + !!pos.max_check_count()) + - 873 * !(pos.major_pieces(Them) || pos.captures_to_hand()) / (1 + pos.check_counting()) - 6 * mg_value(score) / 8 + mg_value(mobility[Them] - mobility[Us]) + 5 * kingFlankAttacks * kingFlankAttacks / 16 @@ -564,9 +564,9 @@ namespace { score -= PawnlessFlank; // King tropism bonus, to anticipate slow motion attacks on our king - score -= FlankAttacks * kingFlankAttacks * (1 + 5 * pos.captures_to_hand() + !!pos.max_check_count()); + score -= FlankAttacks * kingFlankAttacks * (1 + 5 * pos.captures_to_hand() + pos.check_counting()); - if (pos.max_check_count()) + if (pos.check_counting()) score += make_score(0, mg_value(score) / 2); // For drop games, king danger is independent of game phase @@ -902,9 +902,9 @@ namespace { } // nCheck - if (pos.max_check_count()) + if (pos.check_counting()) { - int remainingChecks = pos.max_check_count() - pos.checks_given(Us); + int remainingChecks = pos.checks_remaining(Us); assert(remainingChecks > 0); score += make_score(3600, 1000) / (remainingChecks * remainingChecks); } diff --git a/src/parser.cpp b/src/parser.cpp new file mode 100644 index 0000000..e4c5f70 --- /dev/null +++ b/src/parser.cpp @@ -0,0 +1,208 @@ +/* + Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish + Copyright (C) 2018-2019 Fabian Fichter + + Fairy-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. + + Fairy-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 +#include +#include +#include + +#include "parser.h" +#include "piece.h" +#include "types.h" + +namespace { + + template void set(const std::string& value, T& target) + { + std::stringstream ss(value); + ss >> target; + } + + template <> void set(const std::string& value, Rank& target) { + std::stringstream ss(value); + int i; + ss >> i; + target = Rank(i - 1); + } + + template <> void set(const std::string& value, File& target) { + std::stringstream ss(value); + int i; + ss >> i; + target = File(i - 1); + } + + template <> void set(const std::string& value, std::string& target) { + target = value; + } + + template <> void set(const std::string& value, bool& target) { + target = value == "true"; + } + + template <> void set(const std::string& value, Value& target) { + target = value == "win" ? VALUE_MATE + : value == "loss" ? -VALUE_MATE + : VALUE_DRAW; + } + + template <> void set(const std::string& value, CountingRule& target) { + target = value == "makruk" ? MAKRUK_COUNTING + : value == "asean" ? ASEAN_COUNTING + : NO_COUNTING; + } + + template <> void set(const std::string& value, Bitboard& target) { + char token, token2; + std::stringstream ss(value); + target = 0; + while (ss >> token && ss >> token2) + target |= make_square(File(tolower(token) - 'a'), Rank(token2 - '1')); + } + +} // namespace + +template void VariantParser::parse_attribute(const std::string& key, T& target) { + const auto& it = config.find(key); + if (it != config.end()) + set(it->second, target); +} + +Variant* VariantParser::parse() { + Variant* v = new Variant(); + v->reset_pieces(); + v->promotionPieceTypes = {}; + return parse(v); +} + +Variant* VariantParser::parse(Variant* v) { + // piece types + for (const auto& pieceInfo : pieceMap) + { + const auto& keyValue = config.find(pieceInfo.second->name); + if (keyValue != config.end() && !keyValue->second.empty()) + v->add_piece(pieceInfo.first, keyValue->second.at(0)); + } + parse_attribute("variantTemplate", v->variantTemplate); + parse_attribute("pocketSize", v->pocketSize); + parse_attribute("maxRank", v->maxRank); + parse_attribute("maxFile", v->maxFile); + parse_attribute("chess960", v->chess960); + parse_attribute("startFen", v->startFen); + parse_attribute("promotionRank", v->promotionRank); + // promotion piece types + const auto& it_prom = config.find("promotionPieceTypes"); + if (it_prom != config.end()) + { + char token; + size_t idx; + std::stringstream ss(it_prom->second); + while (ss >> token && ((idx = v->pieceToChar.find(token)) != std::string::npos)) + v->promotionPieceTypes.insert(PieceType(idx)); + } + parse_attribute("sittuyinPromotion", v->sittuyinPromotion); + // promotion limit + const auto& it_prom_limit = config.find("promotionLimit"); + if (it_prom_limit != config.end()) + { + char token; + size_t idx; + std::stringstream ss(it_prom_limit->second); + while (ss >> token && (idx = v->pieceToChar.find(toupper(token))) != std::string::npos && ss >> token && ss >> v->promotionLimit[idx]) {} + } + // promoted piece types + const auto& it_prom_pt = config.find("promotedPieceType"); + if (it_prom_pt != config.end()) + { + char token; + size_t idx, idx2; + std::stringstream ss(it_prom_pt->second); + while ( ss >> token && (idx = v->pieceToChar.find(toupper(token))) != std::string::npos && ss >> token + && ss >> token && (idx2 = v->pieceToChar.find(toupper(token))) != std::string::npos) + v->promotedPieceType[idx] = PieceType(idx2); + } + parse_attribute("piecePromotionOnCapture", v->piecePromotionOnCapture); + parse_attribute("mandatoryPawnPromotion", v->mandatoryPawnPromotion); + parse_attribute("mandatoryPiecePromotion", v->mandatoryPiecePromotion); + parse_attribute("pieceDemotion", v->pieceDemotion); + parse_attribute("endgameEval", v->endgameEval); + parse_attribute("doubleStep", v->doubleStep); + parse_attribute("doubleStepRank", v->doubleStepRank); + parse_attribute("firstRankDoubleSteps", v->firstRankDoubleSteps); + parse_attribute("castling", v->castling); + parse_attribute("castlingDroppedPiece", v->castlingDroppedPiece); + parse_attribute("castlingKingsideFile", v->castlingKingsideFile); + parse_attribute("castlingQueensideFile", v->castlingQueensideFile); + parse_attribute("castlingRank", v->castlingRank); + parse_attribute("checking", v->checking); + parse_attribute("mustCapture", v->mustCapture); + parse_attribute("mustDrop", v->mustDrop); + parse_attribute("pieceDrops", v->pieceDrops); + parse_attribute("dropLoop", v->dropLoop); + parse_attribute("capturesToHand", v->capturesToHand); + parse_attribute("firstRankDrops", v->firstRankDrops); + parse_attribute("dropOnTop", v->dropOnTop); + parse_attribute("whiteDropRegion", v->whiteDropRegion); + parse_attribute("blackDropRegion", v->blackDropRegion); + parse_attribute("sittuyinRookDrop", v->sittuyinRookDrop); + parse_attribute("dropOppositeColoredBishop", v->dropOppositeColoredBishop); + parse_attribute("dropPromoted", v->dropPromoted); + parse_attribute("shogiDoubledPawn", v->shogiDoubledPawn); + parse_attribute("immobilityIllegal", v->immobilityIllegal); + parse_attribute("gating", v->gating); + // game end + parse_attribute("nMoveRule", v->nMoveRule); + parse_attribute("nFoldRule", v->nFoldRule); + parse_attribute("nFoldValue", v->nFoldValue); + parse_attribute("nFoldValueAbsolute", v->nFoldValueAbsolute); + parse_attribute("perpetualCheckIllegal", v->perpetualCheckIllegal); + parse_attribute("stalemateValue", v->stalemateValue); + parse_attribute("checkmateValue", v->checkmateValue); + parse_attribute("shogiPawnDropMateIllegal", v->shogiPawnDropMateIllegal); + parse_attribute("shatarMateRule", v->shatarMateRule); + parse_attribute("bareKingValue", v->bareKingValue); + parse_attribute("extinctionValue", v->extinctionValue); + parse_attribute("bareKingMove", v->bareKingMove); + // extinction piece types + const auto& it_ext = config.find("extinctionPieceTypes"); + if (it_ext != config.end()) + { + char token; + size_t idx; + std::stringstream ss(it_ext->second); + while (ss >> token && ((idx = v->pieceToChar.find(token)) != std::string::npos || token == '*')) + v->extinctionPieceTypes.insert(PieceType(token == '*' ? 0 : idx)); + } + // flag piece type + const auto& it_flag_pt = config.find("flagPiece"); + if (it_flag_pt != config.end()) + { + char token; + size_t idx; + std::stringstream ss(it_flag_pt->second); + if (ss >> token && (idx = v->pieceToChar.find(token)) != std::string::npos) + v->flagPiece = PieceType(idx); + } + parse_attribute("whiteFlag", v->whiteFlag); + parse_attribute("blackFlag", v->blackFlag); + parse_attribute("flagMove", v->flagMove); + parse_attribute("checkCounting", v->checkCounting); + parse_attribute("connectN", v->connectN); + parse_attribute("countingRule", v->countingRule); + return v; +} diff --git a/src/parser.h b/src/parser.h new file mode 100644 index 0000000..2cf7901 --- /dev/null +++ b/src/parser.h @@ -0,0 +1,35 @@ +/* + Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish + Copyright (C) 2018-2019 Fabian Fichter + + Fairy-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. + + Fairy-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 . +*/ + +#ifndef PARSER_H_INCLUDED +#define PARSER_H_INCLUDED + +#include "variant.h" + +class VariantParser { +public: + VariantParser(const std::map& c) : config (c) {}; + Variant* parse(); + Variant* parse(Variant* v); + +private: + std::map config; + template void parse_attribute(const std::string& key, T& target); +}; + +#endif // #ifndef PARSER_H_INCLUDED \ No newline at end of file diff --git a/src/piece.cpp b/src/piece.cpp index aea82e0..9699517 100644 --- a/src/piece.cpp +++ b/src/piece.cpp @@ -33,12 +33,14 @@ void PieceInfo::merge(const PieceInfo* pi) { namespace { PieceInfo* pawn_piece() { PieceInfo* p = new PieceInfo(); + p->name = "pawn"; p->stepsQuiet = {NORTH}; p->stepsCapture = {NORTH_WEST, NORTH_EAST}; return p; } PieceInfo* knight_piece() { PieceInfo* p = new PieceInfo(); + p->name = "knight"; p->stepsQuiet = {2 * SOUTH + WEST, 2 * SOUTH + EAST, SOUTH + 2 * WEST, SOUTH + 2 * EAST, NORTH + 2 * WEST, NORTH + 2 * EAST, 2 * NORTH + WEST, 2 * NORTH + EAST }; p->stepsCapture = {2 * SOUTH + WEST, 2 * SOUTH + EAST, SOUTH + 2 * WEST, SOUTH + 2 * EAST, @@ -47,48 +49,61 @@ namespace { } PieceInfo* bishop_piece() { PieceInfo* p = new PieceInfo(); + p->name = "bishop"; p->sliderQuiet = {NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST}; p->sliderCapture = {NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST}; return p; } PieceInfo* rook_piece() { PieceInfo* p = new PieceInfo(); + p->name = "rook"; p->sliderQuiet = {NORTH, EAST, SOUTH, WEST}; p->sliderCapture = {NORTH, EAST, SOUTH, WEST}; return p; } PieceInfo* queen_piece() { PieceInfo* p = new PieceInfo(); + p->name = "queen"; p->sliderQuiet = {NORTH, EAST, SOUTH, WEST, NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST}; p->sliderCapture = {NORTH, EAST, SOUTH, WEST, NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST}; return p; } PieceInfo* king_piece() { PieceInfo* p = new PieceInfo(); + p->name = "king"; p->stepsQuiet = {SOUTH_WEST, SOUTH, SOUTH_EAST, WEST, EAST, NORTH_WEST, NORTH, NORTH_EAST}; p->stepsCapture = {SOUTH_WEST, SOUTH, SOUTH_EAST, WEST, EAST, NORTH_WEST, NORTH, NORTH_EAST}; return p; } + PieceInfo* commoner_piece() { + PieceInfo* p = king_piece(); + p->name = "commoner"; + return p; + } PieceInfo* fers_piece() { PieceInfo* p = new PieceInfo(); + p->name = "fers"; p->stepsQuiet = {SOUTH_WEST, SOUTH_EAST, NORTH_WEST, NORTH_EAST}; p->stepsCapture = {SOUTH_WEST, SOUTH_EAST, NORTH_WEST, NORTH_EAST}; return p; } PieceInfo* wazir_piece() { PieceInfo* p = new PieceInfo(); + p->name = "wazir"; p->stepsQuiet = {SOUTH, WEST, EAST, NORTH}; p->stepsCapture = {SOUTH, WEST, EAST, NORTH}; return p; } PieceInfo* alfil_piece() { PieceInfo* p = new PieceInfo(); + p->name = "alfil"; p->stepsQuiet = {2 * SOUTH_WEST, 2 * SOUTH_EAST, 2 * NORTH_WEST, 2 * NORTH_EAST}; p->stepsCapture = {2 * SOUTH_WEST, 2 * SOUTH_EAST, 2 * NORTH_WEST, 2 * NORTH_EAST}; return p; } PieceInfo* fers_alfil_piece() { PieceInfo* p = fers_piece(); + p->name = "fers_alfil"; PieceInfo* p2 = alfil_piece(); p->merge(p2); delete p2; @@ -96,12 +111,14 @@ namespace { } PieceInfo* silver_piece() { PieceInfo* p = new PieceInfo(); + p->name = "silver"; p->stepsQuiet = {SOUTH_WEST, SOUTH_EAST, NORTH_WEST, NORTH, NORTH_EAST}; p->stepsCapture = {SOUTH_WEST, SOUTH_EAST, NORTH_WEST, NORTH, NORTH_EAST}; return p; } PieceInfo* aiwok_piece() { PieceInfo* p = rook_piece(); + p->name = "aiwok"; PieceInfo* p2 = knight_piece(); PieceInfo* p3 = fers_piece(); p->merge(p2); @@ -112,6 +129,7 @@ namespace { } PieceInfo* bers_piece() { PieceInfo* p = rook_piece(); + p->name = "bers"; PieceInfo* p2 = fers_piece(); p->merge(p2); delete p2; @@ -119,6 +137,7 @@ namespace { } PieceInfo* archbishop_piece() { PieceInfo* p = bishop_piece(); + p->name = "archbishop"; PieceInfo* p2 = knight_piece(); p->merge(p2); delete p2; @@ -126,6 +145,7 @@ namespace { } PieceInfo* chancellor_piece() { PieceInfo* p = rook_piece(); + p->name = "chancellor"; PieceInfo* p2 = knight_piece(); p->merge(p2); delete p2; @@ -133,6 +153,7 @@ namespace { } PieceInfo* amazon_piece() { PieceInfo* p = queen_piece(); + p->name = "amazon"; PieceInfo* p2 = knight_piece(); p->merge(p2); delete p2; @@ -140,6 +161,7 @@ namespace { } PieceInfo* knibis_piece() { PieceInfo* p = bishop_piece(); + p->name = "knibis"; PieceInfo* p2 = knight_piece(); p->merge(p2); delete p2; @@ -149,6 +171,7 @@ namespace { } PieceInfo* biskni_piece() { PieceInfo* p = bishop_piece(); + p->name = "biskni"; PieceInfo* p2 = knight_piece(); p->merge(p2); delete p2; @@ -158,24 +181,28 @@ namespace { } PieceInfo* shogi_pawn_piece() { PieceInfo* p = new PieceInfo(); + p->name = "shogi_pawn"; p->stepsQuiet = {NORTH}; p->stepsCapture = {NORTH}; return p; } PieceInfo* lance_piece() { PieceInfo* p = new PieceInfo(); + p->name = "lance"; p->sliderQuiet = {NORTH}; p->sliderCapture = {NORTH}; return p; } PieceInfo* shogi_knight_piece() { PieceInfo* p = new PieceInfo(); + p->name = "shogi_knight"; p->stepsQuiet = {2 * NORTH + WEST, 2 * NORTH + EAST}; p->stepsCapture = {2 * NORTH + WEST, 2 * NORTH + EAST}; return p; } PieceInfo* euroshogi_knight_piece() { PieceInfo* p = shogi_knight_piece(); + p->name = "euroshogi_knight"; p->stepsQuiet.push_back(WEST); p->stepsQuiet.push_back(EAST); p->stepsCapture.push_back(WEST); @@ -184,12 +211,14 @@ namespace { } PieceInfo* gold_piece() { PieceInfo* p = new PieceInfo(); + p->name = "gold"; p->stepsQuiet = {SOUTH, WEST, EAST, NORTH_WEST, NORTH, NORTH_EAST}; p->stepsCapture = {SOUTH, WEST, EAST, NORTH_WEST, NORTH, NORTH_EAST}; return p; } PieceInfo* horse_piece() { PieceInfo* p = bishop_piece(); + p->name = "horse"; PieceInfo* p2 = wazir_piece(); p->merge(p2); delete p2; @@ -197,21 +226,25 @@ namespace { } PieceInfo* clobber_piece() { PieceInfo* p = wazir_piece(); + p->name = "clobber"; p->stepsQuiet = {}; return p; } PieceInfo* breakthrough_piece() { PieceInfo* p = pawn_piece(); + p->name = "breakthrough"; p->stepsQuiet.push_back(NORTH_WEST); p->stepsQuiet.push_back(NORTH_EAST); return p; } PieceInfo* immobile_piece() { PieceInfo* p = new PieceInfo(); + p->name = "immobile"; return p; } PieceInfo* cannon_piece() { PieceInfo* p = new PieceInfo(); + p->name = "cannon"; p->sliderQuiet = {NORTH, EAST, SOUTH, WEST}; p->hopperCapture = {NORTH, EAST, SOUTH, WEST}; return p; @@ -246,7 +279,7 @@ void PieceMap::init() { add(IMMOBILE_PIECE, immobile_piece()); add(CANNON, cannon_piece()); add(WAZIR, wazir_piece()); - add(COMMONER, king_piece()); + add(COMMONER, commoner_piece()); add(KING, king_piece()); } diff --git a/src/piece.h b/src/piece.h index bd4809e..af6e4b8 100644 --- a/src/piece.h +++ b/src/piece.h @@ -19,6 +19,7 @@ #ifndef PIECE_H_INCLUDED #define PIECE_H_INCLUDED +#include #include #include @@ -28,6 +29,7 @@ /// PieceInfo struct stores information about the piece movements. struct PieceInfo { + std::string name = ""; std::vector stepsQuiet = {}; std::vector stepsCapture = {}; std::vector sliderQuiet = {}; diff --git a/src/position.cpp b/src/position.cpp index 4aa12ed..2e39a27 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -416,11 +416,11 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960, // Check counter for nCheck ss >> std::skipws >> token >> std::noskipws; - if (max_check_count() && ss.peek() == '+') + if (check_counting() && ss.peek() == '+') { - st->checksGiven[WHITE] = CheckCount(std::max(max_check_count() - std::max(token - '0', 0), 0)); + st->checksRemaining[WHITE] = CheckCount(std::max(token - '0', 0)); ss >> token >> token; - st->checksGiven[BLACK] = CheckCount(std::max(max_check_count() - std::max(token - '0', 0), 0)); + st->checksRemaining[BLACK] = CheckCount(std::max(token - '0', 0)); } else ss.putback(token); @@ -553,9 +553,9 @@ void Position::set_state(StateInfo* si) const { si->key ^= Zobrist::inHand[pc][pieceCountInHand[c][pt]]; } - if (max_check_count()) + if (check_counting()) for (Color c : {WHITE, BLACK}) - si->key ^= Zobrist::checks[c][si->checksGiven[c]]; + si->key ^= Zobrist::checks[c][si->checksRemaining[c]]; } @@ -663,8 +663,8 @@ const string Position::fen() const { ss << (ep_square() == SQ_NONE ? " - " : " " + UCI::square(*this, ep_square()) + " "); // Check count - if (max_check_count()) - ss << (max_check_count() - st->checksGiven[WHITE]) << "+" << (max_check_count() - st->checksGiven[BLACK]) << " "; + if (check_counting()) + ss << st->checksRemaining[WHITE] << "+" << st->checksRemaining[BLACK] << " "; // Counting ply or 50-move rule counter if (st->countingLimit) @@ -1074,8 +1074,8 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { assert(captured == NO_PIECE || color_of(captured) == (type_of(m) != CASTLING ? them : us)); assert(type_of(captured) != KING); - if (max_check_count() && givesCheck) - k ^= Zobrist::checks[us][st->checksGiven[us]] ^ Zobrist::checks[us][++(st->checksGiven[us])]; + if (check_counting() && givesCheck) + k ^= Zobrist::checks[us][st->checksRemaining[us]] ^ Zobrist::checks[us][--(st->checksRemaining[us])]; if (type_of(m) == CASTLING) { @@ -1592,7 +1592,7 @@ bool Position::see_ge(Move m, Value threshold) const { // nCheck - if (max_check_count() && color_of(moved_piece(m)) == sideToMove && gives_check(m)) + if (check_counting() && color_of(moved_piece(m)) == sideToMove && gives_check(m)) return true; // Extinction @@ -1792,7 +1792,7 @@ bool Position::is_immediate_game_end(Value& result, int ply) const { return true; } // nCheck - if (max_check_count() && st->checksGiven[~sideToMove] == max_check_count()) + if (check_counting() && checks_remaining(~sideToMove) == 0) { result = mated_in(ply); return true; diff --git a/src/position.h b/src/position.h index c4c6ce8..75125aa 100644 --- a/src/position.h +++ b/src/position.h @@ -47,7 +47,7 @@ struct StateInfo { int pliesFromNull; int countingPly; int countingLimit; - CheckCount checksGiven[COLOR_NB]; + CheckCount checksRemaining[COLOR_NB]; Square epSquare; Bitboard gatesBB[COLOR_NB]; @@ -144,9 +144,9 @@ public: PieceType capture_the_flag_piece() const; Bitboard capture_the_flag(Color c) const; bool flag_move() const; - CheckCount max_check_count() const; + bool check_counting() const; int connect_n() const; - CheckCount checks_given(Color c) const; + CheckCount checks_remaining(Color c) const; CountingRule counting_rule() const; // Variant-specific properties @@ -602,9 +602,9 @@ inline bool Position::flag_move() const { return var->flagMove; } -inline CheckCount Position::max_check_count() const { +inline bool Position::check_counting() const { assert(var != nullptr); - return var->maxCheckCount; + return var->checkCounting; } inline int Position::connect_n() const { @@ -612,8 +612,8 @@ inline int Position::connect_n() const { return var->connectN; } -inline CheckCount Position::checks_given(Color c) const { - return st->checksGiven[c]; +inline CheckCount Position::checks_remaining(Color c) const { + return st->checksRemaining[c]; } inline CountingRule Position::counting_rule() const { diff --git a/src/search.cpp b/src/search.cpp index 94dec65..9942d41 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -788,7 +788,7 @@ namespace { && depth < 2 * ONE_PLY && !pos.must_capture() && !pos.capture_the_flag_piece() - && !pos.max_check_count() + && !pos.check_counting() && eval <= alpha - RazorMargin) return qsearch(pos, ss, alpha, beta); @@ -805,7 +805,7 @@ namespace { && !( pos.extinction_value() == -VALUE_MATE && pos.extinction_piece_types().find(ALL_PIECES) == pos.extinction_piece_types().end()) && (pos.checking_permitted() || !pos.capture_the_flag_piece()) - && eval - futility_margin(depth, improving) * (1 + !!pos.max_check_count()) >= beta + && eval - futility_margin(depth, improving) * (1 + pos.check_counting()) >= beta && eval < VALUE_KNOWN_WIN) // Do not return unproven wins return eval; @@ -868,7 +868,7 @@ namespace { && (pos.pieces() ^ pos.pieces(CLOBBER_PIECE)) && abs(beta) < VALUE_MATE_IN_MAX_PLY) { - Value raisedBeta = std::min(beta + 216 * (1 + !!pos.max_check_count() + (pos.extinction_value() != VALUE_NONE)) - 48 * improving, VALUE_INFINITE); + Value raisedBeta = std::min(beta + 216 * (1 + pos.check_counting() + (pos.extinction_value() != VALUE_NONE)) - 48 * improving, VALUE_INFINITE); MovePicker mp(pos, ttMove, raisedBeta - ss->staticEval, &thisThread->captureHistory); int probCutCount = 0; diff --git a/src/uci.cpp b/src/uci.cpp index 0d6f9b9..2f6aea1 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -175,6 +175,16 @@ namespace { << "\nNodes/second : " << 1000 * nodes / elapsed << endl; } + // load() is called when engine receives the "load" command. + // The function reads variant configuration files. + + void load(istringstream& is) { + + string token; + while (is >> token) + Options["VariantPath"] = token; + } + } // namespace @@ -236,6 +246,7 @@ void UCI::loop(int argc, char* argv[]) { else if (token == "bench") bench(pos, is, states); else if (token == "d") sync_cout << pos << sync_endl; else if (token == "eval") sync_cout << Eval::trace(pos) << sync_endl; + else if (token == "load") { load(is); argc = 1; } // continue reading stdin else sync_cout << "Unknown command: " << cmd << sync_endl; diff --git a/src/uci.h b/src/uci.h index 9bb4b36..fe9f918 100644 --- a/src/uci.h +++ b/src/uci.h @@ -58,6 +58,7 @@ public: operator double() const; operator std::string() const; bool operator==(const char*) const; + void set_combo(std::vector newComboValues); private: friend std::ostream& operator<<(std::ostream&, const OptionsMap&); diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 86b0410..3fc8430 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -48,6 +48,7 @@ void on_hash_size(const Option& o) { TT.resize(o); } void on_logger(const Option& o) { start_logger(o); } void on_threads(const Option& o) { Threads.set(o); } void on_tb_path(const Option& o) { Tablebases::init(o); } +void on_variant_path(const Option& o) { variants.parse(o); Options["UCI_Variant"].set_combo(variants.get_keys()); } void on_variant_change(const Option &o) { const Variant* v = variants.find(o)->second; PSQT::init(v); @@ -99,6 +100,7 @@ void init(OptionsMap& o) { o["SyzygyProbeDepth"] << Option(1, 1, 100); o["Syzygy50MoveRule"] << Option(true); o["SyzygyProbeLimit"] << Option(7, 0, 7); + o["VariantPath"] << Option("", on_variant_path); } @@ -210,4 +212,8 @@ Option& Option::operator=(const string& v) { return *this; } +void Option::set_combo(std::vector newComboValues) { + comboValues = newComboValues; +} + } // namespace UCI diff --git a/src/variant.cpp b/src/variant.cpp index 2ff7823..e64bf87 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -17,13 +17,19 @@ */ #include +#include +#include +#include +#include "parser.h" +#include "piece.h" #include "variant.h" using std::string; VariantMap variants; // Global object +namespace { // Define variant rules Variant* fairy_variant_base() { Variant* v = new Variant(); @@ -187,13 +193,12 @@ VariantMap variants; // Global object Variant* threecheck_variant() { Variant* v = fairy_variant_base(); v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 3+3 0 1"; - v->maxCheckCount = CheckCount(3); + v->checkCounting = true; return v; } Variant* fivecheck_variant() { - Variant* v = fairy_variant_base(); + Variant* v = threecheck_variant(); v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 5+5 0 1"; - v->maxCheckCount = CheckCount(5); return v; } Variant* crazyhouse_variant() { @@ -467,39 +472,6 @@ VariantMap variants; // Global object v->blackFlag = Rank1BB; return v; } - Variant* connect4_variant() { - Variant* v = fairy_variant_base(); - v->maxRank = RANK_6; - v->maxFile = FILE_G; - v->reset_pieces(); - v->add_piece(IMMOBILE_PIECE, 'p'); - v->startFen = "7/7/7/7/7/7[PPPPPPPPPPPPPPPPPPPPPppppppppppppppppppppp] w 0 1"; - v->pieceDrops = true; - v->dropOnTop = true; - v->promotionPieceTypes = {}; - v->doubleStep = false; - v->castling = false; - v->stalemateValue = VALUE_DRAW; - v->immobilityIllegal = false; - v->connectN = 4; - return v; - } - Variant* tictactoe_variant() { - Variant* v = fairy_variant_base(); - v->maxRank = RANK_3; - v->maxFile = FILE_C; - v->reset_pieces(); - v->add_piece(IMMOBILE_PIECE, 'p'); - v->startFen = "3/3/3[PPPPPpppp] w 0 1"; - v->pieceDrops = true; - v->promotionPieceTypes = {}; - v->doubleStep = false; - v->castling = false; - v->stalemateValue = VALUE_DRAW; - v->immobilityIllegal = false; - v->connectN = 3; - return v; - } #ifdef LARGEBOARDS Variant* shogi_variant() { Variant* v = minishogi_variant_base(); @@ -665,6 +637,10 @@ VariantMap variants; // Global object } #endif +} // namespace + + +/// VariantMap::init() is called at startup to initialize all predefined variants void VariantMap::init() { // Add to UCI_Variant option @@ -711,8 +687,6 @@ void VariantMap::init() { add("shatar", shatar_variant()); add("clobber", clobber_variant()); add("breakthrough", breakthrough_variant()); - add("connect4", connect4_variant()); - add("tictactoe", tictactoe_variant()); #ifdef LARGEBOARDS add("shogi", shogi_variant()); add("capablanca", capablanca_variant()); @@ -731,6 +705,54 @@ void VariantMap::init() { #endif } + +/// VariantMap::parse reads variants from an INI-style configuration file. + +void VariantMap::parse(std::string path) { + if (path.empty() || path == "") + return; + std::ifstream file(path); + if (!file.is_open()) + { + std::cerr << "Unable to open file " << path << std::endl; + return; + } + std::string variant, variant_template, key, value, input; + while (file.peek() != '[' && std::getline(file, input)) {} + + while (file.get() && std::getline(std::getline(file, variant, ']'), input)) + { + // Extract variant template, if specified + if (!std::getline(std::getline(std::stringstream(variant), variant, ':'), variant_template)) + variant_template = ""; + + // Read variant rules + std::map attribs = {}; + while (file.peek() != '[' && std::getline(file, input)) + { + std::stringstream ss(input); + if (ss.peek() != '#' && std::getline(std::getline(ss, key, '=') >> std::ws, value) && !key.empty()) + attribs[key.erase(key.find_last_not_of(" ") + 1)] = value; + } + + // Create variant + if (variants.find(variant) != variants.end()) + std::cerr << "Variant '" << variant << "' already exists." << std::endl; + else if (!variant_template.empty() && variants.find(variant_template) == variants.end()) + std::cerr << "Variant template '" << variant_template << "' does not exist." << std::endl; + else + { + Variant* v = !variant_template.empty() ? VariantParser(attribs).parse(new Variant(*variants.find(variant_template)->second)) + : VariantParser(attribs).parse(); + if (v->maxFile <= FILE_MAX && v->maxRank <= RANK_MAX) + add(variant, v); + else + delete v; + } + } + file.close(); +} + void VariantMap::add(std::string s, const Variant* v) { insert(std::pair(s, v)); } diff --git a/src/variant.h b/src/variant.h index e5abcdd..340f3c7 100644 --- a/src/variant.h +++ b/src/variant.h @@ -93,7 +93,7 @@ struct Variant { Bitboard whiteFlag = 0; Bitboard blackFlag = 0; bool flagMove = false; - CheckCount maxCheckCount = CheckCount(0); + bool checkCounting = false; int connectN = 0; CountingRule countingRule = NO_COUNTING; @@ -115,11 +115,15 @@ struct Variant { } }; -struct VariantMap : public std::map { +class VariantMap : public std::map { +public: void init(); - void add(std::string s, const Variant* v); + void parse(std::string path); void clear_all(); std::vector get_keys(); + +private: + void add(std::string s, const Variant* v); }; extern VariantMap variants; diff --git a/src/variants.ini b/src/variants.ini new file mode 100644 index 0000000..73df9c8 --- /dev/null +++ b/src/variants.ini @@ -0,0 +1,129 @@ +# Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish +# Copyright (C) 2018-2019 Fabian Fichter +# +# Fairy-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. +# +# Fairy-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 . + +# This is a configuration file to add user-defined variants to Fairy-Stockfish. + +# Usage: +# Add "load" and the file path to the SF call (e.g., "./stockfish load variants.ini") +# or set the UCI option "VariantPath" to the path of this file in order to load it. + +# Variant configuration: +# The variant name needs to be specified as a section in square brackets, +# followed by its rule configurations as key-value pairs. +# See variants.h for the available options and their default values. +# If you encounter problems configuring variants, please report them at: +# https://github.com/ianfab/Fairy-Stockfish/issues + +# Example for minishogi configuration that would be equivalent to the built-in variant: + +# [minishogi] +# variantTemplate = shogi +# maxRank = 5 +# maxFile = 5 +# shogi_pawn = p +# silver = s +# gold = g +# bishop = b +# horse = h +# rook = r +# bers = d +# king = k +# startFen = rbsgk/4p/5/P4/KGSBR[-] w 0 1 +# pieceDrops = true +# capturesToHand = true +# promotionRank = 5 +# doubleStep = false +# castling = false +# promotedPieceType = p:g s:g b:h r:d +# shogiDoubledPawn = false +# immobilityIllegal = true +# shogiPawnDropMateIllegal = true +# stalemateValue = loss +# nFoldRule = 4 +# nMoveRule = 0 +# perpetualCheckIllegal = true +# pocketSize = 5 +# nFoldValue = loss +# nFoldValueAbsolute = true + +# Hybrid variant of three-check chess and crazyhouse, using crazyhouse as a template +[3check-crazyhouse:crazyhouse] +startFen = rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 3+3 0 1 +checkCounting = true + +# Hybrid variant of antichess and losalamos +[anti-losalamos] +pawn = p +knight = n +rook = r +queen = q +commoner = k +maxRank = 6 +maxFile = 6 +startFen = rnqknr/pppppp/6/6/PPPPPP/RNQKNR w - - 0 1 +promotionRank = 6 +promotionPieceTypes = nrqk +doubleStep = false +castling = false +mustCapture = true +stalemateValue = win +extinctionValue = win +extinctionPieceTypes = * + +# Indian great chess +# https://www.chessvariants.com/historic.dir/indiangr1.html +[indiangreat] +pawn = p +knight = n +bishop = b +rook = r +queen = q +king = k +archbishop = v +chancellor = w +amazon = g +maxRank = 10 +maxFile = 10 +startFen = rnbqkgvbnr/ppppwwpppp/4pp4/10/10/10/10/4PP4/PPPPWWPPPP/RNBVGKQBNR w - - 0 1 +promotionRank = 10 +promotionPieceTypes = q +doubleStep = false +castling = false + +[tictactoe] +maxRank = 3 +maxFile = 3 +immobile = p +startFen = 3/3/3[PPPPPpppp] w - - 0 1 +pieceDrops = true +doubleStep = false +castling = false +stalemateValue = draw +immobilityIllegal = false +connectN = 3 + +[connect4] +maxRank = 6 +maxFile = 7 +immobile = p +startFen = 7/7/7/7/7/7[PPPPPPPPPPPPPPPPPPPPPppppppppppppppppppppp] w - - 0 1 +pieceDrops = true +dropOnTop = true +doubleStep = false +castling = false +stalemateValue = draw +immobilityIllegal = false +connectN = 4