-## Fairy-Stockfish
+# Fairy-Stockfish
+
+## Overview
[](https://travis-ci.org/ianfab/Fairy-Stockfish)
[](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)
- [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
[](https://travis-ci.org/official-stockfish/Stockfish)
[](https://ci.appveyor.com/project/mcostalba/stockfish/branch/master)
### 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
}
}
- if (pos.max_check_count())
+ if (pos.check_counting())
kingDanger *= 2;
// Unsafe or occupied checking squares will also be considered, as long as
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
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
}
// 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);
}
--- /dev/null
+/*
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include <map>
+#include <vector>
+#include <string>
+#include <sstream>
+
+#include "parser.h"
+#include "piece.h"
+#include "types.h"
+
+namespace {
+
+ template <typename T> 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 <class T> 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;
+}
--- /dev/null
+/*
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef PARSER_H_INCLUDED
+#define PARSER_H_INCLUDED
+
+#include "variant.h"
+
+class VariantParser {
+public:
+ VariantParser(const std::map<std::string, std::string>& c) : config (c) {};
+ Variant* parse();
+ Variant* parse(Variant* v);
+
+private:
+ std::map<std::string, std::string> config;
+ template <class T> void parse_attribute(const std::string& key, T& target);
+};
+
+#endif // #ifndef PARSER_H_INCLUDED
\ No newline at end of file
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,
}
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;
}
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);
}
PieceInfo* bers_piece() {
PieceInfo* p = rook_piece();
+ p->name = "bers";
PieceInfo* p2 = fers_piece();
p->merge(p2);
delete p2;
}
PieceInfo* archbishop_piece() {
PieceInfo* p = bishop_piece();
+ p->name = "archbishop";
PieceInfo* p2 = knight_piece();
p->merge(p2);
delete p2;
}
PieceInfo* chancellor_piece() {
PieceInfo* p = rook_piece();
+ p->name = "chancellor";
PieceInfo* p2 = knight_piece();
p->merge(p2);
delete p2;
}
PieceInfo* amazon_piece() {
PieceInfo* p = queen_piece();
+ p->name = "amazon";
PieceInfo* p2 = knight_piece();
p->merge(p2);
delete p2;
}
PieceInfo* knibis_piece() {
PieceInfo* p = bishop_piece();
+ p->name = "knibis";
PieceInfo* p2 = knight_piece();
p->merge(p2);
delete p2;
}
PieceInfo* biskni_piece() {
PieceInfo* p = bishop_piece();
+ p->name = "biskni";
PieceInfo* p2 = knight_piece();
p->merge(p2);
delete p2;
}
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);
}
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;
}
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;
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());
}
#ifndef PIECE_H_INCLUDED
#define PIECE_H_INCLUDED
+#include <string>
#include <map>
#include <vector>
/// PieceInfo struct stores information about the piece movements.
struct PieceInfo {
+ std::string name = "";
std::vector<Direction> stepsQuiet = {};
std::vector<Direction> stepsCapture = {};
std::vector<Direction> sliderQuiet = {};
// 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);
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]];
}
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)
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)
{
// 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
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;
int pliesFromNull;
int countingPly;
int countingLimit;
- CheckCount checksGiven[COLOR_NB];
+ CheckCount checksRemaining[COLOR_NB];
Square epSquare;
Bitboard gatesBB[COLOR_NB];
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
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 {
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 {
&& depth < 2 * ONE_PLY
&& !pos.must_capture()
&& !pos.capture_the_flag_piece()
- && !pos.max_check_count()
+ && !pos.check_counting()
&& eval <= alpha - RazorMargin)
return qsearch<NT>(pos, ss, alpha, beta);
&& !( 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;
&& (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;
<< "\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
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;
operator double() const;
operator std::string() const;
bool operator==(const char*) const;
+ void set_combo(std::vector<std::string> newComboValues);
private:
friend std::ostream& operator<<(std::ostream&, const OptionsMap&);
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);
o["SyzygyProbeDepth"] << Option(1, 1, 100);
o["Syzygy50MoveRule"] << Option(true);
o["SyzygyProbeLimit"] << Option(7, 0, 7);
+ o["VariantPath"] << Option("<empty>", on_variant_path);
}
return *this;
}
+void Option::set_combo(std::vector<std::string> newComboValues) {
+ comboValues = newComboValues;
+}
+
} // namespace UCI
*/
#include <string>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#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();
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() {
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();
}
#endif
+} // namespace
+
+
+/// VariantMap::init() is called at startup to initialize all predefined variants
void VariantMap::init() {
// Add to UCI_Variant option
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());
#endif
}
+
+/// VariantMap::parse reads variants from an INI-style configuration file.
+
+void VariantMap::parse(std::string path) {
+ if (path.empty() || path == "<empty>")
+ 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<std::string, std::string> 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<std::string, const Variant*>(s, v));
}
Bitboard whiteFlag = 0;
Bitboard blackFlag = 0;
bool flagMove = false;
- CheckCount maxCheckCount = CheckCount(0);
+ bool checkCounting = false;
int connectN = 0;
CountingRule countingRule = NO_COUNTING;
}
};
-struct VariantMap : public std::map<std::string, const Variant*> {
+class VariantMap : public std::map<std::string, const Variant*> {
+public:
void init();
- void add(std::string s, const Variant* v);
+ void parse(std::string path);
void clear_all();
std::vector<std::string> get_keys();
+
+private:
+ void add(std::string s, const Variant* v);
};
extern VariantMap variants;
--- /dev/null
+# 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 <http://www.gnu.org/licenses/>.
+
+# 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