Define piece types with ambiguous demotion multiple times
authorH.G.Muller <hgm@hgm-xboard.(none)>
Sat, 20 Dec 2025 11:07:13 +0000 (12:07 +0100)
committerH.G.Muller <hgm@hgm-xboard.(none)>
Sat, 20 Dec 2025 11:07:13 +0000 (12:07 +0100)
The predefined variants now define pieces that demote differently (or not
at all) multiple times, as custom pieces. To make this possible add_piece()
was changed to allow pieces with the same ID if the synonym is '+'.
a new arrays demotedPieceType[PieceType] and a PieceSet isDemotedType
are set to record this, for later use.

src/variant.cpp
src/variant.h

index ad97b97..996b153 100644 (file)
@@ -793,6 +793,8 @@ namespace {
         v->add_piece(ROOK, 'r');
         v->add_piece(DRAGON, 'd');
         v->add_piece(KING, 'k');
+        v->add_piece(CUSTOM_PIECE_1, 'g', "WfF", '+');
+        v->add_piece(CUSTOM_PIECE_2, 'g', "WfF", '+');
         v->startFen = "rbsgk/4p/5/P4/KGSBR[-] w 0 1";
         v->pieceDrops = true;
         v->capturesToHand = true;
@@ -800,10 +802,15 @@ namespace {
         v->promotionRegion[BLACK] = Rank1BB;
         v->doubleStep = false;
         v->castling = false;
-        v->promotedPieceType[SHOGI_PAWN] = GOLD;
-        v->promotedPieceType[SILVER]     = GOLD;
+        v->promotedPieceType[SHOGI_PAWN] = CUSTOM_PIECE_1;
+        v->promotedPieceType[SILVER]     = CUSTOM_PIECE_2;
         v->promotedPieceType[BISHOP]     = DRAGON_HORSE;
         v->promotedPieceType[ROOK]       = DRAGON;
+        v->demotedPieceType[CUSTOM_PIECE_1] = SHOGI_PAWN;
+        v->demotedPieceType[CUSTOM_PIECE_2] = SILVER;
+        v->demotedPieceType[DRAGON_HORSE] = BISHOP;
+        v->demotedPieceType[DRAGON] = ROOK;
+        v->isPromotedType = piece_set(DRAGON) | DRAGON_HORSE | CUSTOM_PIECE_1 | CUSTOM_PIECE_2;
         v->dropNoDoubled = SHOGI_PAWN;
         v->immobilityIllegal = true;
         v->shogiPawnDropMateIllegal = true;
@@ -838,13 +845,19 @@ namespace {
         v->mandatoryPiecePromotion = true;
         v->pieceDemotion = true;
         v->dropPromoted = true;
-        v->promotedPieceType[LANCE]        = GOLD;
+        v->promotedPieceType[LANCE]        = CUSTOM_PIECE_1;
         v->promotedPieceType[SILVER]       = BISHOP;
         v->promotedPieceType[SHOGI_KNIGHT] = GOLD;
         v->promotedPieceType[SHOGI_PAWN]   = ROOK;
         v->promotedPieceType[GOLD]         = NO_PIECE_TYPE;
         v->promotedPieceType[BISHOP]       = NO_PIECE_TYPE;
         v->promotedPieceType[ROOK]         = NO_PIECE_TYPE;
+        v->promotedPieceType[CUSTOM_PIECE_1] = NO_PIECE_TYPE;
+        v->demotedPieceType[GOLD]           = SHOGI_KNIGHT;
+        v->demotedPieceType[BISHOP]         = SILVER;
+        v->demotedPieceType[ROOK]           = SHOGI_PAWN;
+        v->demotedPieceType[CUSTOM_PIECE_1] = LANCE;
+        v->isPromotedType |= piece_set(ROOK) | BISHOP | GOLD;
         v->immobilityIllegal = false;
         v->shogiPawnDropMateIllegal = false;
         v->dropNoDoubled = NO_PIECE_TYPE;
@@ -861,12 +874,18 @@ namespace {
         v->promotionRegion[BLACK] = AllSquares;
         v->piecePromotionOnCapture = true;
         v->promotedPieceType[LANCE]        = SILVER;
-        v->promotedPieceType[BISHOP]       = GOLD;
+        v->promotedPieceType[BISHOP]       = CUSTOM_PIECE_1;
         v->promotedPieceType[ROOK]         = GOLD;
         v->promotedPieceType[SHOGI_PAWN]   = SHOGI_KNIGHT;
         v->promotedPieceType[SILVER]       = NO_PIECE_TYPE;
         v->promotedPieceType[GOLD]         = NO_PIECE_TYPE;
         v->promotedPieceType[SHOGI_KNIGHT] = NO_PIECE_TYPE;
+        v->promotedPieceType[CUSTOM_PIECE_1] = NO_PIECE_TYPE;
+        v->demotedPieceType[CUSTOM_PIECE_1] = BISHOP;
+        v->demotedPieceType[SILVER]         = LANCE;
+        v->demotedPieceType[GOLD]           = ROOK;
+        v->demotedPieceType[SHOGI_KNIGHT]   = SHOGI_PAWN;
+        v->isPromotedType = piece_set(SILVER) | GOLD | SHOGI_KNIGHT | CUSTOM_PIECE_1;
         return v;
     }
     // Dobutsu
@@ -919,10 +938,13 @@ namespace {
         v->maxRank = RANK_6;
         v->maxFile = FILE_F;
         v->add_piece(SHOGI_KNIGHT, 'n');
+        v->add_piece(CUSTOM_PIECE_3, 'n', "WfF", '+');
         v->startFen = "rbnsgk/5p/6/6/P5/KGSNBR[-] w 0 1";
         v->promotionRegion[WHITE] = Rank5BB | Rank6BB;
         v->promotionRegion[BLACK] = Rank2BB | Rank1BB;
-        v->promotedPieceType[SHOGI_KNIGHT] = GOLD;
+        v->promotedPieceType[SHOGI_KNIGHT] = CUSTOM_PIECE_3;
+        v->demotedPieceType[CUSTOM_PIECE_3] = SHOGI_KNIGHT;
+        v->isPromotedType |= CUSTOM_PIECE_3;
         return v;
     }
     // Tori shogi
@@ -952,6 +974,9 @@ namespace {
         v->castling = false;
         v->promotedPieceType[SHOGI_PAWN]    = CUSTOM_PIECE_6; // swallow promotes to goose
         v->promotedPieceType[CUSTOM_PIECE_1] = CUSTOM_PIECE_7; // falcon promotes to eagle
+        v->demotedPieceType[CUSTOM_PIECE_6] = SHOGI_PAWN;
+        v->demotedPieceType[CUSTOM_PIECE_7] = CUSTOM_PIECE_1;
+        v->isPromotedType = piece_set(CUSTOM_PIECE_6) | CUSTOM_PIECE_7;
         v->mandatoryPiecePromotion = true;
         v->dropNoDoubled = SHOGI_PAWN;
         v->dropNoDoubledCount = 2;
@@ -971,11 +996,14 @@ namespace {
         v->pieceToCharTable = "PNBR.....G.++++Kpnbr.....g.++++k";
         v->maxRank = RANK_8;
         v->maxFile = FILE_H;
-        v->add_piece(CUSTOM_PIECE_1, 'n', "fNsW");
+        v->add_piece(CUSTOM_PIECE_3, 'n', "fNsW");
+        v->add_piece(CUSTOM_PIECE_4, 'n', "WfF", '+');
         v->startFen = "1nbgkgn1/1r4b1/pppppppp/8/8/PPPPPPPP/1B4R1/1NGKGBN1[-] w 0 1";
         v->promotionRegion[WHITE] = Rank6BB | Rank7BB | Rank8BB;
         v->promotionRegion[BLACK] = Rank3BB | Rank2BB | Rank1BB;
-        v->promotedPieceType[CUSTOM_PIECE_1] = GOLD;
+        v->promotedPieceType[CUSTOM_PIECE_3] = CUSTOM_PIECE_4;
+        v->demotedPieceType[CUSTOM_PIECE_4] = CUSTOM_PIECE_3;
+        v->isPromotedType |= CUSTOM_PIECE_4;
         v->mandatoryPiecePromotion = true;
         return v;
     }
@@ -1237,11 +1265,16 @@ namespace {
         v->maxFile = FILE_I;
         v->add_piece(LANCE, 'l');
         v->add_piece(SHOGI_KNIGHT, 'n');
+        v->add_piece(CUSTOM_PIECE_3, 'l', "WfF", '+');
+        v->add_piece(CUSTOM_PIECE_4, 'n', "WfF", '+');
         v->startFen = "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL[-] w 0 1";
         v->promotionRegion[WHITE] = Rank7BB | Rank8BB | Rank9BB;
         v->promotionRegion[BLACK] = Rank3BB | Rank2BB | Rank1BB;
-        v->promotedPieceType[LANCE]        = GOLD;
-        v->promotedPieceType[SHOGI_KNIGHT] = GOLD;
+        v->promotedPieceType[LANCE]        = CUSTOM_PIECE_3;
+        v->promotedPieceType[SHOGI_KNIGHT] = CUSTOM_PIECE_4;
+        v->demotedPieceType[CUSTOM_PIECE_3] = LANCE;
+        v->demotedPieceType[CUSTOM_PIECE_4] = SHOGI_KNIGHT;
+        v->isPromotedType |= piece_set(CUSTOM_PIECE_4) | CUSTOM_PIECE_3;
         return v;
     }
     // Check-Shogi
@@ -1259,11 +1292,13 @@ namespace {
         v->pieceToCharTable = "PNBRLSE..G.+.++.++Kpnbrlse..g.+.++.++k";
         v->remove_piece(KING);
         v->add_piece(COMMONER, 'k');
-        v->add_piece(CUSTOM_PIECE_1, 'e', "FsfW"); // drunk elephant
+        v->add_piece(CUSTOM_PIECE_5, 'e', "FsfW"); // drunk elephant
         v->startFen = "lnsgkgsnl/1r2e2b1/ppppppppp/9/9/9/PPPPPPPPP/1B2E2R1/LNSGKGSNL w 0 1";
         v->capturesToHand = false;
         v->pieceDrops = false;
-        v->promotedPieceType[CUSTOM_PIECE_1] = COMMONER;
+        v->promotedPieceType[CUSTOM_PIECE_5] = COMMONER;
+        v->demotedPieceType[COMMONER] = CUSTOM_PIECE_5;
+        v->isPromotedType |= CUSTOM_PIECE_5;
         v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = COMMONER;
         v->extinctionValue = -VALUE_MATE;
         v->extinctionPieceTypes = piece_set(COMMONER);
@@ -1286,15 +1321,21 @@ namespace {
         v->add_piece(CUSTOM_PIECE_1, 'n', "fRffN"); // Yari knight
         v->add_piece(CUSTOM_PIECE_2, 'b', "fFfR"); // Yari bishop
         v->add_piece(CUSTOM_PIECE_3, 'r', "frlR"); // Yari rook
-        v->add_piece(CUSTOM_PIECE_4, 'g', "WfFbR"); // Yari gold
+        v->add_piece(CUSTOM_PIECE_4, 'g', "WfFbR"); // Yari gold +N
         v->add_piece(CUSTOM_PIECE_5, 's', "fKbR"); // Yari silver
+        v->add_piece(CUSTOM_PIECE_6, 'g', "WfFbR"); // Yari gold +B
         v->startFen = "rnnkbbr/7/ppppppp/7/7/7/PPPPPPP/7/RBBKNNR[-] w 0 1";
         v->promotionRegion[WHITE] = Rank7BB | Rank8BB | Rank9BB;
         v->promotionRegion[BLACK] = Rank3BB | Rank2BB | Rank1BB;
         v->promotedPieceType[SHOGI_PAWN] = CUSTOM_PIECE_5;
         v->promotedPieceType[CUSTOM_PIECE_1] = CUSTOM_PIECE_4;
-        v->promotedPieceType[CUSTOM_PIECE_2] = CUSTOM_PIECE_4;
+        v->promotedPieceType[CUSTOM_PIECE_2] = CUSTOM_PIECE_6;
         v->promotedPieceType[CUSTOM_PIECE_3] = ROOK;
+        v->demotedPieceType[CUSTOM_PIECE_5] = SHOGI_PAWN;
+        v->demotedPieceType[CUSTOM_PIECE_4] = CUSTOM_PIECE_1;
+        v->demotedPieceType[CUSTOM_PIECE_6] = CUSTOM_PIECE_2;
+        v->demotedPieceType[ROOK]           = CUSTOM_PIECE_3;
+        v->isPromotedType = piece_set(ROOK) | CUSTOM_PIECE_4 | CUSTOM_PIECE_5 | CUSTOM_PIECE_6;
         v->pieceDrops = true;
         v->capturesToHand = true;
         v->doubleStep = false;
@@ -1314,14 +1355,19 @@ namespace {
         Variant* v = minishogi_variant_base()->init();
         v->maxRank = RANK_10;
         v->maxFile = FILE_J;
-        v->add_piece(CUSTOM_PIECE_1, 'l', "vR"); // Vertical slider
+        v->add_piece(CUSTOM_PIECE_3, 'l', "vR"); // Vertical slider
         v->add_piece(KNIGHT, 'n');
         v->add_piece(QUEEN, 'q');
+        v->add_piece(CUSTOM_PIECE_4, 'l', "WfF", '+'); 
+        v->add_piece(CUSTOM_PIECE_5, 'n', "WfF", '+'); 
         v->startFen = "lnsgkqgsnl/1r6b1/pppppppppp/10/10/10/10/PPPPPPPPPP/1B6R1/LNSGQKGSNL[-] w 0 1";
         v->promotionRegion[WHITE] = Rank8BB | Rank9BB | Rank10BB;
         v->promotionRegion[BLACK] = Rank3BB | Rank2BB | Rank1BB;
-        v->promotedPieceType[CUSTOM_PIECE_1] = GOLD;
-        v->promotedPieceType[KNIGHT] = GOLD;
+        v->promotedPieceType[CUSTOM_PIECE_3] = CUSTOM_PIECE_4;
+        v->promotedPieceType[KNIGHT] = CUSTOM_PIECE_5;
+        v->demotedPieceType[CUSTOM_PIECE_4] = CUSTOM_PIECE_3;
+        v->demotedPieceType[CUSTOM_PIECE_5] = KNIGHT;
+        v->isPromotedType |= piece_set(CUSTOM_PIECE_4) | CUSTOM_PIECE_5;
         return v;
     }
     // Capablanca chess
@@ -1635,6 +1681,7 @@ namespace {
         v->promotionPieceTypes[WHITE] = piece_set(QUEEN) | CHANCELLOR | ARCHBISHOP | ROOK | BISHOP;
         v->promotionPieceTypes[BLACK] = piece_set(QUEEN) | CHANCELLOR | ARCHBISHOP | ROOK | BISHOP;
         v->promotedPieceType[PAWN] = CUSTOM_PIECE_3;
+        v->demotedPieceType[CUSTOM_PIECE_3] = PAWN;
         v->promotionRegion[WHITE] = Rank10BB;
         v->promotionRegion[BLACK] = Rank1BB;
         v->doubleStepRegion[WHITE] = Rank2BB | make_bitboard(SQ_B3, SQ_C3, SQ_F3, SQ_G3);
index 0e6d44c..cffc8ba 100644 (file)
@@ -59,6 +59,8 @@ struct Variant {
   bool sittuyinPromotion = false;
   int promotionLimit[PIECE_TYPE_NB] = {}; // 0 means unlimited
   PieceType promotedPieceType[PIECE_TYPE_NB] = {};
+  PieceType demotedPieceType[PIECE_TYPE_NB] = {};
+  PieceSet isPromotedType = NO_PIECE_SET;
   bool piecePromotionOnCapture = false;
   bool mandatoryPawnPromotion = true;
   bool mandatoryPiecePromotion = false;
@@ -181,6 +183,7 @@ struct Variant {
   void add_piece(PieceType pt, char c, std::string betza = "", char c2 = ' ') {
       // Avoid ambiguous definition by removing existing piece with same letter
       size_t idx;
+      if(c2 == '+') c2 = ' '; else
       if ((idx = pieceToChar.find(toupper(c))) != std::string::npos)
           remove_piece(PieceType(idx));
       // Now add new piece