From ac3ecf1541dc65208047ecaae2ff3904936506e3 Mon Sep 17 00:00:00 2001 From: H.G.Muller Date: Thu, 20 Oct 2022 15:05:06 +0200 Subject: [PATCH] Implement Duck Chess An SVG image for a Duck is added. Communication with engine uses the 'Alien Edition' protocol, where a non-final leg is printed by the engine on a separate line, suffixed by a comma. Peculiarity is that the destination of the FIDE piece has to be written in place of the old Duck location, and that the promotion suffix has to be on the final leg. SAN writes the Duck destination behind the move, separated from it by a comma. In this case the promotion suffix is on the first leg. --- Makefile.am | 2 +- backend.c | 61 ++++++++++++--- board.c | 4 +- common.h | 2 + dialogs.c | 3 +- draw.c | 15 ++-- parser.c | 7 ++ svg/WhiteDucky.svg | 224 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 298 insertions(+), 20 deletions(-) create mode 100644 svg/WhiteDucky.svg diff --git a/Makefile.am b/Makefile.am index a3051ba..4bea85d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -169,7 +169,7 @@ dist_svg_DATA = svg/icon_white.svg svg/icon_black.svg \ svg/eo_Analyzing.svg svg/eo_Black.svg \ svg/eo_Clear.svg svg/eo_Ponder.svg \ svg/eo_Thinking.svg svg/eo_Unknown.svg \ - svg/eo_White.svg + svg/eo_White.svg svg/WhiteDucky.svg shogidir = $(gamedatadir)/themes/shogi dist_shogi_DATA = \ diff --git a/backend.c b/backend.c index 1b78234..c092efb 100644 --- a/backend.c +++ b/backend.c @@ -1273,6 +1273,7 @@ InitBackEnd1 () case VariantLion: /* should work */ case VariantChuChess: /* should work */ case VariantJanggi: + case VariantDuck: break; } } @@ -7083,7 +7084,7 @@ int lastLoadGameNumber = 0, lastLoadPositionNumber = 0; int lastLoadGameUseList = FALSE; char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ]; ChessMove lastLoadGameStart = EndOfFile; -int doubleClick; +int doubleClick, doDuck = -1, duckX, duckY; Boolean addToBookFlag; static Board rightsBoard, nullBoard; @@ -7091,7 +7092,7 @@ void UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar) { ChessMove moveType; - ChessSquare pup; + ChessSquare pup = boards[currentMove][fromY][fromX]; int ff=fromX, rf=fromY, ft=toX, rt=toY; /* Check if the user is playing in turn. This is complicated because we @@ -7139,14 +7140,14 @@ UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar) case AnalyzeMode: case Training: if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move - if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn && - (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) { + if ((int) pup >= (int) BlackPawn && + (int) pup < (int) EmptySquare) { /* User is moving for Black */ if (WhiteOnMove(currentMove)) { DisplayMoveError(_("It is White's turn")); return; } - } else { + } else if(pup < (int) BlackPawn) { /* User is moving for White */ if (!WhiteOnMove(currentMove)) { DisplayMoveError(_("It is Black's turn")); @@ -7384,6 +7385,17 @@ FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int prom ClearMap(); + if(gameInfo.variant == VariantDuck) { + if(doDuck < 0) { // Duck move not yet indicated + Board testBoard; + CopyBoard(testBoard, boards[forwardMostMove]); // do the move without Duck + ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard); + DrawPosition(TRUE, testBoard); + duckX = fromX; duckY = fromY; doDuck = promoChar; // remember promotion + return 1; + } + } + /* If we need the chess program but it's dead, restart it */ ResurrectChessProgram(); @@ -7685,6 +7697,18 @@ LeftClick (ClickType clickType, int xPix, int yPix) x = BOARD_WIDTH - 1 - x; } + saveAnimate = appData.animate; + if(clickType == Press && doDuck >= 0) { // extra click for Duck placement + if(boards[currentMove][y][x] != EmptySquare && (x != duckX || y != duckY)) return; // ignore clicks on occupied square + killX = x; killY = y; + appData.animate = FALSE; + UserMoveEvent(duckX, duckY, toX, toY, doDuck); // promoChoice remembered in doDuck + fromX = fromY = killX = killY = -1; + appData.animate = saveAnimate; + doDuck = -1; + return; + } + if(gameMode == EditPosition && fromX < 0 && selectedType != EmptySquare && (boards[currentMove][y][x] == EmptySquare || x == createX && y == createY)) { // placement click if(clickType == Press) { @@ -7950,7 +7974,6 @@ LeftClick (ClickType clickType, int xPix, int yPix) piece = boards[currentMove][fromY][fromX]; - saveAnimate = appData.animate; if (clickType == Press) { if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece; if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) { @@ -9120,9 +9143,13 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h 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; + if(firstLeg[0]) { // there was a previous leg + char buf[20], *p = machineMove+1, *q = buf+1, f; + if(gameInfo.variant == VariantDuck) { // Duck Chess: 1st leg is FIDE move, 2nd is Duck + sscanf(machineMove, "%c%d%c%d", &f, &killY, &f, &killY); killX = f - AAA; killY -= ONE - '0'; + safeStrCpy(machineMove, firstLeg, 20); + } else { // only support case where same piece makes two step - char buf[20], *p = machineMove+1, *q = buf+1, f; safeStrCpy(buf, machineMove, 20); while(isdigit(*q)) q++; // find start of to-square safeStrCpy(machineMove, firstLeg, 20); @@ -9131,7 +9158,8 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h 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; legs = 0; + } + firstLeg[0] = NULLCHAR; legs = 0; } if (!ParseOneMove(machineMove, forwardMostMove, &moveType, @@ -10525,6 +10553,11 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something // victim = board[killY][killX], + if(gameInfo.variant == VariantDuck) { // killXY used to indicate Duck move + int r, f; + for(r=0; r= 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) + if(killX >= 0 && killY >= 0) { + if(gameInfo.variant == VariantDuck) { + if(promoChar) sprintf(s + strlen(s), "=%c,%c%d", ToUpper(promoChar), killX + AAA, killY + ONE - '0'); + else sprintf(s + strlen(s), ",%c%d", killX + AAA, killY + ONE - '0'); + } else 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; diff --git a/board.c b/board.c index c13082f..2c1f7bb 100644 --- a/board.c +++ b/board.c @@ -605,7 +605,7 @@ AnimateMove (Board board, int fromX, int fromY, int toX, int toY) if (piece >= EmptySquare) return; if(x2 >= 0) toX = kill2X, toY = kill2Y; else - if(killX >= 0) toX = killX, toY = killY; // [HGM] lion: first to kill square + if(killX >= 0 && gameInfo.variant != VariantDuck) toX = killX, toY = killY; // [HGM] lion: first to kill square again: @@ -814,7 +814,7 @@ DrawSquare (int row, int column, ChessSquare piece, int do_flash) snprintf(tString, 3, "%d", piece); align = 4; // holdings count in upper-left corner } - if(piece == DarkSquare) square_color = 2; + if(piece == DarkSquare) square_color = (gameInfo.variant == VariantDuck ? 3 : 2); if(square_color == 2 || appData.blindfold) piece = EmptySquare; if (do_flash && piece != EmptySquare && appData.flashCount > 0) { diff --git a/common.h b/common.h index 4dbb76a..ee516f4 100644 --- a/common.h +++ b/common.h @@ -415,6 +415,7 @@ typedef enum { VariantLion, VariantChuChess, VariantJanggi, + VariantDuck, VariantUnknown /* Catchall for other unknown variants */ } VariantClass; @@ -466,6 +467,7 @@ typedef enum { "lion",\ "elven",\ "janggi",\ + "duck",\ "unknown" \ } diff --git a/dialogs.c b/dialogs.c index 731e423..753b331 100644 --- a/dialogs.c +++ b/dialogs.c @@ -498,7 +498,8 @@ static Option variantDescriptors[] = { { VariantJanggi, SAME_ROW, 135, NULL, (void*) &Pick, "#BFFFFF", NULL, Button, N_("Janggi (9x10)")}, // dummy, to have good alignment { VariantChuChess, 0, 135, NULL, (void*) &Pick, "#BFBFBF", NULL, Button, N_("elven chess (10x10)")}, { VariantCourier, SAME_ROW,135, NULL, (void*) &Pick, "#BFFFBF", NULL, Button, N_("courier (12x8)")}, -{ -1, 0, 135, NULL, (void*) &Pick, "#FFFFFF", NULL, Button, N_(" ")}, // dummy, to have good alignment +{ VariantDuck, 0, 135, NULL, (void*) &Pick, "#FFFFFF", NULL, Button, N_("Duck Chess")}, +//{ -1, 0, 135, NULL, (void*) &Pick, "#FFFFFF", NULL, Button, N_(" ")}, // dummy, to have good alignment { VariantChu, SAME_ROW, 135, NULL, (void*) &Pick, "#BFFFBF", NULL, Button, N_("chu shogi (12x12)")}, // optional buttons for engine-defined variants { 0, NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }, diff --git a/draw.c b/draw.c index a05d41a..7d1792f 100644 --- a/draw.c +++ b/draw.c @@ -106,10 +106,10 @@ extern char *getenv(); Boolean cairoAnimate; Option *currBoard; cairo_surface_t *csBoardWindow; -static cairo_surface_t *pngPieceImages[2][(int)BlackPawn]; // png 256 x 256 images -static cairo_surface_t *pngPieceBitmaps[2][(int)BlackPawn]; // scaled pieces as used -static cairo_surface_t *pngPieceBitmaps2[2][(int)BlackPawn]; // scaled pieces in store -static RsvgHandle *svgPieces[2][(int)BlackPawn]; // vector pieces in store +static cairo_surface_t *pngPieceImages[2][(int)BlackPawn+1]; // png 256 x 256 images +static cairo_surface_t *pngPieceBitmaps[2][(int)BlackPawn+1]; // scaled pieces as used +static cairo_surface_t *pngPieceBitmaps2[2][(int)BlackPawn+1]; // scaled pieces in store +static RsvgHandle *svgPieces[2][(int)BlackPawn+1]; // vector pieces in store static cairo_surface_t *pngBoardBitmap[2], *pngOriginalBoardBitmap[2]; int useTexture, textureW[2], textureH[2]; @@ -165,7 +165,7 @@ SelectPieces(VariantClass v) int i; for(i=0; i<2; i++) { int p; - for(p=0; p<=(int)WhiteKing; p++) + for(p=0; p<=(int)WhiteKing+1; p++) pngPieceBitmaps[i][p] = pngPieceBitmaps2[i][p]; // defaults if(v == VariantShogi && BOARD_HEIGHT != 7) { // no exceptions in Tori Shogi pngPieceBitmaps[i][(int)WhiteCannon] = pngPieceBitmaps2[i][(int)WhiteTokin]; @@ -311,7 +311,7 @@ char *pngPieceNames[] = // must be in same order as internal piece encoding "LShield", "Pegasus", "Wizard", "Copper", "Iron", "Viking", "Flag", "Axe", "Dolphin", "Leopard", "Claw", "Left", "Butterfly", "PromoBishop", "PromoRook", "HCrown", "RShield", "Prince", "Phoenix", "Kylin", "Drunk", "Right", "GoldPawn", "GoldKnight", "PromoHorse", "PromoDragon", "GoldLance", "GoldSilver", "HSword", "PromoSword", "PromoHSword", "Princess", "King", - NULL + "Ducky", NULL }; char *backupPiece[] = { // pieces that map on other in default theme ("Crown" - "Drunk") @@ -494,6 +494,7 @@ CreatePNGPieces (char *pieceDir) int p; for(p=0; pngPieceNames[p]; p++) { ScaleOnePiece(0, p, pieceDir); + if(p == BlackPawn) break; // no black Duck ScaleOnePiece(1, p, pieceDir); } SelectPieces(gameInfo.variant); @@ -791,6 +792,7 @@ BlankSquare (cairo_surface_t *dest, int x, int y, int color, ChessSquare piece, case 0: col = appData.darkSquareColor; break; case 1: col = appData.lightSquareColor; break; case 2: col = "#000000"; break; + case 3: col = "#6080C0"; break; default: col = "#808080"; break; // cannot happen } SetPen(cr, 2.0, col, 0); @@ -821,6 +823,7 @@ pngDrawPiece (cairo_surface_t *dest, ChessSquare piece, int square_color, int x, } if(piece == WhiteKing && kind == appData.jewelled) piece = WhiteZebra; if(appData.upsideDown && flipView) kind = 1 - kind; // swap white and black pieces + if(square_color == 3) piece = BlackPawn, kind = 0; // Ducky BlankSquare(dest, x, y, square_color, piece, 1); // erase previous contents with background cr = cairo_create (dest); cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y); diff --git a/parser.c b/parser.c index 5246944..630a782 100644 --- a/parser.c +++ b/parser.c @@ -523,6 +523,13 @@ NextUnit (char **p) fromY = (currentMoveString[1] = coord[1] + '0') - ONE; currentMoveString[4] = cl.promoCharIn = PromoSuffix(p); currentMoveString[5] = NULLCHAR; + if(**p == ',' && gameInfo.variant == VariantDuck) { // Duck square follows + currentMoveString[7] = currentMoveString[4]; + currentMoveString[4] = ';'; + currentMoveString[5] = *++*p; killX = **p - AAA; + currentMoveString[6] = *++*p; killY = *(*p)++ - ONE; + currentMoveString[8] = NULLCHAR; + } if(**p == 'x' && !cl.promoCharIn) { // other leg follows char *q = *p; int x = *++*p, y; diff --git a/svg/WhiteDucky.svg b/svg/WhiteDucky.svg new file mode 100644 index 0000000..4f3866a --- /dev/null +++ b/svg/WhiteDucky.svg @@ -0,0 +1,224 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- 1.7.0.4