From af3d528cf16eae800ff7f0b8b62f77ced7493412 Mon Sep 17 00:00:00 2001 From: H.G.Muller Date: Tue, 8 Mar 2016 11:51:29 +0100 Subject: [PATCH] Implement triple capture Moves can now have two locust squares (and promote even when they are locust captures). This applies to move entry (but only through click- click; buttonless dragging does not yet work), as well as sending or receiving from engine, and generating and parsing SAN. --- backend.c | 63 ++++++++++++++++++++++++++++++++++++++---------------------- moves.c | 19 +++++++++++++---- moves.h | 2 +- parser.c | 17 ++++++++++++++- 4 files changed, 70 insertions(+), 31 deletions(-) diff --git a/backend.c b/backend.c index c487dd1..2657808 100644 --- a/backend.c +++ b/backend.c @@ -5161,7 +5161,15 @@ SendMoveToProgram (int moveNum, ChessProgramState *cps) m[2], m[3] - '0', m[5], m[6] - '0', m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0'); - else + else if(*c && m[8]) { // kill square followed by 2 characters: 2nd kill square rather than promo suffix + *c = m[9]; + snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to three moves + m[7], m[8] - '0', + m[7], m[8] - '0', + m[5], m[6] - '0', + m[5], m[6] - '0', + m[2], m[3] - '0', c); + } else snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves m[5], m[6] - '0', m[5], m[6] - '0', @@ -5364,14 +5372,14 @@ CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char AAA + ff, ONE + rf, AAA + ft, ONE + rt); if(killX >= 0 && killY >= 0) { sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY); - if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + killX, ONE + killY); + if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y); } } else { sprintf(move, "%c%c%c%c%c\n", AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar); if(killX >= 0 && killY >= 0) { sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY); - if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + killX, ONE + killY, promoChar); + if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar); } } } @@ -5571,7 +5579,7 @@ ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fro *toX = currentMoveString[2] - AAA; *toY = currentMoveString[3] - ONE; *promoChar = currentMoveString[4]; - if(*promoChar == ';') *promoChar = currentMoveString[7]; + if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)]; if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT || *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) { if (appData.debugMode) { @@ -7406,10 +7414,11 @@ Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VO { typedef char Markers[BOARD_RANKS][BOARD_FILES]; Markers *m = (Markers *) closure; - if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2)) + if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : + kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4)) (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare || kind == WhiteCapturesEnPassant - || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 3; + || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3; else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3; } @@ -7611,7 +7620,7 @@ LeftClick (ClickType clickType, int xPix, int yPix) if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves doubleClick = TRUE; gatingPiece = boards[currentMove][y][x]; } - fromX = x; fromY = y; toX = toY = killX = killY = -1; + fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1; if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) || // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) { @@ -7663,7 +7672,7 @@ LeftClick (ClickType clickType, int xPix, int yPix) !(fromP == BlackKing && toP == BlackRook && frc)))) { /* Clicked again on same color piece -- changed his mind */ second = (x == fromX && y == fromY); - killX = killY = -1; + killX = killY = kill2X = kill2Y = -1; if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) { second = FALSE; // first double-click rather than scond click doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves @@ -7769,7 +7778,7 @@ LeftClick (ClickType clickType, int xPix, int yPix) return; } if(x == killX && y == killY) { // second click on this square, which was selected as first-leg target - killX = killY = -1; // this informs us no second leg is coming, so treat as to-click without intermediate + killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1; // this informs us no second leg is coming, so treat as to-click without intermediate } else if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) { @@ -7818,7 +7827,7 @@ LeftClick (ClickType clickType, int xPix, int yPix) if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill else { kill2X = killX; kill2Y = killY; - killX = x; killY = y; //remeber this square as intermediate + killX = x; killY = y; // remember this square as intermediate ReportClick("put", x, y); // and inform engine ReportClick("lift", x, y); MarkTargetSquares(0); @@ -8674,7 +8683,7 @@ static char stashedInputMove[MSG_SIZ], abortEngineThink; void HandleMachineMove (char *message, ChessProgramState *cps) { - static char firstLeg[20]; + static char firstLeg[20], legs; char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ]; char realname[MSG_SIZ]; int fromX, fromY, toX, toY; @@ -8827,11 +8836,14 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h // [HGM] lion: (some very limited) support for Alien protocol killX = killY = kill2X = kill2Y = -1; if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move + if(legs++) return; // middle leg contains only redundant info, ignore (but count it) safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives return; } if(p = strchr(machineMove, ',')) { // we got both legs in one (happens on book move) + char *q = strchr(p+1, ','); // second comma? safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off + if(q) legs = 2, p = q; else legs = 1; // with 3-leg move we clipof first two legs! safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20); } if(firstLeg[0]) { // there was a previous leg; @@ -8841,10 +8853,11 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h while(isdigit(*q)) q++; // find start of to-square safeStrCpy(machineMove, firstLeg, 20); while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove) - if(*p == *buf) // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling) + if(legs == 2) sscanf(p, "%c%d", &f, &kill2Y), kill2X = f - AAA, kill2Y -= ONE - '0'; // in 3-leg move 2nd kill is to-sqr of 1st leg + else if(*p == *buf) // if first-leg to not equal to second-leg from first leg says unmodified (assume it is King move of castling) safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global - firstLeg[0] = NULLCHAR; + firstLeg[0] = NULLCHAR; legs = 0; } if (!ParseOneMove(machineMove, forwardMostMove, &moveType, @@ -10213,7 +10226,7 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B; } - if (fromX == toX && fromY == toY) return; + if (fromX == toX && fromY == toY && killX < 0) return; piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */ king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */ @@ -10493,13 +10506,17 @@ MakeMove (int fromX, int fromY, int toX, int toY, int promoChar) ChessSquare p = boards[forwardMostMove][toY][toX]; // forwardMostMove++; // [HGM] bare: moved downstream + if(kill2X >= 0) x = kill2X, y = kill2Y; else if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one (void) CoordsToAlgebraic(boards[forwardMostMove], PosFlags(forwardMostMove), fromY, fromX, y, x, (killX < 0)*promoChar, s); + if(kill2X >= 0 && kill2Y >= 0) + sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture if(killX >= 0 && killY >= 0) - sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0', promoChar); + sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x', + toX + AAA, toY + ONE - '0', promoChar); if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */ int timeLeft; static int lastLoadFlag=0; int king, piece; @@ -10613,7 +10630,7 @@ ShowMove (int fromX, int fromY, int toX, int toY) currentMove = forwardMostMove; } - killX = killY = -1; // [HGM] lion: used up + killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up if (instant) return; @@ -11456,7 +11473,7 @@ GameEnds (ChessMove result, char *resultDetails, int whosays) result, resultDetails ? resultDetails : "(null)", whosays); } - fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion + fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit) @@ -11954,7 +11971,7 @@ Reset (int redraw, int init) ClearPremoveHighlights(); gotPremove = FALSE; alarmSounded = FALSE; - killX = killY = -1; // [HGM] lion + killX = killY = kill2X = kill2Y = -1; // [HGM] lion GameEnds(EndOfFile, NULL, GE_PLAYER); if(appData.serverMovesName != NULL) { @@ -12338,7 +12355,7 @@ LoadGameOneMove (ChessMove readAhead) thinkOutput[0] = NULLCHAR; MakeMove(fromX, fromY, toX, toY, promoChar); - killX = killY = -1; // [HGM] lion: used up + killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up currentMove = forwardMostMove; return TRUE; } @@ -12925,7 +12942,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList) if (gameMode != BeginningOfGame) { Reset(FALSE, TRUE); } - killX = killY = -1; // [HGM] lion: in case we did not Reset + killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset gameFileFP = f; if (lastLoadGameFP != NULL && lastLoadGameFP != f) { @@ -15369,7 +15386,7 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y) switch (selection) { case ClearBoard: - fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress + fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress MarkTargetSquares(1); CopyBoard(currentBoard, boards[0]); CopyBoard(menuBoard, initialPosition); @@ -15822,7 +15839,7 @@ ForwardInner (int target) seekGraphUp = FALSE; MarkTargetSquares(1); - fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress + fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress if (gameMode == PlayFromGameFile && !pausing) PauseEvent(); @@ -15941,7 +15958,7 @@ BackwardInner (int target) if (gameMode == EditPosition) return; seekGraphUp = FALSE; MarkTargetSquares(1); - fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress + fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress if (currentMove <= backwardMostMove) { ClearHighlights(); DrawPosition(full_redraw, boards[currentMove]); diff --git a/moves.c b/moves.c index 867a940..d0fc45e 100644 --- a/moves.c +++ b/moves.c @@ -460,11 +460,14 @@ MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle if(!(bit & all)) *atom = rotate[*atom - 'A']; // orth-diag interconversion to make direction valid if(occup & mode & 0x104) // no side effects, merge legs to one move MovesFromString(board, flags, f, r, x, y, dir, cont, cb, cl); - if(occup & mode & 3 && (killX < 0 || killX == x && killY == y)) { // destructive first leg + if(occup & mode & 3 && (killX < 0 || kill2X < 0 && (legNr > 1 || killX == x && killY == y) || + (legNr == 1 ? kill2X == x && kill2Y == y : killX == x && killY == y))) { // destructive first leg int cnt = 0; + legNr <<= 1; MovesFromString(board, flags, f, r, x, y, dir, cont, &OK, &cnt); // count possible continuations + legNr >>= 1; if(cnt) { // and if there are - if(killX < 0) cb(board, flags, FirstLeg, r, f, y, x, cl); // then generate their first leg + if(legNr & 1 ? killX < 0 : kill2X < 0) cb(board, flags, FirstLeg, r, f, y, x, cl); // then generate their first leg legNr <<= 1; MovesFromString(board, flags, f, r, x, y, dir, cont, cb, cl); legNr >>= 1; @@ -1677,7 +1680,7 @@ CheckTest (Board board, int flags, int rf, int ff, int rt, int ft, int enPassant { CheckTestClosure cl; ChessSquare king = flags & F_WHITE_ON_MOVE ? WhiteKing : BlackKing; - ChessSquare captured = EmptySquare, ep=0, trampled=0; + ChessSquare captured = EmptySquare, ep=0, trampled=0, trampled2 = 0; int saveKill = killX; /* Suppress warnings on uninitialized variables */ @@ -1702,7 +1705,10 @@ CheckTest (Board board, int flags, int rf, int ff, int rt, int ft, int enPassant board[rf][ft] = EmptySquare; } else { captured = board[rt][ft]; - if(killX >= 0) { trampled = board[killY][killX]; board[killY][killX] = EmptySquare; killX = -1; } + if(killX >= 0) { + trampled = board[killY][killX]; board[killY][killX] = EmptySquare; killX = -1; saveKill += (kill2X << 16) + (1 << 30); + if(kill2X >= 0) { trampled2 = board[kill2Y][kill2X]; board[kill2Y][kill2X] = EmptySquare; kill2X = -1; } + } } if(rf == DROP_RANK) board[rt][ft] = ff; else { // [HGM] drop board[rt][ft] = board[rf][ff]; @@ -1751,7 +1757,10 @@ CheckTest (Board board, int flags, int rf, int ff, int rt, int ft, int enPassant board[rf][ft] = captured; board[rt][ft] = EmptySquare; } else { - if(saveKill >= 0) board[killY][killX = saveKill] = trampled; + if(saveKill >= 0) { + if(saveKill & 1<<30) board[kill2Y][kill2X = saveKill >> 16 & 0xFFF] = trampled2; + board[killY][killX = saveKill & 0xFFF] = trampled; + } board[rt][ft] = captured; } board[EP_STATUS] = ep; diff --git a/moves.h b/moves.h index 5ace504..4a02a19 100644 --- a/moves.h +++ b/moves.h @@ -182,4 +182,4 @@ ChessMove CoordsToAlgebraic P((Board board, int flags, int rf, int ff, int rt, int ft, int promoChar, char out[MOVE_LEN])); -extern int quickFlag, killX, killY, legNr; +extern int quickFlag, killX, killY, kill2X, kill2Y, legNr; diff --git a/parser.c b/parser.c index 1d18403..c2eda2b 100644 --- a/parser.c +++ b/parser.c @@ -510,19 +510,32 @@ NextUnit (char **p) } if( type[1] != type[2] && // means fromY is of opposite type as ToX, or NOTHING (type[0] == NOTHING || type[0] == type[2]) ) { // well formed - + int suffix = 7; fromX = (currentMoveString[0] = coord[0] + 'a') - AAA; fromY = (currentMoveString[1] = coord[1] + '0') - ONE; currentMoveString[4] = cl.promoCharIn = PromoSuffix(p); currentMoveString[5] = NULLCHAR; + if(**p == 'x' && !cl.promoCharIn) { // other leg follows + char *q = *p; + int x = *++*p, y; + ++*p; y = Number(p); + if(**p == '-' || **p == 'x') { // 3-leg move! + currentMoveString[7] = (kill2X = toX) + AAA; // what we thought was to-square is in fact 1st kill-square of two + currentMoveString[8] = (kill2Y = toY) + ONE; // append it after 2nd kill-square + toX = x - AAA; // kludge alert: this will become 2nd kill square + toY = y + '0' - ONE; + suffix += 2; + } else *p = q; // 2-leg move, rewind to leave reading of 2nd leg to code below + } if(!cl.promoCharIn && (**p == '-' || **p == 'x')) { // Lion-type multi-leg move currentMoveString[5] = (killX = toX) + AAA; // what we thought was to-square is in fact kill-square currentMoveString[6] = (killY = toY) + ONE; // append it as suffix behind long algebraic move currentMoveString[4] = ';'; - currentMoveString[7] = NULLCHAR; + currentMoveString[suffix+1] = NULLCHAR; // read new to-square (VERY non-robust! Assumes correct (non-alpha-rank) syntax, and messes up on errors) toX = cl.ftIn = (currentMoveString[2] = *++*p) - AAA; ++*p; toY = cl.rtIn = (currentMoveString[3] = Number(p) + '0') - ONE; + currentMoveString[suffix] = cl.promoCharIn = PromoSuffix(p); } if(type[0] != NOTHING && type[1] != NOTHING && type[3] != NOTHING) { // fully specified. ChessSquare realPiece = boards[yyboardindex][fromY][fromX]; -- 1.7.0.4