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;
+int solvingTime, totalTime, jawsClock;
/* States for ics_getting_history */
#define H_FALSE 0
case VariantChu:
case VariantChuChess:
case VariantLion:
+ case VariantJanggi:
flags |= F_NULL_MOVE;
break;
+ case VariantDuck:
+ flags |= F_IGNORE_CHECK;
default:
break;
}
*/
char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
-char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
+char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ], hintSrc;
char thinkOutput1[MSG_SIZ*10];
-char promoRestrict[MSG_SIZ];
+char promoRestrict[MSG_SIZ], defaultChoice[MSG_SIZ];
ChessProgramState first, second, pairing;
ExitAnalyzeMode();
DoSleep( appData.delayBeforeQuit );
SendToProgram("quit\n", cps);
- DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
+ DestroyChildProcess(cps->pr, 4*!cps->isUCI + cps->useSigterm);
}
cps->pr = NoProc;
if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
void
AddToEngineList (int i)
{
+ if(addToList) {
int len;
char quote, buf[MSG_SIZ];
char *q = firstChessProgramNames, *p = newEngineCommand;
SaveEngineList();
FloatToFront(&appData.recentEngineList, buf);
ASSIGN(currentEngine[i], buf);
+ }
}
void
case VariantSpartan: /* should work */
case VariantLion: /* should work */
case VariantChuChess: /* should work */
+ case VariantJanggi:
+ case VariantDuck:
break;
}
}
appData.loadPositionFile)) {
DisplayFatalError(_("Bad position file"), 0, 1);
return 0;
- }
+ } else startedFromSetupPosition = TRUE;
}
return 1;
}
if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
safeStrCpy(buf, lastMsg, MSG_SIZ);
DisplayMessage(_("Pick new game"), "");
- flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
+ if(flock(fileno(tf), LOCK_EX)) { // lock the tourney file while we are messing with it
+ abortMatch = TRUE; appData.matchMode = FALSE; DisplayError("Access to tourney file denied", 0);
+ fclose(tf); return;
+ }
ParseArgsFromFile(tf);
p = q = appData.results;
if(appData.debugMode) {
appData.remoteUser, appData.telnetProgram,
appData.icsHost, appData.icsPort);
}
- return StartChildProcess(buf, "", &icsPR);
+ return StartChildProcess(buf, "", &icsPR, 0);
}
} else if (appData.useTelnet) {
return TRUE;
}
+int remoteEchoOption = FALSE; /* telnet ECHO option */
+
void
read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
{
telnet server that will try to keep WILL ECHO on permanently.
*/
if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
- static int remoteEchoOption = FALSE; /* telnet ECHO option */
unsigned char option;
oldi = i;
switch ((unsigned char) buf[++i]) {
char buf[MSG_SIZ];
if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
- if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
+ if(PosFlags(0) & F_NULL_MOVE) {
sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
SendToProgram(buf, cps);
return;
char *m = moveList[moveNum];
static char c[2];
*c = m[7]; if(*c == '\n') *c = NULLCHAR; // promoChar
+ if(gameInfo.variant == VariantDuck)
+ snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
+ m[2], m[3] - '0',
+ m[2], m[3] - '0',
+ m[5], m[6] - '0', c);
+ else
if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
m[2], m[3] - '0',
CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
{
if (rf == DROP_RANK) {
- if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
- sprintf(move, "%c@%c%c\n",
- ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
+ if(ff == EmptySquare) sprintf(move, "@@@@\n"); else { // [HGM] pass
+ char p = PieceToChar((ChessSquare) ff);
+ if(p == '+' && pieceNickName[ff] >= 'A') p = pieceNickName[ff]; // prefer nick over +X
+ sprintf(move, "%c@%c%c\n",
+ ToUpper(p), AAA + ft, ONE + rt);
+ }
} else {
+ char c = autoProm[boards[forwardMostMove][rf][ff]];
+ if(c && (c == '-' || boards[forwardMostMove][rt][ft] != EmptySquare)) promoChar = '+';
if (promoChar == 'x' || promoChar == NULLCHAR) {
sprintf(move, "%c%c%c%c\n",
AAA + ff, ONE + rf, AAA + ft, ONE + rt);
char *lastParseAttempt;
void
-ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
+ParsePV (char *pv, Boolean storeComments, Boolean atEnd, int engine)
{ // Parse a string of PV moves, and append to current game, behind forwardMostMove
int fromX, fromY, toX, toY; char promoChar;
ChessMove moveType;
if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
lastParseAttempt = pv;
valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
- if(!valid && nr == 0 &&
- ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
- nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
- // Hande case where played move is different from leading PV move
+ if(!valid && nr == 0) {
+ if(engine == hintSrc && *lastHint && appData.ponderNextMove &&
+ ParseOneMove(lastHint, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
+ // first PV move is invalid, but the engine provided hint and is probably pondering on it
+ valid++; goto append_move; // use this hint as first move
+ }
+ if(ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
+ nr++; moveType = Comment; // PV was for previous position; kludge to make sure we continue
+ // Handle case where played move is different from leading PV move
CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
parseList[endPV-1][0] = NULLCHAR;
safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
}
- }
+ }
+ }
pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
valid++; // allow comments in PV
continue;
}
+ append_move:
nr++;
if(endPV+1 > framePtr) break; // no space, truncate
if(!valid) break;
Collapse(origIndex - lineStart);
return FALSE;
}
- ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
+ ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode, pane+1);
*start = startPV; *end = index-1;
extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
return TRUE;
}
char *
-PvToSAN (char *pv)
+PvToSAN (char *pv, int engine)
{
static char buf[10*MSG_SIZ];
int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
*buf = NULLCHAR;
if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
- ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
+ ParsePV(pv, FALSE, 2, engine+1); // this appends PV to game, suppressing any display of it
for(i = forwardMostMove; i<endPV; i++){
if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
{ // called on right mouse click to load PV
int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
lastX = x; lastY = y;
- ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
+ ParsePV(lastPV[which], FALSE, TRUE, 0); // load the PV of the thinking engine in the boards array.
extendGame = FALSE;
return TRUE;
}
{
int n = 0;
if(!*escapes) return strlen(s);
- while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
+ while(*s) n += (!strchr("-*/^", *s) && !strchr(escapes, *s)) - 2*(*s == '='), s++;
return n;
}
if(c == '^' || c == '-') { // has specified partner
int p;
for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
- if(c == '^') table[i] = '+';
if(p < EmptySquare) {
if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
}
+ if(c == '-') autoProm[i] = autoProm[p] = '-', c = '^';
+ if(c == '^') table[i] = '+';
} else if(c == '*') {
table[i] = partner[i];
promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
initialPosition[EP_STATUS] = EP_NONE;
initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
+ for(i=0; i<EmptySquare; i++) autoProm[i] = 0;
if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
SetCharTable(pieceNickName, appData.pieceNickNames);
else SetCharTable(pieceNickName, "............");
SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
break;
case VariantXiangqi:
+ case VariantJanggi:
pieces = XiangqiArray;
gameInfo.boardWidth = 9;
gameInfo.boardHeight = 10;
if(appData.pieceToCharTable != NULL)
SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
- for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
+ for( j=0; j<BOARD_WIDTH; j++ ) {
+ ChessSquare s = EmptySquare;
+ int fw = (gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantJanggi && j == BOARD_WIDTH/2);
if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
s = (ChessSquare) 0; /* account holding counts in guard band */
initialPosition[i][j] = s;
if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
- initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
+ initialPosition[fw][j] = pieces[0][j-gameInfo.holdingsWidth];
initialPosition[pawnRow][j] = WhitePawn;
initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
- if(gameInfo.variant == VariantXiangqi) {
+ if(gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantJanggi) {
if(j&1) {
initialPosition[pawnRow][j] =
initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
}
}
- initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] = pieces[1][j-gameInfo.holdingsWidth];
+ initialPosition[BOARD_HEIGHT-1-fw][j] = pieces[1][j-gameInfo.holdingsWidth];
}
if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
if( (gameInfo.variant == VariantShogi) && !overrule ) {
if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
+ gameInfo.variant == VariantJanggi ||
!(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
return FALSE;
if(legal[toY][toX] == 4) return FALSE;
piece = boards[currentMove][fromY][fromX];
+ if(PieceToChar(CHUPROMOTED(piece)) == '+') highestPromotingPiece = WhiteKing; // always obey PTC table
if(gameInfo.variant == VariantChu) {
promotionZoneSize = (BOARD_HEIGHT - deadRanks)/3;
if(legal[toY][toX] == 6) return FALSE; // no promotion if highlights deny it
} else if(gameInfo.variant == VariantShogi) {
promotionZoneSize = (BOARD_HEIGHT- deadRanks)/3 +(BOARD_HEIGHT == 8);
highestPromotingPiece = (int)WhiteAlfil;
- if(PieceToChar(piece) != '+' || PieceToChar(CHUPROMOTED(piece)) == '+') highestPromotingPiece = piece;
+ if(PieceToChar(piece) != '+' && PieceToChar(CHUPROMOTED(piece)) == '+') highestPromotingPiece = piece;
} else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
promotionZoneSize = 3;
- }
+ } else if(gameInfo.variant == VariantSChess && !gameInfo.holdingsSize) promotionZoneSize = 2;
// Treat Lance as Pawn when it is not representing Amazon or Lance
if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
if((int)piece >= BlackPawn) {
if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
return FALSE;
- if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
+ if(fromY < promotionZoneSize && (gameInfo.variant == VariantChuChess || gameInfo.variant == VariantFairy)) return FALSE;
highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
} else {
if( toY < BOARD_HEIGHT - deadRanks - promotionZoneSize &&
fromY < BOARD_HEIGHT - deadRanks - promotionZoneSize) return FALSE;
- if(fromY >= BOARD_HEIGHT - deadRanks - promotionZoneSize && gameInfo.variant == VariantChuChess)
+ if(fromY >= BOARD_HEIGHT - deadRanks - promotionZoneSize &&
+ (gameInfo.variant == VariantChuChess || gameInfo.variant == VariantFairy))
return FALSE;
}
} else DrawPositionX(repaint, board);
}
+static Boolean prelude;
+
int
OKToStartUserMove (int x, int y)
{
if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
case EditGame:
case AnalyzeMode:
- if (!white_piece && WhiteOnMove(currentMove)) {
+ if (!white_piece && WhiteOnMove(currentMove) && prelude != 2) {
DisplayMoveError(_("It is White's turn"));
return FALSE;
}
- if (white_piece && !WhiteOnMove(currentMove)) {
+ if (white_piece && (!WhiteOnMove(currentMove) || prelude == 2)) {
DisplayMoveError(_("It is Black's turn"));
return FALSE;
}
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;
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
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"));
DrawPosition(FALSE, boards[currentMove]);
return;
} else if (toX >= 0 && toY >= 0) {
- if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
+ if(toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
ChessSquare p = boards[0][rf][ff];
if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
}
+ } else if(gameInfo.variant == VariantSChess && !gameInfo.holdingsSize && gatingPiece == EmptySquare && (fromY == 0 || fromY == BOARD_HEIGHT-1)) {
+ boards[0][fromY][fromX] = DarkSquare; // empty on gating rank -> dark
} else
boards[0][fromY][fromX] = gatingPiece;
ClearHighlights();
fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
fromY = DROP_RANK;
+ if(autoProm[fromX] && shiftKey) fromX = CHUPROMOTED(fromX);
}
/* [HGM] always test for legality, to get promotion info */
return;
}
+ if(gameInfo.variant == VariantSChess && !gameInfo.holdingsSize && !promoChar) {
+ int piece, rank, file;
+ switch(moveType) { // detect castlings
+ case WhiteKingSideCastle: file = BOARD_RGHT-1; rank = 0; break;
+ case WhiteQueenSideCastle: file = BOARD_LEFT; rank = 0; break;
+ case BlackKingSideCastle: file = BOARD_RGHT-1; rank = BOARD_HEIGHT-1; break;
+ case BlackQueenSideCastle: file = BOARD_LEFT; rank = BOARD_HEIGHT-1; break;
+ default: file = -1;
+ }
+ if(file >= 0 && (piece = boards[currentMove][rank][file]) != DarkSquare) { // castling that should have gated at Rook
+ promoChar = ToLower(PieceToChar(piece));
+ }
+ }
+
+ if(moveType == Swap && killX < 0) killX = fromX, killY = fromY, moveType = NormalMove;
FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
}
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();
bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
first.maybeThinking = TRUE;
} else if(fromY == DROP_RANK && fromX == EmptySquare) {
+ if(PosFlags(0) & F_NULL_MOVE) SendMoveToProgram(currentMove, &first); else {
if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
SendBoard(&first, currentMove+1);
+ }
if(second.analyzing) {
if(!second.useSetboard) SendToProgram("undo\n", &second);
SendBoard(&second, currentMove+1);
GameEnds(WhiteWins, "White mates", GE_PLAYER);
}
break;
+ case MT_STEALMATE:
+ if (WhiteOnMove(currentMove)) {
+ GameEnds(WhiteWins, "Stalemated", GE_PLAYER);
+ } else {
+ GameEnds(BlackWins, "Stalemated", GE_PLAYER);
+ }
+ break;
case MT_STALEMATE:
GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
break;
typedef char Markers[BOARD_RANKS][BOARD_FILES];
Markers *m = (Markers *) closure;
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))
+ 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 & 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;
+ || kind == BlackCapturesEnPassant), legal[rt][ft] = 3;
+ if(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)) (*m)[rt][ft] = 5;
+ } else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
}
static int hoverSavedValid;
int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
if(gameMode == EditPosition) return FALSE; // no promotions when editing position
// some variants have fixed promotion piece, no promotion at all, or another selection mechanism
+ if(gameInfo.variant == VariantSChess && !gameInfo.holdingsSize) zone = 2;
if(IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantXiangqi ||
+ gameInfo.variant == VariantJanggi ||
gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
Boolean right; // instructs front-end to use button-1 events as if they were button 3
Boolean deferChoice;
int createX = -1, createY = -1; // square where we last created a piece in EditPosition mode
+ChessSquare selectedType = EmptySquare;
void
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) {
+ ChessSquare newType = boards[currentMove][y][x];
+ if(newType != EmptySquare) {
+ do { if(++newType == EmptySquare) newType = WhitePawn; } while(PieceToChar(newType) == '.');
+ } else newType = selectedType;
+ EditPositionMenuEvent(newType, x, y);
+ createX = x; createY = y; // remember where we last dropped
+ }
+ return;
+ }
+
// map clicks in offsetted holdings back to true coords (or switch the offset)
if(x == BOARD_RGHT+1) {
if(handOffsets & 1) {
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 = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
+ fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1; safeStrCpy(promoRestrict, defaultChoice, MSG_SIZ);
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) {
ReportClick("lift", x, y);
MarkTargetSquares(0);
if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
+ if(gameInfo.variant == VariantSChess && !gameInfo.holdingsWidth) { // auto-gating
+ int white = (boards[currentMove][fromY][fromX] < BlackPawn);
+ if(fromY == (white ? 1 : BOARD_HEIGHT - 2) // piece on shifted back-rank
+ && boards[currentMove][VIRGIN][fromX] & (white ? VIRGIN_W : VIRGIN_B)) { // and is virgin
+ int p = boards[currentMove][fromY - 2*white + 1][fromX];
+ if(p != DarkSquare) gatingPiece = p;
+ }
+ }
DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
promoSweep = defaultPromoChoice;
- selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
+ selectFlag = 0; lastX = xPix; lastY = yPix; safeStrCpy(promoRestrict, defaultChoice, MSG_SIZ);
Sweep(0); // Pawn that is going to promote: preview promotion piece
DisplayMessage("", _("Pull pawn backwards to under-promote"));
}
!(fromP == BlackKing && toP == BlackRook && frc)))) {
/* Clicked again on same color piece -- changed his mind */
second = (x == fromX && y == fromY);
- killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
+ killX = killY = kill2X = kill2Y = -1; safeStrCpy(promoRestrict, defaultChoice, MSG_SIZ);
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
}
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) &&
+ (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && gameInfo.holdingsWidth &&
y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
gatingPiece = boards[currentMove][fromY][fromX];
else gatingPiece = doubleClick ? fromP : EmptySquare;
DragPieceBegin(xPix, yPix, FALSE);
if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
promoSweep = defaultPromoChoice;
- selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
+ selectFlag = 0; lastX = xPix; lastY = yPix; safeStrCpy(promoRestrict, defaultChoice, MSG_SIZ);
Sweep(0); // Pawn that is going to promote: preview promotion piece
}
}
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) {
MarkTargetSquares(1);
} else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
- *promoRestrict = 0; appData.flashCount = saveFlash;
+ safeStrCpy(promoRestrict, defaultChoice, MSG_SIZ); appData.flashCount = saveFlash;
if (appData.animate || appData.highlightLastMove) {
SetHighlights(fromX, fromY, toX, toY);
} else {
}
}
+void
+Deselect ()
+{
+ fromX = -1;
+ ClearHighlights();
+}
+
int
RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
{ // front-end-free part taken out of PieceMenuPopup
case EditPosition:
if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
if (xSqr < 0 || ySqr < 0) return -1;
- if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
+// if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
+ if(appData.pieceMenu) { // select click
+ ChessSquare newType = boards[currentMove][ySqr][xSqr];
+ boards[currentMove][ySqr][xSqr] = EmptySquare;
+ if(newType == EmptySquare && selectedType == EmptySquare) { // click on empty summons context menu if not deselect
+ DisplayNote( _("To edit the position you can:\n"
+ "* Move pieces around with left mouse button\n"
+ "* Copy pieces by moving with Ctrl key pressed\n"
+ " OR by starting the move with a double-click\n"
+ "* Click a K, R or P a second time to toggle its rights\n"
+ "* 'Lift' a piece with right-click for multi-dropping\n"
+ "* Drop a piece of the lifted type by left-click on empty\n"
+ "* Right-click on empty to finish dropping\n"
+ "* Adjust the type of a dropped piece by clicking it again\n"
+ "* Click the active clock (repeatedly) to clear the board\n"
+ "* Click the inactive clock to change the side to move"));
+ return -2;
+ }
+ selectedType = newType; Deselect();
+ if(selectedType == EmptySquare) DisplayMessage("", ""); else {
+ char buf[MSG_SIZ];
+ snprintf(buf, MSG_SIZ, "left-click places %s %c", newType < BlackPawn ? _("white") : _("black"), ToUpper(PieceToChar(newType)));
+ DisplayMessage("", buf);
+ }
+ DrawPosition(FALSE, boards[currentMove]);
+ return -2;
+ }
if(xSqr == createX && ySqr == createY && xSqr != BOARD_LEFT-2 && xSqr != BOARD_RGHT+1) {
ChessSquare p = boards[currentMove][ySqr][xSqr];
do { if(++p == EmptySquare) p = WhitePawn; } while(PieceToChar(p) == '.');
Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
/* Some material-based adjudications that have to be made before stalemate test */
- if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
+ if((gameInfo.variant == VariantAtomic || gameInfo.variant == VariantDuck) && nr[WhiteKing] + nr[BlackKing] < 2) {
// [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
if(canAdjudicate && appData.checkMates) {
reason = "Xboard adjudication: Stalemate";
if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
- if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
+ if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway || gameInfo.variant == VariantDuck) // [HGM] losers:
boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
else if(gameInfo.variant == VariantSuicide) // in suicide it depends
boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
static ChessProgramState *stalledEngine;
-static char stashedInputMove[MSG_SIZ], abortEngineThink;
+static char stashedInputMove[MSG_SIZ], abortEngineThink, startPieceToChar[MSG_SIZ];
+static char preludeText[MSG_SIZ], diceRoll[MSG_SIZ];
void
HandleMachineMove (char *message, ChessProgramState *cps)
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;
ChessMove moveType;
char promoChar, roar;
char *p, *pv=buf1;
if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
(sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
{
+ int fromX, fromY, toX, toY;
if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
safeStrCpy(stashedInputMove, message, MSG_SIZ);
// [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(machineMove[strlen(machineMove)-1] == ',') { // move ends in comma: 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(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
+ int l = strlen(firstLeg), promo = machineMove[4];
+ sscanf(machineMove, "%c%d%c%d", &f, &killY, &f, &killY); killX = f - AAA; killY -= ONE - '0';
+ safeStrCpy(machineMove, firstLeg, 20);
+ snprintf(machineMove + l - 1, 20 - l, ";%c%d%c", killX + AAA, killY + ONE - '0', promo);
+ } 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);
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,
ChessMove moveType;
moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
fromY, fromX, toY, toX, promoChar);
+ if(gameInfo.variant == VariantDuck &&
+ (killX < BOARD_LEFT || killX >= BOARD_RGHT || killY < 0 || killY >= BOARD_HEIGHT ||
+ boards[forwardMostMove][killY][killX] != EmptySquare && (killX != fromX || killY != fromY))) moveType = IllegalMove;
if(moveType == IllegalMove) {
snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
buf1, GE_XBOARD);
return;
- } else if(!appData.fischerCastling)
+ } else if(!appData.fischerCastling && toX != BOARD_WIDTH>>1)
/* [HGM] Kludge to handle engines that send FRC-style castling
when they shouldn't (like TSCP-Gothic) */
switch(moveType) {
}
}
hintRequested = FALSE;
- lastHint[0] = NULLCHAR;
+ lastHint[0] = NULLCHAR; hintSrc = 0;
bookRequested = FALSE;
/* Program may be pondering now */
cps->maybeThinking = TRUE;
}
if (!strncmp(message, "setup ", 6) &&
- (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
+ (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || prelude ||
NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
) { // [HGM] allow first engine to define opening position
- int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
+ int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ], *p = varName;
+ Board tmp;
if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
*buf = NULLCHAR;
if(sscanf(message, "setup (%s", buf) == 1) {
- s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
- ASSIGN(appData.pieceToCharTable, buf);
+ char *ptc = strlen(buf) < 3 ? "PNBRQKpnbrqk" : buf;
+ s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, ptc, SUFFIXES);
+ ASSIGN(appData.pieceToCharTable, ptc);
+ if(gameInfo.variant == VariantUnknown) safeStrCpy(startPieceToChar, ptc, MSG_SIZ);
}
+ CopyBoard(tmp, boards[0]);
dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
if(dummy >= 3) {
while(message[s] && message[s++] != ' ');
- if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
- dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
+ if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand || dummy == 4) {
+ // engine wants to change board format or variant
// if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
- if(hand > h) handSize = hand; else handSize = h;
- appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
- if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
+ if(hand > h) handSize = hand; else handSize = h;
+ appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
+ if(dummy == 4) {
+ p += prelude = (*p == '!'); // strip leading '!', and enable acceptance of further setups
+ gameInfo.variant = StringToVariant(p); // parent variant
+ }
InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
- startedFromSetupPosition = FALSE;
+// startedFromSetupPosition = FALSE;
}
}
- if(startedFromSetupPosition) return;
+ fromX = fromY = -1;
ParseFEN(boards[0], &dummy, message+s, FALSE);
- DrawPosition(TRUE, boards[0]);
+ if(dummy) prelude *= 2;
CopyBoard(initialPosition, boards[0]);
- startedFromSetupPosition = TRUE;
+ MarkTargetSquares(1); ClearHighlights();
+ if(startedFromSetupPosition) CopyBoard(boards[0], tmp);
+ DrawPosition(TRUE, boards[0]);
+ if(!prelude) startedFromSetupPosition = TRUE;
return;
}
if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
}
return;
}
- if(!appData.testLegality && sscanf(message, "choice %s", promoRestrict) == 1) {
+ if(sscanf(message, "choice %s", promoRestrict) == 1) {
+ if(gameMode == BeginningOfGame && cps == &first &&
+ (!appData.testLegality || *engineVariant != NULLCHAR || gameInfo.variant == VariantFairy)) {
+ safeStrCpy(defaultChoice, promoRestrict, MSG_SIZ); // redefine promotion choice for entire game
+ return;
+ }
+ if(appData.testLegality) return;
if(deferChoice) {
LeftClick(Press, 0, 0); // finish the click that was interrupted
} else if(promoSweep != EmptySquare) {
}
return;
}
+ if(!strncmp(message, "dice ", 5)) { // [HGM] dice: provide dice rolls
+ if(gameMode != TwoMachinesPlay || (cps->twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
+ static char previousRoll[MSG_SIZ];
+ char buf[MSG_SIZ], *p = message + 5;
+ int n, k, l;
+ sprintf(buf, "pips");
+ while(sscanf(p, "%d", &n) == 1) {
+ k = (random() + 256*random() & 0xFFFF)*n >> 16;
+ l = strlen(buf);
+ snprintf(buf + l, MSG_SIZ - 1 - l, " %d/%d", k+1, n);
+ while(*++p > ' ') {}
+ }
+ l = strlen(diceRoll);
+ if(l == 0) safeStrCpy(previousRoll, diceRoll+1, MSG_SIZ); // remember series from previous turn
+ snprintf(diceRoll + l, MSG_SIZ - l, "%s", buf + 4); // accumulate rolls for display
+ strcat(buf, "\n"); SendToProgram(buf, cps);
+ if(gameMode != TwoMachinesPlay) {
+ int human = (gameMode == MachinePlaysWhite) != WhiteOnMove(forwardMostMove);
+ if(human) snprintf(buf, MSG_SIZ, "Your dice roll:%s (mine %s)", diceRoll, previousRoll);
+ else snprintf(buf, MSG_SIZ, "My dice roll:%s", diceRoll);
+ DisplayTitle(buf);
+ } else if(cps->other->dice) SendToProgram(buf, cps->other);
+ }
+ 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.
*/
snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
SendToICS(buf1);
}
- } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
+ } else {
+ if(forwardMostMove == 0 && !strncmp(message+11, "prelude ", 8)) { // allow engine to add Prelude tag
+ strncpy(preludeText, message+19, MSG_SIZ);
+ } else
+ if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
+ }
return;
}
if (!strncmp(message, "tellall ", 8)) {
* Look for hint output
*/
if (sscanf(message, "Hint: %s", buf1) == 1) {
+ int fromX, fromY, toX, toY;
if (cps == &first && hintRequested) {
hintRequested = FALSE;
if (ParseOneMove(buf1, forwardMostMove, &moveType,
DisplayError(buf2, 0);
}
} else {
- safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
+ safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0])); hintSrc = (cps != &first) + 1;
}
return;
}
curscore = -curscore;
}
- if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
+ if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1, cps == &second);
if(*bestMove) { // rememer time best EPD move was first found
int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
curscore = -curscore;
}
+ if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1, cps == &second);
+
cpstats.depth = plylev;
cpstats.nodes = nodes;
cpstats.time = time;
cpstats.movelist[0] = '\0';
if (buf1[0] != NULLCHAR) {
- safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
+ safeStrCpy( cpstats.movelist, pv, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
}
cpstats.ok_to_send = 0;
void
ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
{
- ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, epFile, lastFile, lastRank, berolina = 0;
+ ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2;
+ 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;
/* [HGM] compute & store e.p. status and castling rights for new position */
/* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
+ if(gameInfo.variant == VariantSChess && !gameInfo.holdingsSize) promoRank = 2;
oldEP = (signed char)board[EP_STATUS]; epRank = board[EP_RANK]; epFile = board[EP_FILE]; lastFile = board[LAST_TO] & 255,lastRank = board[LAST_TO] >> 8;
board[EP_STATUS] = EP_NONE;
- board[EP_FILE] = board[EP_RANK] = 100, board[LAST_TO] = 0x4040;
+ board[EP_FILE] = board[EP_RANK] = 100, board[LAST_TO] = toX + 256*toY;
if (fromY == DROP_RANK) {
/* must be first */
// ChessSquare victim;
int i;
+ piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion and igui */
+ board[fromY][fromX] = EmptySquare;
+
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<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) if(board[r][f] == DarkSquare) board[r][f] = EmptySquare;
+ board[killY][killX] = DarkSquare;
+ } else {
killed = board[killY][killX],
board[killY][killX] = EmptySquare,
board[EP_STATUS] = EP_CAPTURE;
if( kill2X >= 0 && kill2Y >= 0)
killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
+ if(killed == EmptySquare) // [HGM] unload: kill-square also used for push destination
+ board[killY][killX] = board[toY][toX], board[EP_STATUS] = EP_NONE;
+ }
}
if( board[toY][toX] != EmptySquare ) {
board[EP_STATUS] = EP_CAPTURE;
if( (fromX != toX || fromY != toY) && // not igui!
- (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
- captured == BlackLion && board[fromY][fromX] != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules
+ (captured == WhiteLion && piece != BlackLion ||
+ captured == BlackLion && piece != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules
board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
}
}
- pawn = board[fromY][fromX];
+ pawn = piece;
if(pieceDesc[pawn] && strchr(pieceDesc[pawn], 'e')) { // piece with user-defined e.p. capture
- if(captured == EmptySquare && toX == epFile && (toY == (epRank & 127) || toY + (pawn < BlackPawn ? -1 : 1) == epRank - 128)) {
+ board[EP_RANK] = epRank; board[EP_FILE] = epFile; // Ughh! cleared it too soon :-(
+ isEP = EnPassantTest(board, PosFlags(currentMove), fromY, fromX, toY, toX, promoChar);
+ if(isEP != 1) board[EP_RANK] = board[EP_FILE] = 100;// assumes only e.p. capturing pieces generate rights!
+ if(isEP == 2) {
captured = board[lastRank][lastFile]; // remove victim
board[lastRank][lastFile] = EmptySquare;
pawn = EmptySquare; // kludge to suppress old e.p. code
if( pawn == WhitePawn ) {
if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
board[EP_STATUS] = EP_PAWN_MOVE;
- if( toY-fromY>=2) {
- board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
+ if(!isEP && toY-fromY>=2) {
+ board[EP_FILE] = (2*fromX + toX + 1)/3; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
gameInfo.variant != VariantBerolina || toX < fromX)
board[EP_STATUS] = toX | berolina;
if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
gameInfo.variant != VariantBerolina || toX > fromX)
board[EP_STATUS] = toX;
- board[LAST_TO] = toX + 256*toY;
}
} else
if( pawn == BlackPawn ) {
if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
board[EP_STATUS] = EP_PAWN_MOVE;
- if( toY-fromY<= -2) {
- board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
+ if(!isEP && toY-fromY<= -2) {
+ board[EP_FILE] = (2*fromX + toX + 1)/3; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
gameInfo.variant != VariantBerolina || toX < fromX)
board[EP_STATUS] = toX | berolina;
if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
gameInfo.variant != VariantBerolina || toX > fromX)
board[EP_STATUS] = toX;
- board[LAST_TO] = toX + 256*toY;
}
}
}
if(gameInfo.variant == VariantSChess) { // update virginity
- if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
- if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
- if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
- if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
+ int offs = !gameInfo.holdingsSize;
+ if(fromY == offs) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
+ if(fromY == BOARD_HEIGHT-1-offs) board[VIRGIN][fromX] &= ~VIRGIN_B;
+ if(toY == offs) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
+ if(toY == BOARD_HEIGHT-1-offs) board[VIRGIN][toX] &= ~VIRGIN_B;
}
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 */
if(gameInfo.variant == VariantKnightmate)
king += (int) WhiteUnicorn - (int) WhiteKing;
+ if(autoProm[piece]) {
+ board[toY][toX] = (autoProm[piece] != '!' || board[toY][toX] != EmptySquare ? CHUPROMOTED(piece) : piece);
+ } else
if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
&& (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) { // and tramples own
- board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
+ board[toY][toX] = piece;
board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
board[EP_STATUS] = EP_NONE; // capture was fake!
} else
} else
/* Code added by Tord: */
/* 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) {
+ if (piece == WhiteKing && board[toY][toX] == WhiteRook ||
+ piece == WhiteRook && board[toY][toX] == WhiteKing) {
board[EP_STATUS] = EP_NONE; // capture was fake!
- board[fromY][fromX] = EmptySquare;
board[toY][toX] = EmptySquare;
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 ||
- board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
+ } else if (piece == BlackKing && board[toY][toX] == BlackRook ||
+ piece == BlackRook && board[toY][toX] == BlackKing) {
board[EP_STATUS] = EP_NONE;
- board[fromY][fromX] = EmptySquare;
board[toY][toX] = EmptySquare;
if((toX > fromX) != (piece == BlackRook)) {
board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
/* End of code added by Tord */
} else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
- board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
- board[toY][toX] = piece;
- } else if (board[fromY][fromX] == king
+ board[toY][toX] = piece; // never castle if King has virgin moves defined on it other than castling
+ } else if (piece == king
&& fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
&& toY == fromY && toX > fromX+1) {
for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
; // castle with nearest piece
board[fromY][toX-1] = board[fromY][rookX];
board[fromY][rookX] = EmptySquare;
- board[fromY][fromX] = EmptySquare;
board[toY][toX] = king;
- } else if (board[fromY][fromX] == king
+ } else if (piece == king
&& fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
&& toY == fromY && toX < fromX-1) {
for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
; // castle with nearest piece
board[fromY][toX+1] = board[fromY][rookX];
board[fromY][rookX] = EmptySquare;
- board[fromY][fromX] = EmptySquare;
board[toY][toX] = king;
- } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
- board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
+ } else if ((piece == WhitePawn && gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantJanggi ||
+ piece == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
&& toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
) {
/* white pawn promotion */
board[toY][toX] = CharToPiece(ToUpper(promoChar));
if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
- board[fromY][fromX] = EmptySquare;
} else if ((fromY >= BOARD_HEIGHT>>1)
&& (epFile == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
&& (toX != fromX)
&& gameInfo.variant != VariantXiangqi
+ && gameInfo.variant != VariantJanggi
&& gameInfo.variant != VariantBerolina
&& (pawn == WhitePawn)
&& (board[toY][toX] == EmptySquare)) {
if(lastFile == 100) lastFile = (board[fromY][toX] == BlackPawn ? toX : fromX), lastRank = fromY; // assume FIDE e.p. if victim present
- board[fromY][fromX] = EmptySquare;
board[toY][toX] = piece;
captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
} else if ((fromY == BOARD_HEIGHT-4)
&& (toX == fromX)
&& gameInfo.variant == VariantBerolina
- && (board[fromY][fromX] == WhitePawn)
+ && (piece == WhitePawn)
&& (board[toY][toX] == EmptySquare)) {
- board[fromY][fromX] = EmptySquare;
board[toY][toX] = WhitePawn;
if(oldEP & EP_BEROLIN_A) {
captured = board[fromY][fromX-1];
}else{ captured = board[fromY][fromX+1];
board[fromY][fromX+1] = EmptySquare;
}
- } else if (board[fromY][fromX] == king
+ } else if (piece == king
&& fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
&& toY == fromY && toX > fromX+1) {
for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
;
board[fromY][toX-1] = board[fromY][rookX];
board[fromY][rookX] = EmptySquare;
- board[fromY][fromX] = EmptySquare;
board[toY][toX] = king;
- } else if (board[fromY][fromX] == king
+ } else if (piece == king
&& fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
&& toY == fromY && toX < fromX-1) {
for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
;
board[fromY][toX+1] = board[fromY][rookX];
board[fromY][rookX] = EmptySquare;
- board[fromY][fromX] = EmptySquare;
board[toY][toX] = king;
} else if (fromY == 7 && fromX == 3
- && board[fromY][fromX] == BlackKing
+ && piece == BlackKing
&& toY == 7 && toX == 5) {
- board[fromY][fromX] = EmptySquare;
board[toY][toX] = BlackKing;
board[fromY][7] = EmptySquare;
board[toY][4] = BlackRook;
} else if (fromY == 7 && fromX == 3
- && board[fromY][fromX] == BlackKing
+ && piece == BlackKing
&& toY == 7 && toX == 1) {
- board[fromY][fromX] = EmptySquare;
board[toY][toX] = BlackKing;
board[fromY][0] = EmptySquare;
board[toY][2] = BlackRook;
- } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
- board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
+ } else if ((piece == BlackPawn && gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantJanggi ||
+ piece == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
&& toY < promoRank && promoChar
) {
/* black pawn promotion */
board[toY][toX] = CharToPiece(ToLower(promoChar));
if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
- board[fromY][fromX] = EmptySquare;
} else if ((fromY < BOARD_HEIGHT>>1)
&& (epFile == toX && epRank == toY || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
&& (toX != fromX)
&& gameInfo.variant != VariantXiangqi
+ && gameInfo.variant != VariantJanggi
&& gameInfo.variant != VariantBerolina
&& (pawn == BlackPawn)
&& (board[toY][toX] == EmptySquare)) {
if(lastFile == 100) lastFile = (board[fromY][toX] == WhitePawn ? toX : fromX), lastRank = fromY;
- board[fromY][fromX] = EmptySquare;
board[toY][toX] = piece;
captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
} else if ((fromY == 3)
&& (toX == fromX)
&& gameInfo.variant == VariantBerolina
- && (board[fromY][fromX] == BlackPawn)
+ && (piece == BlackPawn)
&& (board[toY][toX] == EmptySquare)) {
- board[fromY][fromX] = EmptySquare;
board[toY][toX] = BlackPawn;
if(oldEP & EP_BEROLIN_A) {
captured = board[fromY][fromX-1];
board[fromY][fromX+1] = EmptySquare;
}
} else {
- ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
- board[fromY][fromX] = EmptySquare;
board[toY][toX] = piece;
}
}
/* and erasing image if necessary */
p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
if(p < (int) BlackPawn) { /* white drop */
+ if(PieceToChar(p) == '+') p = CHUDEMOTED(p); // promoted drop
p -= (int)WhitePawn;
p = PieceToNumber((ChessSquare)p);
if(p >= gameInfo.holdingsSize) p = 0;
board[p][BOARD_WIDTH-2] = 0;
} else { /* black drop */
p -= (int)BlackPawn;
+ if(PieceToChar(p) == '+') p = CHUDEMOTED(p);
p = PieceToNumber((ChessSquare)p);
if(p >= gameInfo.holdingsSize) p = 0;
if(--board[handSize-1-p][1] <= 0)
}
}
- if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
- board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
+ if(gameInfo.variant == VariantSChess) {
+ int file = fromX, white = (piece < BlackPawn);
+ int rank = fromY-2*white+1;
+ if(promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
+ if(!gameInfo.holdingsSize && rank >= 0 && rank < BOARD_HEIGHT) {
+ if(board[rank][file] == DarkSquare) file = (toX > fromX ? BOARD_RGHT -1 : BOARD_LEFT); // must be castling
+ board[rank][file] = DarkSquare;
+ }
+ board[fromY][file] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
+ }
+ if(!gameInfo.holdingsSize) {
+ rank = toY+2*white-1;
+ if(captured != EmptySquare && (rank == 0 || rank == BOARD_HEIGHT-1))
+ board[rank][toX] = DarkSquare; // gateable piece disappears with its gator
+ }
} else
if(promoChar == '+') {
/* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
ChessSquare p = boards[forwardMostMove][toY][toX];
// forwardMostMove++; // [HGM] bare: moved downstream
+ diceRoll[0] = NULLCHAR; // [HGM] dice: consumed by this move
+ if(gameInfo.variant != VariantDuck) {
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)
+ 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;
int width = 8, height = 8, holdings = 0; // most common sizes
if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
// correct the deviations default for each variant
- if( v == VariantXiangqi ) width = 9, height = 10;
+ if( v == VariantXiangqi || v == VariantJanggi ) width = 9, height = 10;
if( v == VariantShogi ) width = 9, height = 9, holdings = 7;
if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
if( v == VariantCapablanca || v == VariantCapaRandom ||
StartChessProgram (ChessProgramState *cps)
{
char buf[MSG_SIZ];
- int err;
+ int err, priority = appData.niceEngines;
if (appData.noChessProgram) return;
cps->initDone = FALSE;
+ if(cps->isUCI && strstr(appData.adapterCommand, "%niceEngines")) priority = 0; // no nicing on nice adapters!
+
if (strcmp(cps->host, "localhost") == 0) {
- err = StartChildProcess(cps->program, cps->dir, &cps->pr);
+ err = StartChildProcess(cps->program, cps->dir, &cps->pr, priority);
} else if (*appData.remoteShell == NULLCHAR) {
err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
} else {
snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
cps->host, appData.remoteUser, cps->program);
}
- err = StartChildProcess(buf, "", &cps->pr);
+ err = StartChildProcess(buf, "", &cps->pr, 0);
}
if (err != 0) {
s = malloc(len);
snprintf(s, len, "%s%s%s", firstChessProgramNames, currentEngine[n], q);
FREE(firstChessProgramNames); firstChessProgramNames = s; // new list
- if(*engineListFile) SaveEngineList();
+ SaveEngineList();
}
// following implemented as macro to avoid type limitations
appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
Reset(FALSE, first.pr != NoProc);
+ if(startPieceToChar[0]) SetCharTableEsc(pieceToChar, startPieceToChar, SUFFIXES);
res = LoadGameOrPosition(matchGame); // setup game
appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
+ if(startPieceToChar[0]) SetCharTableEsc(pieceToChar, startPieceToChar, SUFFIXES);
if(!res) return; // abort when bad game/pos file
if(appData.epd) {// in EPD mode we make sure first engine is to move
firstWhite = !(forwardMostMove & 1);
ExitAnalyzeMode();
DoSleep( appData.delayBeforeQuit );
SendToProgram("quit\n", &first);
- DestroyChildProcess(first.pr, 4 + first.useSigterm);
+ DestroyChildProcess(first.pr, 4*!first.isUCI + first.useSigterm);
first.reload = TRUE;
}
first.pr = NoProc;
if (second.pr != NoProc) {
DoSleep( appData.delayBeforeQuit );
SendToProgram("quit\n", &second);
- DestroyChildProcess(second.pr, 4 + second.useSigterm);
+ DestroyChildProcess(second.pr, 4*!second.isUCI + second.useSigterm);
second.reload = TRUE;
}
second.pr = NoProc;
pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
deadRanks = 0; // assume entire board is used
handSize = 0;
+ prelude = FALSE; preludeText[0] = NULLCHAR;
for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
CleanupTail(); // [HGM] vari: delete any stored variations
CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
second.maybeThinking = FALSE;
first.bookSuspend = FALSE; // [HGM] book
second.bookSuspend = FALSE;
+ diceRoll[0] = NULLCHAR;
thinkOutput[0] = NULLCHAR;
- lastHint[0] = NULLCHAR;
+ lastHint[0] = NULLCHAR; hintSrc = 0;
ClearGameInfo(&gameInfo);
gameInfo.variant = StringToVariant(appData.variant);
+ *defaultChoice = NULLCHAR; // [HGM] promo
if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
gameInfo.variant = VariantUnknown;
strncpy(engineVariant, appData.variant, MSG_SIZ);
GameEnds(WhiteWins, "White mates", GE_FILE);
}
break;
+ case MT_STEALMATE:
+ if (WhiteOnMove(currentMove)) {
+ GameEnds(WhiteWins, "Stalemated", GE_FILE);
+ } else {
+ GameEnds(BlackWins, "Stalemated", GE_FILE);
+ }
+ break;
case MT_STALEMATE:
GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
break;
GameEnds(WhiteWins, "White mates", GE_FILE);
}
break;
+ case MT_STEALMATE:
+ if (WhiteOnMove(currentMove)) {
+ GameEnds(WhiteWins, "Stalemated", GE_FILE);
+ } else {
+ GameEnds(BlackWins, "Stalemated", GE_FILE);
+ }
+ break;
case MT_STALEMATE:
GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
break;
total++;
}
}
- epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
+ epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantJanggi && gameInfo.variant != VariantBerolina;
return total;
}
int err, pos = -1;
GameMode oldGameMode;
VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
- char oldName[MSG_SIZ];
+ char oldName[MSG_SIZ], vs = 0;
safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
if (!err) numPGNTags++;
/* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
- if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
+ if(gameInfo.variant != oldVariant && gameInfo.variant != VariantUnknown &&
+ (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR) || vs) {
startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
InitPosition(TRUE);
- oldVariant = gameInfo.variant;
+ oldVariant = gameInfo.variant; vs++; // force obeying second variant switch
if (appData.debugMode)
fprintf(debugFP, "New variant %d\n", (int) oldVariant);
}
if (backwardMostMove > 0 || startedFromSetupPosition) {
char *fen = PositionToFEN(backwardMostMove, NULL, 1);
fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
+ if(*preludeText) fprintf(f, "[Prelude \"%s\"]\n", preludeText);
fprintf(f, "\n{--------------\n");
PrintPosition(f, backwardMostMove);
fprintf(f, "--------------}\n");
DoSleep( appData.delayBeforeQuit );
SendToProgram("quit\n", &first);
- DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
+ DestroyChildProcess(first.pr, 4*!first.isUCI + first.useSigterm /* [AS] first.useSigterm */ );
}
if (second.pr != NoProc) {
DoSleep( appData.delayBeforeQuit );
SendToProgram("quit\n", &second);
- DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
+ DestroyChildProcess(second.pr, 4*!second.isUCI + second.useSigterm /* [AS] second.useSigterm */ );
}
if (first.isr != NULL) {
RemoveInputSource(first.isr);
else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
StartClocks();
} else {
+ if(jawsClock && gameMode == EditGame) StartClocks();
DisplayBothClocks();
}
if (gameMode == PlayFromGameFile) {
pausing = TRUE;
ModeHighlight();
break;
+ case EditGame:
+ if(!jawsClock) return;
+ StopClocks();
+ pausing = TRUE;
+ ModeHighlight();
+ break;
}
}
}
if (gameMode == EditPosition)
EditPositionDone(TRUE);
-
+/*
if (!WhiteOnMove(currentMove)) {
DisplayError(_("It is not White's turn"), 0);
return;
}
-
+*/
if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
ExitAnalyzeMode();
gameMode = MachinePlaysWhite;
pausing = FALSE;
ModeHighlight();
+
+ if(!WhiteOnMove(currentMove)) {
+ first.bookSuspend = TRUE;
+ StartClocks();
+ firstMove = FALSE;
+ return;
+ }
+
SetGameInfo();
snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
DisplayTitle(buf);
if (gameMode == EditPosition)
EditPositionDone(TRUE);
-
+/*
if (WhiteOnMove(currentMove)) {
DisplayError(_("It is not Black's turn"), 0);
return;
}
-
+*/
if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
ExitAnalyzeMode();
gameMode = MachinePlaysBlack;
pausing = FALSE;
ModeHighlight();
+
+ if(WhiteOnMove(currentMove)) {
+ first.bookSuspend = TRUE;
+ StartClocks();
+ firstMove = FALSE;
+ return;
+ }
+
SetGameInfo();
snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
DisplayTitle(buf);
SetMachineThinkingEnables();
first.maybeThinking = TRUE;
StartClocks();
+ firstMove = FALSE;
if (appData.autoFlipView && flipView) {
flipView = !flipView;
}
void
+DisplayClockMessage (char *msg)
+{
+ if(!msg) SetClockMessage(0, NULL), SetClockMessage(1, NULL); else
+ if(blackPlaysFirst) SetClockMessage(1, msg), SetClockMessage(0, _("Set White_to Move"));
+ else SetClockMessage(0, msg), SetClockMessage(1, _("Set Black_to Move"));
+ DisplayBothClocks();
+}
+
+int clearCycle;
+
+void
EditPositionEvent ()
{
int i;
currentMove = forwardMostMove = backwardMostMove = 0;
HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
DisplayMove(-1);
- if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
+ DisplayMessage(_("Keep Ctrl pressed to duplicate pieces"), "");
+ DisplayClockMessage(_("Clear_Board"));
+ clearCycle = 0;
}
void
{
int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
+ selectedType = EmptySquare;
startedFromSetupPosition = TRUE;
InitChessProgram(&first, FALSE);
if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
int r, f;
boards[0][EP_STATUS] = EP_NONE;
- for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
+ for(f=0; f<=6; f++) boards[0][CASTLING][f] = NoRights;
for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
if(rightsBoard[r][f]) {
ChessSquare p = boards[0][r][f];
}
DisplayTitle("");
DisplayMessage("", "");
+ DisplayClockMessage(NULL);
timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
timeRemaining[1][forwardMostMove] = blackTimeRemaining;
gameMode = EditGame;
ModeHighlight();
HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
ClearHighlights(); /* [AS] */
+ if(!appData.icsActive && !appData.noChessProgram) {
+ if(forwardMostMove) MachineWhiteEvent(); else MachineBlackEvent();
+ }
}
/* Pause for `ms' milliseconds */
char buf[MSG_SIZ];
ChessSquare piece = boards[0][y][x];
static Board erasedBoard, currentBoard, menuBoard, nullBoard;
- static int lastVariant;
int baseRank = BOARD_HEIGHT-1, hasRights = 0;
if (gameMode != EditPosition && gameMode != IcsExamining) return;
SendToICS(ics_prefix);
SendToICS("clearboard\n");
} else {
- int nonEmpty = 0;
for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
for (y = 0; y < BOARD_HEIGHT; y++) {
AAA + x, ONE + y);
SendToICS(buf);
}
- } else if(boards[0][y][x] != DarkSquare) {
- if(boards[0][y][x] != p) nonEmpty++;
- boards[0][y][x] = p;
}
}
}
CopyBoard(rightsBoard, nullBoard);
if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
int r, i;
+ CopyBoard(menuBoard, initialPosition);
for(r = 0; r < BOARD_HEIGHT; r++) {
for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
ChessSquare p = menuBoard[r][x];
}
}
menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
- DisplayMessage("Clicking clock again restores position", "");
- if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
- if(!nonEmpty) { // asked to clear an empty board
- CopyBoard(boards[0], menuBoard);
- } else
- if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
- CopyBoard(boards[0], initialPosition);
- } else
- if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
- && !CompareBoards(nullBoard, erasedBoard)) {
+ switch(clearCycle++) {
+ case 0:
+ DisplayClockMessage(_("Clear_More"));
+ CopyBoard(erasedBoard, boards[0]);
+ CopyBoard(boards[0], menuBoard); break;
+ case 1:
+ DisplayClockMessage(_("Restore_Position"));
+ for(r = 0; r < BOARD_HEIGHT; r++) {
+ ChessSquare king = WhiteKing;
+ for(x = 0; x < BOARD_WIDTH; x++) { // erase except Kings and center
+ ChessSquare p = (x == BOARD_LEFT-1 || x == BOARD_RGHT ? 0 : EmptySquare);
+ if((x < BOARD_WIDTH/2 - 2 || x >= BOARD_WIDTH/2 + 2 ||
+ r < BOARD_HEIGHT/2 - 2 || r >= BOARD_HEIGHT/2 + 2) &&
+ boards[0][r][x] != king && boards[0][r][x] != king + BlackPawn
+ && boards[0][r][x] != DarkSquare && boards[0][r][x] != p) boards[0][r][x] = EmptySquare;
+ }
+ }
+ break;
+ case 2:
+ if(!CompareBoards(erasedBoard, initialPosition)) { // initial position if notyet there
+ DisplayClockMessage(_("Resume_Edit"));
+ CopyBoard(boards[0], initialPosition);
+ break;
+ }
+ clearCycle++;
+ case 3:
+ DisplayClockMessage(_("Clear_Board"));
CopyBoard(boards[0], erasedBoard);
- } else
- CopyBoard(erasedBoard, currentBoard);
+ }
+ clearCycle &= 3; // wrap
for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
case WhitePlay:
SetWhiteToPlayEvent();
+ DisplayClockMessage("");
break;
case BlackPlay:
SetBlackToPlayEvent();
+ DisplayClockMessage("");
break;
case EmptySquare:
case BlackQueen:
if(gameInfo.variant == VariantShatranj ||
gameInfo.variant == VariantXiangqi ||
+ gameInfo.variant == VariantJanggi ||
gameInfo.variant == VariantCourier ||
gameInfo.variant == VariantASEAN ||
gameInfo.variant == VariantMakruk )
case WhiteKing:
baseRank = 0;
case BlackKing:
- if(gameInfo.variant == VariantXiangqi)
+ if(gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantJanggi)
selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
if(gameInfo.variant == VariantKnightmate)
selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
{ // [HGM] code moved to back-end from winboard.c
if(which) { // black clock
if (gameMode == EditPosition || gameMode == IcsExamining) {
- if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
- SetBlackToPlayEvent();
- } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
+ if(blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
+ else EditPositionMenuEvent(BlackPlay, 0, 0);
+ } else if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == BeginningOfGame ||
gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
} else if (shiftKey) {
}
} else { // white clock
if (gameMode == EditPosition || gameMode == IcsExamining) {
- if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
- SetWhiteToPlayEvent();
+ if(!blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
+ else EditPositionMenuEvent(WhitePlay, 0, 0);
} else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
+ if (BoolFeature(&p, "dice", &cps->dice, cps)) continue; // [HGM] dice
if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
if (IntFeature(&p, "done", &val, cps)) {
FeatureDone(cps, val);
break;
case EditGame:
+ if(jawsClock) break;
case PlayFromGameFile:
case IcsExamining:
return;
int i, j, fromX, fromY, toX, toY;
int whiteToPlay, haveRights = nrCastlingRights;
char buf[MSG_SIZ];
- char *p, *q;
+ char *p, *q, c;
int emptycount;
ChessSquare piece;
else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
emptycount = 0;
}
- if(PieceToChar(piece) == '+') {
+ c = PieceToChar(piece);
+ if(c == '+') {
/* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
- *p++ = '+';
- piece = (ChessSquare)(CHUDEMOTED(piece));
+ if(autoProm[piece] && pieceNickName[piece] >= 'A') c = pieceNickName[piece]; else
+ *p++ = '+', piece = (ChessSquare)(CHUDEMOTED(piece)), c = PieceToChar(piece);
}
- *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
+ *p++ = (piece == DarkSquare ? '*' : c);
if(*p = PieceSuffix(piece)) p++;
if(p[-1] == '~') {
/* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
}
if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
+ gameInfo.variant != VariantJanggi &&
gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
/* En passant target square */
#endif
} else if (*p == '*') {
board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
+ } else if(*p == '0') {
+ p++; board[i][(j++) + gameInfo.holdingsWidth] = EmptySquare;
} else if (isdigit(*p)) {
emptycount = *p++ - '0';
while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
/* set defaults in case FEN is incomplete */
board[EP_STATUS] = EP_UNKNOWN;
board[TOUCHED_W] = board[TOUCHED_B] = 0;
- for(i=0; i<nrCastlingRights; i++ ) {
+ for(i=0; i<6; i++ ) {
board[CASTLING][i] =
appData.fischerCastling ? NoRights : initialRights[i];
} /* assume possible unless obviously impossible */
if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
/* read e.p. field in games that know e.p. capture */
- if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
+ if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantJanggi &&
gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
if(*p=='-') {
PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
// kludge: use ParsePV() to append variation to game
move = currentMove;
- ParsePV(start, TRUE, TRUE);
+ ParsePV(start, TRUE, TRUE, 0);
forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
ClearPremoveHighlights();
CommentPopDown();