char*
safeStrCpy( char *dst, const char *src, size_t count )
-{
- /* see for example: https://buildsecurityin.us-cert.gov/bsi-rules/home/g1/854-BSI.html
- *
- * usage: safeStrCpy( stringA, stringB, sizeof(stringA)/sizeof(stringA[0]);
- */
-
+{ // [HGM] made safe
+ int i;
assert( dst != NULL );
assert( src != NULL );
assert( count > 0 );
- strncpy( dst, src, count );
- if( dst[ count-1 ] != '\0' )
+ for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
+ if( i == count-1 && dst[i] != NULLCHAR)
{
+ dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
if(appData.debugMode)
printf("safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
}
- dst[ count-1 ] = '\0';
return dst;
}
case VariantJanus: /* should work */
case VariantSuper: /* experimental */
case VariantGreat: /* experimental, requires legality testing to be off */
+ case VariantSChess: /* S-Chess, should work */
break;
}
}
int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
char to_play, board_chars[200];
- char move_str[500], str[500], elapsed_time[500];
+ char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
char black[32], white[32];
Board board;
int prevMove = currentMove;
&ticking);
if (n < 21) {
- snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
+ snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
DisplayError(str, 0);
return;
}
if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
if(appData.debugMode)
fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
- safeStrCpy(move_str, buf, sizeof(move_str)/sizeof(move_str[0]));
+ safeStrCpy(move_str, buf, MSG_SIZ);
}
valid = ParseOneMove(move_str, moveNum - 1, &moveType,
&fromX, &fromY, &toX, &toY, &promoChar)
AAA + ff, ONE + rf, AAA + ft, ONE + rt);
} else {
sprintf(move, "%c%c%c%c%c\n",
- AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar == '^' ? '+' : promoChar);
+ AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
}
}
}
int *fromX, *fromY, *toX, *toY;
char *promoChar;
{
- char moveCopy[20], *p = moveCopy;
- strncpy(moveCopy, move, 20); // make a copy of move to preprocess it
- if(gameInfo.variant == VariantShogi) {
- while(*p && *p != ' ') p++;
- if(p[-1] == '+') p[-1] = '^'; // in Shogi '+' is promotion, distinguish from check
- }
- if (appData.debugMode) {
- fprintf(debugFP, "move to parse: %s\n", moveCopy);
- }
- *moveType = yylexstr(moveNum, moveCopy, yy_textstr, sizeof yy_textstr);
+ *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
switch (*moveType) {
case WhitePromotion:
if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
SetCharTable(pieceNickName, appData.pieceNickNames);
else SetCharTable(pieceNickName, "............");
+ pieces = FIDEArray;
switch (gameInfo.variant) {
case VariantFischeRandom:
shuffleOpenings = TRUE;
default:
- pieces = FIDEArray;
break;
case VariantShatranj:
pieces = ShatranjArray;
gameInfo.boardWidth = 10;
SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
break;
+ case VariantSChess:
+ SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
+ gameInfo.holdingsSize = 7;
+ break;
case VariantJanus:
pieces = JanusArray;
gameInfo.boardWidth = 10;
initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
}
+ if( gameInfo.variant == VariantSChess ) {
+ initialPosition[1][0] = BlackMarshall;
+ initialPosition[2][0] = BlackAngel;
+ initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
+ initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
+ initialPosition[1][1] = initialPosition[2][1] =
+ initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
+ }
if (appData.debugMode) {
fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
}
/* Kludge to set black to move, avoiding the troublesome and now
* deprecated "black" command.
*/
- if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
+ if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
+ SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
SendToProgram("edit\n", cps);
SendToProgram("#\n", cps);
if(toY == 0 && piece == BlackPawn ||
toY == 0 && piece == BlackQueen ||
toY <= 1 && piece == BlackKnight) {
- *promoChoice = '^';
+ *promoChoice = '+';
return FALSE;
}
} else {
if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
- *promoChoice = '^';
+ *promoChoice = '+';
return FALSE;
}
}
gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
if(appData.testLegality && !premove) {
moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
- fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '^' : NULLCHAR);
+ fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
if(moveType != WhitePromotion && moveType != BlackPromotion)
return FALSE;
}
return FALSE;
}
+ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
+
void LeftClick(ClickType clickType, int xPix, int yPix)
{
int x, y;
autoQueen = appData.alwaysPromoteToQueen;
if (fromX == -1) {
+ gatingPiece = EmptySquare;
if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
if (clickType == Press) {
/* First square */
/* Check if clicking again on the same color piece */
fromP = boards[currentMove][fromY][fromX];
toP = boards[currentMove][y][x];
- frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
+ frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
if ((WhitePawn <= fromP && fromP <= WhiteKing &&
WhitePawn <= toP && toP <= WhiteKing &&
!(fromP == WhiteKing && toP == WhiteRook && frc) &&
ClearHighlights();
}
if (OKToStartUserMove(x, y)) {
+ if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
+ (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
+ y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
+ gatingPiece = boards[currentMove][fromY][fromX];
+ else gatingPiece = EmptySquare;
fromX = x;
fromY = y; dragging = 1;
MarkTargetSquares(0);
DragPieceBegin(xPix, yPix);
}
- return;
}
+ if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
+ second = FALSE;
}
// ignore clicks on holdings
if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
/* Second up/down in same square; just abort move */
second = 0;
fromX = fromY = -1;
+ gatingPiece = EmptySquare;
ClearHighlights();
gotPremove = 0;
ClearPremoveHighlights();
}
// off-board moves should not be highlighted
- if(x < 0 || x < 0) ClearHighlights();
+ if(x < 0 || y < 0) ClearHighlights();
+
+ if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
SetHighlights(fromX, fromY, toX, toY);
return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
}
+ if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
+ int dummy, s=6; char buf[MSG_SIZ];
+ if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
+ if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
+ ParseFEN(boards[0], &dummy, message+s);
+ DrawPosition(TRUE, boards[0]);
+ startedFromSetupPosition = TRUE;
+ return;
+ }
/* [HGM] Allow engine to set up a position. Don't ask me why one would
* want this, I was asked to put it in, and obliged.
*/
* Look for communication commands
*/
if (!strncmp(message, "telluser ", 9)) {
- EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
+ if(message[9] == '\\' && message[10] == '\\')
+ EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
DisplayNote(message + 9);
return;
}
if (!strncmp(message, "tellusererror ", 14)) {
cps->userError = 1;
- EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
+ if(message[14] == '\\' && message[15] == '\\')
+ EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
DisplayError(message + 14, 0);
return;
}
yynewstr(game);
for (;;) {
yyboardindex = boardIndex;
- moveType = (ChessMove) yylex();
+ moveType = (ChessMove) Myylex();
switch (moveType) {
case IllegalMove: /* maybe suicide chess, etc. */
if (appData.debugMode) {
if( board[toY][toX] != EmptySquare )
board[EP_STATUS] = EP_CAPTURE;
- /* [HGM] In Shatranj and Courier all promotions are to Ferz */
- if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
- && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
-
if (fromY == DROP_RANK) {
/* must be first */
piece = board[toY][toX] = (ChessSquare) fromX;
king += (int) WhiteUnicorn - (int) WhiteKing;
/* Code added by Tord: */
- /* FRC castling assumed when king captures friendly rook. */
- if (board[fromY][fromX] == WhiteKing &&
- board[toY][toX] == WhiteRook) {
+ /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
+ if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
+ board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
board[fromY][fromX] = EmptySquare;
board[toY][toX] = EmptySquare;
- if(toX > fromX) {
+ if((toX > fromX) != (piece == WhiteRook)) {
board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
} else {
board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
}
- } else if (board[fromY][fromX] == BlackKing &&
- board[toY][toX] == BlackRook) {
+ } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
+ board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
board[fromY][fromX] = EmptySquare;
board[toY][toX] = EmptySquare;
- if(toX > fromX) {
+ if((toX > fromX) != (piece == BlackRook)) {
board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
} else {
board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
/* [HGM] OK, so I have written it. Holdings are stored in the */
/* penultimate board files, so they are automaticlly stored */
/* in the game history. */
- if (fromY == DROP_RANK) {
+ if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
+ && promoChar && piece != WhitePawn && piece != BlackPawn) {
/* Delete from holdings, by decreasing count */
/* and erasing image if necessary */
- p = (int) fromX;
+ p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
if(p < (int) BlackPawn) { /* white drop */
p -= (int)WhitePawn;
p = PieceToNumber((ChessSquare)p);
}
}
if (captured != EmptySquare && gameInfo.holdingsSize > 0
- && gameInfo.variant != VariantBughouse ) {
+ && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
/* [HGM] holdings: Add to holdings, if holdings exist */
if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
// [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
board[toY][toX] = EmptySquare;
}
}
- if(promoChar == '^') {
+ if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
+ board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
+ } else
+ if(promoChar == '+') {
/* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
board[toY][toX] = (ChessSquare) (PROMOTED piece);
- } else if(!appData.testLegality) { // without legality testing, unconditionally believe promoChar
- board[toY][toX] = CharToPiece(promoChar);
+ } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
+ board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
}
if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
&& promoChar != NULLCHAR && gameInfo.holdingsSize) {
} else {
if (gameFileFP == NULL)
return FALSE;
- moveType = (ChessMove) yylex();
+ moveType = (ChessMove) Myylex();
}
done = FALSE;
cm = lastLoadGameStart = EndOfFile;
while (gn > 0) {
yyboardindex = forwardMostMove;
- cm = (ChessMove) yylex();
+ cm = (ChessMove) Myylex();
switch (cm) {
case EndOfFile:
if (cmailMsgLoaded) {
if (gn > 0) {
do {
yyboardindex = forwardMostMove;
- cm = (ChessMove) yylex();
+ cm = (ChessMove) Myylex();
} while (cm == PGNTag || cm == Comment);
}
break;
/* Skip any header junk before position diagram and/or move 1 */
for (;;) {
yyboardindex = forwardMostMove;
- cm = (ChessMove) yylex();
+ cm = (ChessMove) Myylex();
if (cm == EndOfFile ||
cm == GNUChessGame || cm == XBoardGame) {
}
yyboardindex = forwardMostMove;
- cm = (ChessMove) yylex();
+ cm = (ChessMove) Myylex();
/* Handle comments interspersed among the tags */
while (cm == Comment) {
p = yy_text;
AppendComment(currentMove, p, FALSE);
yyboardindex = forwardMostMove;
- cm = (ChessMove) yylex();
+ cm = (ChessMove) Myylex();
}
}
}
}
yyboardindex = forwardMostMove;
- cm = (ChessMove) yylex();
+ cm = (ChessMove) Myylex();
}
if (first.pr == NoProc) {
p = yy_text;
AppendComment(currentMove, p, FALSE);
yyboardindex = forwardMostMove;
- cm = (ChessMove) yylex();
+ cm = (ChessMove) Myylex();
}
if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
}
void
+SettingsMenuIfReady()
+{
+ if (second.lastPing != second.lastPong) {
+ DisplayMessage("", _("Waiting for second chess program"));
+ ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
+ return;
+ }
+ ThawUI();
+ DisplayMessage("", "");
+ SettingsPopUp(&second);
+}
+
+int
+WaitForSecond(DelayedEventCallback retry)
+{
+ if (second.pr == NULL) {
+ StartChessProgram(&second);
+ if (second.protocolVersion == 1) {
+ retry();
+ } else {
+ /* kludge: allow timeout for initial "feature" command */
+ FreezeUI();
+ DisplayMessage("", _("Starting second chess program"));
+ ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
+ }
+ return 1;
+ }
+ return 0;
+}
+
+void
TwoMachinesEvent P((void))
{
int i;
TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
ResurrectChessProgram(); /* in case first program isn't running */
- if (second.pr == NULL) {
- StartChessProgram(&second);
- if (second.protocolVersion == 1) {
- TwoMachinesEventIfReady();
- } else {
- /* kludge: allow timeout for initial "feature" command */
- FreezeUI();
- DisplayMessage("", _("Starting second chess program"));
- ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
- }
- return;
- }
+ if(WaitForSecond(TwoMachinesEventIfReady)) return;
DisplayMessage("", "");
InitChessProgram(&second, FALSE);
SendToProgram("force\n", &second);
+ if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
+ ScheduleDelayedEvent(TwoMachinesEvent, 10);
+ return;
+ }
if (startedFromSetupPosition) {
SendBoard(&second, backwardMostMove);
if (appData.debugMode) {
} else {
commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
if(addBraces)
- safeStrCpy(commentList[index], "{\n", sizeof(commentList[index])/sizeof(commentList[index][0]));
+ safeStrCpy(commentList[index], "{\n", 3);
else commentList[index][0] = NULLCHAR;
strcat(commentList[index], text);
strcat(commentList[index], "\n");
sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
+ sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
+ sscanf(message, "hint: %c", &c)!=1 &&
sscanf(message, "pong %c", &c)!=1 && start != '#') {
quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
print = (appData.engineComments >= 2);
if(p && (p == cps->optionSettings || p[-1] == ',')) {
snprintf(buf, MSG_SIZ, "option %s", p);
if(p = strstr(buf, ",")) *p = 0;
+ if(q = strchr(buf, '=')) switch(opt->type) {
+ case ComboBox:
+ for(n=0; n<opt->max; n++)
+ if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
+ break;
+ case TextBox:
+ safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
+ break;
+ case Spin:
+ case CheckBox:
+ opt->value = atoi(q+1);
+ default:
+ break;
+ }
strcat(buf, "\n");
SendToProgram(buf, cps);
}
{
DelayedEventCallback cb = GetDelayedEvent();
if ((cb == InitBackEnd3 && cps == &first) ||
+ (cb == SettingsMenuIfReady && cps == &second) ||
(cb == TwoMachinesEventIfReady && cps == &second)) {
CancelDelayedEvent();
ScheduleDelayedEvent(cb, val ? 1 : 3600000);