Support variants that can split pieces v4.9.x
authorH.G.Muller <hgm@hgm-xboard.(none)>
Fri, 28 Nov 2025 13:29:14 +0000 (14:29 +0100)
committerH.G.Muller <hgm@hgm-xboard.(none)>
Fri, 28 Nov 2025 13:46:38 +0000 (14:46 +0100)
CECP is extended by a new engine-GUI command 'split', which transfers a
matrix that specifies which piece should be left on the from-square after
a move. This can be dependent on the moved piece type, but also on the
type it promotes to. It is transferred as a matrix filled with the ID of
the piece to be left behind, where rows are separated by slashes. Each row
starts with the ID for non-promoting moves, and then (optionally) continues
with the cases for promotion choices in pieceToCharTable order.
  E.g., to have a Knight leave behind Pawns in otherwise normal Chess,
the engine can send:

split /P//////p

where non-,emmtioned and empty rows are taken to specify the from-square
should be left empty. To specify promotion to Knight should actually give
you two Knights (other promotions being unaffected) the engine could send

split --N//////--------n

The split command only works in engine-defined variants, and should be
sent after 'setup' in order for the pieceToCharTable (needed to interpret
it) to be known.
  Beware that the format allows for enemy promotion, so that the black
promotion choices come late in the row.

backend.c

index 6432f8d..62f2b86 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -299,7 +299,7 @@ static int initPing = -1;
 int border;       /* [HGM] width of board rim, needed to size seek graph  */
 char bestMove[MSG_SIZ], avoidMove[MSG_SIZ];
 int solvingTime, totalTime, jawsClock;
-
+ChessSquare leaveBehind[EmptySquare+1][EmptySquare];
 /* States for ics_getting_history */
 #define H_FALSE 0
 #define H_REQUESTED 1
@@ -8984,6 +8984,21 @@ LoadError (char *errmess, ChessProgramState *cps)
     return TRUE;
 }
 
+void
+ParseMatrix (char *s, ChessSquare matrix[][EmptySquare])
+{
+  int i= -1, j, k;
+  char c = '/';
+  do {
+    if(c == '/') { while(pieceToChar[++i] == '.') {} j = -2; continue; }
+    if(j == -2) { j++; k = EmptySquare; } else {
+      while(pieceToChar[++j] == '.') {}
+      k = j;
+    }
+    matrix[k][i] = CharToPiece(c);
+  } while((c = *s++));
+}
+
 char *savedMessage;
 ChessProgramState *savedState;
 void
@@ -9478,6 +9493,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
       }
       return;
     }
+    if(sscanf(message, "split %s" , buf1) == 1) { if(*engineVariant) ParseMatrix(buf1, leaveBehind); return;}
     if(sscanf(message, "choice %s", promoRestrict) == 1) {
       if(gameMode == BeginningOfGame && cps == &first && 
          (!appData.testLegality || *engineVariant != NULLCHAR || gameInfo.variant == VariantFairy)) {
@@ -10552,7 +10568,7 @@ ParseGameHistory (char *game)
 void
 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
 {
-  ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2;
+  ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2, left;
   int p, rookX, oldEP, epRank, epFile, lastFile, lastRank, berolina = 0, isEP = 0;
   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
 
@@ -10927,6 +10943,8 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
           && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
         board[toY][toX] = newPiece;
     }
+    left = leaveBehind[promoChar ? board[toY][toX] : EmptySquare][piece]; // [HGM] for variants that split pieces
+    if(left != EmptySquare) board[fromY][fromX] = left; // make sure this does not interfere unless split command was received
     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
                && promoChar != NULLCHAR && gameInfo.holdingsSize) {
        // [HGM] superchess: take promotion piece out of holdings
@@ -12456,7 +12474,7 @@ ResurrectChessProgram ()
 void
 Reset (int redraw, int init)
 {
-    int i;
+    int i, j;
 
     if (appData.debugMode) {
        fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
@@ -12467,6 +12485,7 @@ Reset (int redraw, int init)
     handSize = 0;
     prelude = FALSE; preludeText[0] = NULLCHAR;
     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
+    for(i=0; i<=EmptySquare; i++) for(j=0; j<EmptySquare; j++) leaveBehind[i][j] = EmptySquare;
     CleanupTail(); // [HGM] vari: delete any stored variations
     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
     pausing = pauseExamInvalid = FALSE;