Support reading variants from configuration file
authorFabian Fichter <ianfab@users.noreply.github.com>
Fri, 30 Aug 2019 17:22:30 +0000 (19:22 +0200)
committerFabian Fichter <ianfab@users.noreply.github.com>
Sat, 31 Aug 2019 13:09:28 +0000 (15:09 +0200)
Read variants from INI-style configuration file in order to
support user-defined variants without requiring recompilation.

16 files changed:
Readme.md
src/Makefile
src/evaluate.cpp
src/parser.cpp [new file with mode: 0644]
src/parser.h [new file with mode: 0644]
src/piece.cpp
src/piece.h
src/position.cpp
src/position.h
src/search.cpp
src/uci.cpp
src/uci.h
src/ucioption.cpp
src/variant.cpp
src/variant.h
src/variants.ini [new file with mode: 0644]

index 5733f03..ad0cb0e 100644 (file)
--- 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)
index 4411293..ce35fbc 100644 (file)
@@ -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
index 08ab3c5..7e0acb4 100644 (file)
@@ -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 (file)
index 0000000..e4c5f70
--- /dev/null
@@ -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 <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;
+}
diff --git a/src/parser.h b/src/parser.h
new file mode 100644 (file)
index 0000000..2cf7901
--- /dev/null
@@ -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 <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
index aea82e0..9699517 100644 (file)
@@ -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());
 }
 
index bd4809e..af6e4b8 100644 (file)
@@ -19,6 +19,7 @@
 #ifndef PIECE_H_INCLUDED
 #define PIECE_H_INCLUDED
 
+#include <string>
 #include <map>
 #include <vector>
 
@@ -28,6 +29,7 @@
 /// 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 = {};
index 4aa12ed..2e39a27 100644 (file)
@@ -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;
index c4c6ce8..75125aa 100644 (file)
@@ -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 {
index 94dec65..9942d41 100644 (file)
@@ -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<NT>(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;
 
index 0d6f9b9..2f6aea1 100644 (file)
@@ -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;
 
index 9bb4b36..fe9f918 100644 (file)
--- 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<std::string> newComboValues);
 
 private:
   friend std::ostream& operator<<(std::ostream&, const OptionsMap&);
index 86b0410..3fc8430 100644 (file)
@@ -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("<empty>", on_variant_path);
 }
 
 
@@ -210,4 +212,8 @@ Option& Option::operator=(const string& v) {
   return *this;
 }
 
+void Option::set_combo(std::vector<std::string> newComboValues) {
+    comboValues = newComboValues;
+}
+
 } // namespace UCI
index 2ff7823..e64bf87 100644 (file)
 */
 
 #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();
@@ -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 == "<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));
 }
index e5abcdd..340f3c7 100644 (file)
@@ -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<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;
diff --git a/src/variants.ini b/src/variants.ini
new file mode 100644 (file)
index 0000000..73df9c8
--- /dev/null
@@ -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 <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