2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free
9 * Software Foundation, Inc.
11 * Enhancements Copyright 2005 Alessandro Scotti
13 * The following terms apply to Digital Equipment Corporation's copyright
15 * ------------------------------------------------------------------------
18 * Permission to use, copy, modify, and distribute this software and its
19 * documentation for any purpose and without fee is hereby granted,
20 * provided that the above copyright notice appear in all copies and that
21 * both that copyright notice and this permission notice appear in
22 * supporting documentation, and that the name of Digital not be
23 * used in advertising or publicity pertaining to distribution of the
24 * software without specific, written prior permission.
26 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
27 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
28 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
29 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
30 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
31 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
33 * ------------------------------------------------------------------------
35 * The following terms apply to the enhanced version of XBoard
36 * distributed by the Free Software Foundation:
37 * ------------------------------------------------------------------------
39 * GNU XBoard is free software: you can redistribute it and/or modify
40 * it under the terms of the GNU General Public License as published by
41 * the Free Software Foundation, either version 3 of the License, or (at
42 * your option) any later version.
44 * GNU XBoard is distributed in the hope that it will be useful, but
45 * WITHOUT ANY WARRANTY; without even the implied warranty of
46 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
47 * General Public License for more details.
49 * You should have received a copy of the GNU General Public License
50 * along with this program. If not, see http://www.gnu.org/licenses/. *
52 *------------------------------------------------------------------------
53 ** See the file ChangeLog for a revision history. */
55 /* [AS] Also useful here for debugging */
59 int flock(int f, int code);
64 # define EGBB_NAME "egbbdll64.dll"
66 # define EGBB_NAME "egbbdll.dll"
71 # include <sys/file.h>
76 # define EGBB_NAME "egbbso64.so"
78 # define EGBB_NAME "egbbso.so"
80 // kludge to allow Windows code in back-end by converting it to corresponding Linux code
82 # define HMODULE void *
83 # define LoadLibrary(x) dlopen(x, RTLD_LAZY)
84 # define GetProcAddress dlsym
94 #include <sys/types.h>
103 #else /* not STDC_HEADERS */
106 # else /* not HAVE_STRING_H */
107 # include <strings.h>
108 # endif /* not HAVE_STRING_H */
109 #endif /* not STDC_HEADERS */
112 # include <sys/fcntl.h>
113 #else /* not HAVE_SYS_FCNTL_H */
116 # endif /* HAVE_FCNTL_H */
117 #endif /* not HAVE_SYS_FCNTL_H */
119 #if TIME_WITH_SYS_TIME
120 # include <sys/time.h>
124 # include <sys/time.h>
130 #if defined(_amigados) && !defined(__GNUC__)
135 extern int gettimeofday(struct timeval *, struct timezone *);
143 #include "frontend.h"
150 #include "backendz.h"
151 #include "evalgraph.h"
152 #include "engineoutput.h"
156 # define _(s) gettext (s)
157 # define N_(s) gettext_noop (s)
158 # define T_(s) gettext(s)
171 int establish P((void));
172 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
173 char *buf, int count, int error));
174 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
175 char *buf, int count, int error));
176 void SendToICS P((char *s));
177 void SendToICSDelayed P((char *s, long msdelay));
178 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
179 void HandleMachineMove P((char *message, ChessProgramState *cps));
180 int AutoPlayOneMove P((void));
181 int LoadGameOneMove P((ChessMove readAhead));
182 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
183 int LoadPositionFromFile P((char *filename, int n, char *title));
184 int SavePositionToFile P((char *filename));
185 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
186 void ShowMove P((int fromX, int fromY, int toX, int toY));
187 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
188 /*char*/int promoChar));
189 void BackwardInner P((int target));
190 void ForwardInner P((int target));
191 int Adjudicate P((ChessProgramState *cps));
192 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
193 void EditPositionDone P((Boolean fakeRights));
194 void PrintOpponents P((FILE *fp));
195 void PrintPosition P((FILE *fp, int move));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199 char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201 int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
250 extern void ConsoleCreate();
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
264 int deadRanks, handSize, handOffsets;
266 extern int tinyLayout, smallLayout;
267 ChessProgramStats programStats;
268 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
270 static int exiting = 0; /* [HGM] moved to top */
271 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
272 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
273 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
274 int partnerHighlight[2];
275 Boolean partnerBoardValid = 0;
276 char partnerStatus[MSG_SIZ];
278 Boolean originalFlip;
279 Boolean twoBoards = 0;
280 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
281 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
282 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
283 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
284 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
285 int opponentKibitzes;
286 int lastSavedGame; /* [HGM] save: ID of game */
287 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
288 extern int chatCount;
290 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
291 char legal[BOARD_RANKS][BOARD_FILES]; /* [HGM] legal target squares */
292 char lastMsg[MSG_SIZ];
293 char lastTalker[MSG_SIZ];
294 ChessSquare pieceSweep = EmptySquare;
295 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
296 int promoDefaultAltered;
297 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
298 static int initPing = -1;
299 int border; /* [HGM] width of board rim, needed to size seek graph */
300 char bestMove[MSG_SIZ], avoidMove[MSG_SIZ];
301 int solvingTime, totalTime;
303 /* States for ics_getting_history */
305 #define H_REQUESTED 1
306 #define H_GOT_REQ_HEADER 2
307 #define H_GOT_UNREQ_HEADER 3
308 #define H_GETTING_MOVES 4
309 #define H_GOT_UNWANTED_HEADER 5
311 /* whosays values for GameEnds */
320 /* Maximum number of games in a cmail message */
321 #define CMAIL_MAX_GAMES 20
323 /* Different types of move when calling RegisterMove */
325 #define CMAIL_RESIGN 1
327 #define CMAIL_ACCEPT 3
329 /* Different types of result to remember for each game */
330 #define CMAIL_NOT_RESULT 0
331 #define CMAIL_OLD_RESULT 1
332 #define CMAIL_NEW_RESULT 2
334 /* Telnet protocol constants */
345 safeStrCpy (char *dst, const char *src, size_t count)
348 assert( dst != NULL );
349 assert( src != NULL );
352 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
353 if( i == count && dst[count-1] != NULLCHAR)
355 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
356 if(appData.debugMode)
357 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
363 /* Some compiler can't cast u64 to double
364 * This function do the job for us:
366 * We use the highest bit for cast, this only
367 * works if the highest bit is not
368 * in use (This should not happen)
370 * We used this for all compiler
373 u64ToDouble (u64 value)
376 u64 tmp = value & u64Const(0x7fffffffffffffff);
377 r = (double)(s64)tmp;
378 if (value & u64Const(0x8000000000000000))
379 r += 9.2233720368547758080e18; /* 2^63 */
383 /* Fake up flags for now, as we aren't keeping track of castling
384 availability yet. [HGM] Change of logic: the flag now only
385 indicates the type of castlings allowed by the rule of the game.
386 The actual rights themselves are maintained in the array
387 castlingRights, as part of the game history, and are not probed
393 int flags = F_ALL_CASTLE_OK;
394 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
395 switch (gameInfo.variant) {
397 flags &= ~F_ALL_CASTLE_OK;
398 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
399 flags |= F_IGNORE_CHECK;
401 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
404 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
406 case VariantKriegspiel:
407 flags |= F_KRIEGSPIEL_CAPTURE;
409 case VariantCapaRandom:
410 case VariantFischeRandom:
411 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
412 case VariantNoCastle:
413 case VariantShatranj:
418 flags &= ~F_ALL_CASTLE_OK;
421 case VariantChuChess:
423 flags |= F_NULL_MOVE;
428 if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
432 FILE *gameFileFP, *debugFP, *serverFP;
433 char *currentDebugFile; // [HGM] debug split: to remember name
436 [AS] Note: sometimes, the sscanf() function is used to parse the input
437 into a fixed-size buffer. Because of this, we must be prepared to
438 receive strings as long as the size of the input buffer, which is currently
439 set to 4K for Windows and 8K for the rest.
440 So, we must either allocate sufficiently large buffers here, or
441 reduce the size of the input buffer in the input reading part.
444 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
445 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
446 char thinkOutput1[MSG_SIZ*10];
447 char promoRestrict[MSG_SIZ];
449 ChessProgramState first, second, pairing;
451 /* premove variables */
454 int premoveFromX = 0;
455 int premoveFromY = 0;
456 int premovePromoChar = 0;
458 Boolean alarmSounded;
459 /* end premove variables */
461 char *ics_prefix = "$";
462 enum ICS_TYPE ics_type = ICS_GENERIC;
464 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
465 int pauseExamForwardMostMove = 0;
466 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
467 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
468 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
469 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
470 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
471 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
472 int whiteFlag = FALSE, blackFlag = FALSE;
473 int userOfferedDraw = FALSE;
474 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
475 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
476 int cmailMoveType[CMAIL_MAX_GAMES];
477 long ics_clock_paused = 0;
478 ProcRef icsPR = NoProc, cmailPR = NoProc;
479 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
480 GameMode gameMode = BeginningOfGame;
481 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
482 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
483 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
484 int hiddenThinkOutputState = 0; /* [AS] */
485 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
486 int adjudicateLossPlies = 6;
487 char white_holding[64], black_holding[64];
488 TimeMark lastNodeCountTime;
489 long lastNodeCount=0;
490 int shiftKey, controlKey; // [HGM] set by mouse handler
492 int have_sent_ICS_logon = 0;
494 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
495 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
496 Boolean adjustedClock;
497 long timeControl_2; /* [AS] Allow separate time controls */
498 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
499 long timeRemaining[2][MAX_MOVES];
500 int matchGame = 0, nextGame = 0, roundNr = 0;
501 Boolean waitingForGame = FALSE, startingEngine = FALSE;
502 TimeMark programStartTime, pauseStart;
503 char ics_handle[MSG_SIZ];
504 int have_set_title = 0;
506 /* animateTraining preserves the state of appData.animate
507 * when Training mode is activated. This allows the
508 * response to be animated when appData.animate == TRUE and
509 * appData.animateDragging == TRUE.
511 Boolean animateTraining;
517 Board boards[MAX_MOVES];
518 /* [HGM] Following 7 needed for accurate legality tests: */
519 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
520 unsigned char initialRights[BOARD_FILES];
521 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
522 int initialRulePlies, FENrulePlies;
523 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
525 Boolean shuffleOpenings;
526 int mute; // mute all sounds
528 // [HGM] vari: next 12 to save and restore variations
529 #define MAX_VARIATIONS 10
530 int framePtr = MAX_MOVES-1; // points to free stack entry
532 int savedFirst[MAX_VARIATIONS];
533 int savedLast[MAX_VARIATIONS];
534 int savedFramePtr[MAX_VARIATIONS];
535 char *savedDetails[MAX_VARIATIONS];
536 ChessMove savedResult[MAX_VARIATIONS];
538 void PushTail P((int firstMove, int lastMove));
539 Boolean PopTail P((Boolean annotate));
540 void PushInner P((int firstMove, int lastMove));
541 void PopInner P((Boolean annotate));
542 void CleanupTail P((void));
544 ChessSquare FIDEArray[2][BOARD_FILES] = {
545 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
546 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
547 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
548 BlackKing, BlackBishop, BlackKnight, BlackRook }
551 ChessSquare twoKingsArray[2][BOARD_FILES] = {
552 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
553 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
554 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
555 BlackKing, BlackKing, BlackKnight, BlackRook }
558 ChessSquare KnightmateArray[2][BOARD_FILES] = {
559 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
560 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
561 { BlackRook, BlackMan, BlackBishop, BlackQueen,
562 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
565 ChessSquare SpartanArray[2][BOARD_FILES] = {
566 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
567 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
568 { BlackAlfil, BlackDragon, BlackKing, BlackTower,
569 BlackTower, BlackKing, BlackAngel, BlackAlfil }
572 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
573 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
574 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
575 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
576 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
579 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
580 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
581 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
582 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
583 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
586 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
587 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
588 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
589 { BlackRook, BlackKnight, BlackMan, BlackFerz,
590 BlackKing, BlackMan, BlackKnight, BlackRook }
593 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
594 { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
595 WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
596 { BlackRook, BlackKnight, BlackMan, BlackFerz,
597 BlackKing, BlackMan, BlackKnight, BlackRook }
600 ChessSquare lionArray[2][BOARD_FILES] = {
601 { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
602 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
603 { BlackRook, BlackLion, BlackBishop, BlackQueen,
604 BlackKing, BlackBishop, BlackKnight, BlackRook }
608 #if (BOARD_FILES>=10)
609 ChessSquare ShogiArray[2][BOARD_FILES] = {
610 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
611 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
612 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
613 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
616 ChessSquare XiangqiArray[2][BOARD_FILES] = {
617 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
618 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
619 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
620 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
623 ChessSquare CapablancaArray[2][BOARD_FILES] = {
624 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
625 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
626 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
627 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
630 ChessSquare GreatArray[2][BOARD_FILES] = {
631 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
632 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
633 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
634 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
637 ChessSquare JanusArray[2][BOARD_FILES] = {
638 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
639 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
640 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
641 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
644 ChessSquare GrandArray[2][BOARD_FILES] = {
645 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
646 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
647 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
648 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
651 ChessSquare ChuChessArray[2][BOARD_FILES] = {
652 { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
653 WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
654 { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
655 BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
659 ChessSquare GothicArray[2][BOARD_FILES] = {
660 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
661 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
662 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
663 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
666 #define GothicArray CapablancaArray
670 ChessSquare FalconArray[2][BOARD_FILES] = {
671 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
672 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
673 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
674 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
677 #define FalconArray CapablancaArray
680 #else // !(BOARD_FILES>=10)
681 #define XiangqiPosition FIDEArray
682 #define CapablancaArray FIDEArray
683 #define GothicArray FIDEArray
684 #define GreatArray FIDEArray
685 #endif // !(BOARD_FILES>=10)
687 #if (BOARD_FILES>=12)
688 ChessSquare CourierArray[2][BOARD_FILES] = {
689 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
690 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
691 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
692 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
694 ChessSquare ChuArray[6][BOARD_FILES] = {
695 { WhiteLance, WhiteCat, WhiteCopper, WhiteFerz, WhiteWazir, WhiteKing,
696 WhiteAlfil, WhiteWazir, WhiteFerz, WhiteCopper, WhiteCat, WhiteLance },
697 { BlackLance, BlackCat, BlackCopper, BlackFerz, BlackWazir, BlackAlfil,
698 BlackKing, BlackWazir, BlackFerz, BlackCopper, BlackCat, BlackLance },
699 { WhiteAxe, EmptySquare, WhiteBishop, EmptySquare, WhiteClaw, WhiteMarshall,
700 WhiteAngel, WhiteClaw, EmptySquare, WhiteBishop, EmptySquare, WhiteAxe },
701 { BlackAxe, EmptySquare, BlackBishop, EmptySquare, BlackClaw, BlackAngel,
702 BlackMarshall, BlackClaw, EmptySquare, BlackBishop, EmptySquare, BlackAxe },
703 { WhiteDagger, WhiteSword, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
704 WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSword, WhiteDagger },
705 { BlackDagger, BlackSword, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
706 BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSword, BlackDagger }
708 #else // !(BOARD_FILES>=12)
709 #define CourierArray CapablancaArray
710 #define ChuArray CapablancaArray
711 #endif // !(BOARD_FILES>=12)
714 Board initialPosition;
717 /* Convert str to a rating. Checks for special cases of "----",
719 "++++", etc. Also strips ()'s */
721 string_to_rating (char *str)
723 while(*str && !isdigit(*str)) ++str;
725 return 0; /* One of the special "no rating" cases */
733 /* Init programStats */
734 programStats.movelist[0] = 0;
735 programStats.depth = 0;
736 programStats.nr_moves = 0;
737 programStats.moves_left = 0;
738 programStats.nodes = 0;
739 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
740 programStats.score = 0;
741 programStats.got_only_move = 0;
742 programStats.got_fail = 0;
743 programStats.line_is_book = 0;
748 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
749 if (appData.firstPlaysBlack) {
750 first.twoMachinesColor = "black\n";
751 second.twoMachinesColor = "white\n";
753 first.twoMachinesColor = "white\n";
754 second.twoMachinesColor = "black\n";
757 first.other = &second;
758 second.other = &first;
761 if(appData.timeOddsMode) {
762 norm = appData.timeOdds[0];
763 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
765 first.timeOdds = appData.timeOdds[0]/norm;
766 second.timeOdds = appData.timeOdds[1]/norm;
769 if(programVersion) free(programVersion);
770 if (appData.noChessProgram) {
771 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
772 sprintf(programVersion, "%s", PACKAGE_STRING);
774 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
775 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
776 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
781 UnloadEngine (ChessProgramState *cps)
783 /* Kill off first chess program */
784 if (cps->isr != NULL)
785 RemoveInputSource(cps->isr);
788 if (cps->pr != NoProc) {
790 DoSleep( appData.delayBeforeQuit );
791 SendToProgram("quit\n", cps);
792 DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
795 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
799 ClearOptions (ChessProgramState *cps)
802 cps->nrOptions = cps->comboCnt = 0;
803 for(i=0; i<MAX_OPTIONS; i++) {
804 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
805 cps->option[i].textValue = 0;
809 char *engineNames[] = {
810 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
811 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
813 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
814 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
819 InitEngine (ChessProgramState *cps, int n)
820 { // [HGM] all engine initialiation put in a function that does one engine
824 cps->which = engineNames[n];
825 cps->maybeThinking = FALSE;
829 cps->sendDrawOffers = 1;
831 cps->program = appData.chessProgram[n];
832 cps->host = appData.host[n];
833 cps->dir = appData.directory[n];
834 cps->initString = appData.engInitString[n];
835 cps->computerString = appData.computerString[n];
836 cps->useSigint = TRUE;
837 cps->useSigterm = TRUE;
838 cps->reuse = appData.reuse[n];
839 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
840 cps->useSetboard = FALSE;
842 cps->usePing = FALSE;
845 cps->usePlayother = FALSE;
846 cps->useColors = TRUE;
847 cps->useUsermove = FALSE;
848 cps->sendICS = FALSE;
849 cps->sendName = appData.icsActive;
850 cps->sdKludge = FALSE;
851 cps->stKludge = FALSE;
852 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
853 TidyProgramName(cps->program, cps->host, cps->tidy);
855 ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
856 cps->analysisSupport = 2; /* detect */
857 cps->analyzing = FALSE;
858 cps->initDone = FALSE;
860 cps->pseudo = appData.pseudo[n];
862 /* New features added by Tord: */
863 cps->useFEN960 = FALSE;
864 cps->useOOCastle = TRUE;
865 /* End of new features added by Tord. */
866 cps->fenOverride = appData.fenOverride[n];
868 /* [HGM] time odds: set factor for each machine */
869 cps->timeOdds = appData.timeOdds[n];
871 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
872 cps->accumulateTC = appData.accumulateTC[n];
873 cps->maxNrOfSessions = 1;
878 cps->drawDepth = appData.drawDepth[n];
879 cps->supportsNPS = UNKNOWN;
880 cps->memSize = FALSE;
881 cps->maxCores = FALSE;
882 ASSIGN(cps->egtFormats, "");
885 cps->optionSettings = appData.engOptions[n];
887 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
888 cps->isUCI = appData.isUCI[n]; /* [AS] */
889 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
892 if (appData.protocolVersion[n] > PROTOVER
893 || appData.protocolVersion[n] < 1)
898 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
899 appData.protocolVersion[n]);
900 if( (len >= MSG_SIZ) && appData.debugMode )
901 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
903 DisplayFatalError(buf, 0, 2);
907 cps->protocolVersion = appData.protocolVersion[n];
910 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
911 ParseFeatures(appData.featureDefaults, cps);
914 ChessProgramState *savCps;
916 GameMode oldMode, tryNr;
918 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
919 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
920 char *insert, *wbOptions, *currentEngine[2]; // point in ChessProgramNames were we should insert new engine
921 static char newEngineCommand[MSG_SIZ];
924 FloatToFront(char **list, char *engineLine)
926 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
928 if(appData.recentEngines <= 0) return;
929 TidyProgramName(engineLine, "localhost", tidy+1);
930 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
931 strncpy(buf+1, *list, MSG_SIZ-50);
932 if(p = strstr(buf, tidy)) { // tidy name appears in list
933 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
934 while(*p++ = *++q); // squeeze out
936 strcat(tidy, buf+1); // put list behind tidy name
937 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
938 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
939 ASSIGN(*list, tidy+1);
946 if(*engineListFile && (f = fopen(engineListFile, "w"))) {
947 fprintf(f, "-firstChessProgramNames {%s}\n", firstChessProgramNames);
953 AddToEngineList (int i)
956 char quote, buf[MSG_SIZ];
957 char *q = firstChessProgramNames, *p = newEngineCommand;
958 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
959 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
960 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s",
961 quote, p, quote, appData.directory[i],
962 useNick ? " -fn \"" : "",
963 useNick ? nickName : "",
965 v1 ? " -firstProtocolVersion 1" : "",
966 hasBook ? "" : " -fNoOwnBookUCI",
967 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
968 storeVariant ? " -variant " : "",
969 storeVariant ? VariantName(gameInfo.variant) : "");
970 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " %s", wbOptions);
971 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 2);
972 if(insert != q) insert[-1] = NULLCHAR;
973 snprintf(firstChessProgramNames, len, "%s\n%s\n%s", q, buf, insert);
976 FloatToFront(&appData.recentEngineList, buf);
977 ASSIGN(currentEngine[i], buf);
984 if(WaitForEngine(savCps, LoadEngine)) return;
985 if(tryNr == 1 && !isUCI) { SendToProgram("uci\n", savCps); tryNr = 2; ScheduleDelayedEvent(LoadEngine, FEATURE_TIMEOUT); return; }
986 if(tryNr) v1 |= (tryNr == 2), tryNr = 0, AddToEngineList(0); // deferred to after protocol determination
987 CommonEngineInit(); // recalculate time odds
988 if(gameInfo.variant != StringToVariant(appData.variant)) {
989 // we changed variant when loading the engine; this forces us to reset
990 Reset(TRUE, savCps != &first);
991 oldMode = BeginningOfGame; // to prevent restoring old mode
993 InitChessProgram(savCps, FALSE);
994 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
995 DisplayMessage("", "");
996 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
997 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
1000 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
1004 ReplaceEngine (ChessProgramState *cps, int n)
1006 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
1008 if(oldMode != BeginningOfGame) EditGameEvent();
1011 appData.noChessProgram = FALSE;
1012 appData.clockMode = TRUE;
1015 if(n && !tryNr) return; // only startup first engine immediately; second can wait (unless autodetect)
1016 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
1020 static char resetOptions[] =
1021 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
1022 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
1023 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
1024 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
1027 Load (ChessProgramState *cps, int i)
1029 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
1030 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
1031 ASSIGN(currentEngine[i], engineLine);
1032 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
1033 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
1034 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
1035 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
1036 appData.firstProtocolVersion = PROTOVER;
1037 ParseArgsFromString(buf);
1039 ReplaceEngine(cps, i);
1040 FloatToFront(&appData.recentEngineList, engineLine);
1041 if(gameMode == BeginningOfGame) Reset(TRUE, TRUE);
1045 while(q = strchr(p, SLASH)) p = q+1;
1046 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1047 if(engineDir[0] != NULLCHAR) {
1048 ASSIGN(appData.directory[i], engineDir); p = engineName;
1049 } else if(p != engineName) { // derive directory from engine path, when not given
1051 ASSIGN(appData.directory[i], engineName);
1053 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1054 } else { ASSIGN(appData.directory[i], "."); }
1055 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1057 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1058 snprintf(command, MSG_SIZ, "%s %s", p, params);
1061 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1062 ASSIGN(appData.chessProgram[i], p);
1063 tryNr = 3; // requests adding to list without auto-detect
1064 if(isUCI == 3) tryNr = 1, isUCI = 0; // auto-detect
1065 appData.isUCI[i] = isUCI;
1066 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1067 appData.hasOwnBookUCI[i] = hasBook;
1068 if(!nickName[0]) useNick = FALSE;
1069 if(useNick) ASSIGN(appData.pgnName[i], nickName);
1070 safeStrCpy(newEngineCommand, p, MSG_SIZ);
1071 ReplaceEngine(cps, i);
1077 int matched, min, sec;
1079 * Parse timeControl resource
1081 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1082 appData.movesPerSession)) {
1084 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1085 DisplayFatalError(buf, 0, 2);
1089 * Parse searchTime resource
1091 if (*appData.searchTime != NULLCHAR) {
1092 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1094 searchTime = min * 60;
1095 } else if (matched == 2) {
1096 searchTime = min * 60 + sec;
1099 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1100 DisplayFatalError(buf, 0, 2);
1109 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1110 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1112 GetTimeMark(&programStartTime);
1113 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1114 appData.seedBase = random() + (random()<<15);
1115 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1117 ClearProgramStats();
1118 programStats.ok_to_send = 1;
1119 programStats.seen_stat = 0;
1122 * Initialize game list
1128 * Internet chess server status
1130 if (appData.icsActive) {
1131 appData.matchMode = FALSE;
1132 appData.matchGames = 0;
1134 appData.noChessProgram = !appData.zippyPlay;
1136 appData.zippyPlay = FALSE;
1137 appData.zippyTalk = FALSE;
1138 appData.noChessProgram = TRUE;
1140 if (*appData.icsHelper != NULLCHAR) {
1141 appData.useTelnet = TRUE;
1142 appData.telnetProgram = appData.icsHelper;
1145 appData.zippyTalk = appData.zippyPlay = FALSE;
1148 /* [AS] Initialize pv info list [HGM] and game state */
1152 for( i=0; i<=framePtr; i++ ) {
1153 pvInfoList[i].depth = -1;
1154 boards[i][EP_STATUS] = EP_NONE;
1155 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1161 /* [AS] Adjudication threshold */
1162 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1164 InitEngine(&first, 0);
1165 InitEngine(&second, 1);
1168 pairing.which = "pairing"; // pairing engine
1169 pairing.pr = NoProc;
1171 pairing.program = appData.pairingEngine;
1172 pairing.host = "localhost";
1175 if (appData.icsActive) {
1176 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1177 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1178 appData.clockMode = FALSE;
1179 first.sendTime = second.sendTime = 0;
1183 /* Override some settings from environment variables, for backward
1184 compatibility. Unfortunately it's not feasible to have the env
1185 vars just set defaults, at least in xboard. Ugh.
1187 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1192 if (!appData.icsActive) {
1196 /* Check for variants that are supported only in ICS mode,
1197 or not at all. Some that are accepted here nevertheless
1198 have bugs; see comments below.
1200 VariantClass variant = StringToVariant(appData.variant);
1202 case VariantBughouse: /* need four players and two boards */
1203 case VariantKriegspiel: /* need to hide pieces and move details */
1204 /* case VariantFischeRandom: (Fabien: moved below) */
1205 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1206 if( (len >= MSG_SIZ) && appData.debugMode )
1207 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1209 DisplayFatalError(buf, 0, 2);
1212 case VariantUnknown:
1213 case VariantLoadable:
1223 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1224 if( (len >= MSG_SIZ) && appData.debugMode )
1225 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1227 DisplayFatalError(buf, 0, 2);
1230 case VariantNormal: /* definitely works! */
1231 if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1232 safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1235 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1236 case VariantFairy: /* [HGM] TestLegality definitely off! */
1237 case VariantGothic: /* [HGM] should work */
1238 case VariantCapablanca: /* [HGM] should work */
1239 case VariantCourier: /* [HGM] initial forced moves not implemented */
1240 case VariantShogi: /* [HGM] could still mate with pawn drop */
1241 case VariantChu: /* [HGM] experimental */
1242 case VariantKnightmate: /* [HGM] should work */
1243 case VariantCylinder: /* [HGM] untested */
1244 case VariantFalcon: /* [HGM] untested */
1245 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1246 offboard interposition not understood */
1247 case VariantWildCastle: /* pieces not automatically shuffled */
1248 case VariantNoCastle: /* pieces not automatically shuffled */
1249 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1250 case VariantLosers: /* should work except for win condition,
1251 and doesn't know captures are mandatory */
1252 case VariantSuicide: /* should work except for win condition,
1253 and doesn't know captures are mandatory */
1254 case VariantGiveaway: /* should work except for win condition,
1255 and doesn't know captures are mandatory */
1256 case VariantTwoKings: /* should work */
1257 case VariantAtomic: /* should work except for win condition */
1258 case Variant3Check: /* should work except for win condition */
1259 case VariantShatranj: /* should work except for all win conditions */
1260 case VariantMakruk: /* should work except for draw countdown */
1261 case VariantASEAN : /* should work except for draw countdown */
1262 case VariantBerolina: /* might work if TestLegality is off */
1263 case VariantCapaRandom: /* should work */
1264 case VariantJanus: /* should work */
1265 case VariantSuper: /* experimental */
1266 case VariantGreat: /* experimental, requires legality testing to be off */
1267 case VariantSChess: /* S-Chess, should work */
1268 case VariantGrand: /* should work */
1269 case VariantSpartan: /* should work */
1270 case VariantLion: /* should work */
1271 case VariantChuChess: /* should work */
1279 NextIntegerFromString (char ** str, long * value)
1284 while( *s == ' ' || *s == '\t' ) {
1290 if( *s >= '0' && *s <= '9' ) {
1291 while( *s >= '0' && *s <= '9' ) {
1292 *value = *value * 10 + (*s - '0');
1305 NextTimeControlFromString (char ** str, long * value)
1308 int result = NextIntegerFromString( str, &temp );
1311 *value = temp * 60; /* Minutes */
1312 if( **str == ':' ) {
1314 result = NextIntegerFromString( str, &temp );
1315 *value += temp; /* Seconds */
1323 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1324 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1325 int result = -1, type = 0; long temp, temp2;
1327 if(**str != ':') return -1; // old params remain in force!
1329 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1330 if( NextIntegerFromString( str, &temp ) ) return -1;
1331 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1334 /* time only: incremental or sudden-death time control */
1335 if(**str == '+') { /* increment follows; read it */
1337 if(**str == '!') type = *(*str)++; // Bronstein TC
1338 if(result = NextIntegerFromString( str, &temp2)) return -1;
1339 *inc = temp2 * 1000;
1340 if(**str == '.') { // read fraction of increment
1341 char *start = ++(*str);
1342 if(result = NextIntegerFromString( str, &temp2)) return -1;
1344 while(start++ < *str) temp2 /= 10;
1348 *moves = 0; *tc = temp * 1000; *incType = type;
1352 (*str)++; /* classical time control */
1353 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1365 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1366 { /* [HGM] get time to add from the multi-session time-control string */
1367 int incType, moves=1; /* kludge to force reading of first session */
1368 long time, increment;
1371 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1373 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1374 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1375 if(movenr == -1) return time; /* last move before new session */
1376 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1377 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1378 if(!moves) return increment; /* current session is incremental */
1379 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1380 } while(movenr >= -1); /* try again for next session */
1382 return 0; // no new time quota on this move
1386 ParseTimeControl (char *tc, float ti, int mps)
1390 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1393 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1394 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1395 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1399 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1401 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1404 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1406 snprintf(buf, MSG_SIZ, ":%s", mytc);
1408 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1410 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1415 /* Parse second time control */
1418 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1426 timeControl_2 = tc2 * 1000;
1436 timeControl = tc1 * 1000;
1439 timeIncrement = ti * 1000; /* convert to ms */
1440 movesPerSession = 0;
1443 movesPerSession = mps;
1451 if (appData.debugMode) {
1452 # ifdef __GIT_VERSION
1453 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1455 fprintf(debugFP, "Version: %s\n", programVersion);
1458 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1460 set_cont_sequence(appData.wrapContSeq);
1461 if (appData.matchGames > 0) {
1462 appData.matchMode = TRUE;
1463 } else if (appData.matchMode) {
1464 appData.matchGames = 1;
1466 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1467 appData.matchGames = appData.sameColorGames;
1468 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1469 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1470 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1473 if (appData.noChessProgram || first.protocolVersion == 1) {
1476 /* kludge: allow timeout for initial "feature" commands */
1478 DisplayMessage("", _("Starting chess program"));
1479 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1484 CalculateIndex (int index, int gameNr)
1485 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1487 if(index > 0) return index; // fixed nmber
1488 if(index == 0) return 1;
1489 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1490 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1495 LoadGameOrPosition (int gameNr)
1496 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1497 if (*appData.loadGameFile != NULLCHAR) {
1498 if (!LoadGameFromFile(appData.loadGameFile,
1499 CalculateIndex(appData.loadGameIndex, gameNr),
1500 appData.loadGameFile, FALSE)) {
1501 DisplayFatalError(_("Bad game file"), 0, 1);
1504 } else if (*appData.loadPositionFile != NULLCHAR) {
1505 if (!LoadPositionFromFile(appData.loadPositionFile,
1506 CalculateIndex(appData.loadPositionIndex, gameNr),
1507 appData.loadPositionFile)) {
1508 DisplayFatalError(_("Bad position file"), 0, 1);
1516 ReserveGame (int gameNr, char resChar)
1518 FILE *tf = fopen(appData.tourneyFile, "r+");
1519 char *p, *q, c, buf[MSG_SIZ];
1520 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1521 safeStrCpy(buf, lastMsg, MSG_SIZ);
1522 DisplayMessage(_("Pick new game"), "");
1523 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1524 ParseArgsFromFile(tf);
1525 p = q = appData.results;
1526 if(appData.debugMode) {
1527 char *r = appData.participants;
1528 fprintf(debugFP, "results = '%s'\n", p);
1529 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1530 fprintf(debugFP, "\n");
1532 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1534 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1535 safeStrCpy(q, p, strlen(p) + 2);
1536 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1537 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1538 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1539 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1542 fseek(tf, -(strlen(p)+4), SEEK_END);
1544 if(c != '"') // depending on DOS or Unix line endings we can be one off
1545 fseek(tf, -(strlen(p)+2), SEEK_END);
1546 else fseek(tf, -(strlen(p)+3), SEEK_END);
1547 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1548 DisplayMessage(buf, "");
1549 free(p); appData.results = q;
1550 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1551 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1552 int round = appData.defaultMatchGames * appData.tourneyType;
1553 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1554 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1555 UnloadEngine(&first); // next game belongs to other pairing;
1556 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1558 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1562 MatchEvent (int mode)
1563 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1565 if(matchMode) { // already in match mode: switch it off
1567 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1570 // if(gameMode != BeginningOfGame) {
1571 // DisplayError(_("You can only start a match from the initial position."), 0);
1575 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1576 /* Set up machine vs. machine match */
1578 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1579 if(appData.tourneyFile[0]) {
1581 if(nextGame > appData.matchGames) {
1583 if(strchr(appData.results, '*') == NULL) {
1585 appData.tourneyCycles++;
1586 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1588 NextTourneyGame(-1, &dummy);
1590 if(nextGame <= appData.matchGames) {
1591 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1593 ScheduleDelayedEvent(NextMatchGame, 10000);
1598 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1599 DisplayError(buf, 0);
1600 appData.tourneyFile[0] = 0;
1604 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1605 DisplayFatalError(_("Can't have a match with no chess programs"),
1610 matchGame = roundNr = 1;
1611 first.matchWins = second.matchWins = totalTime = 0; // [HGM] match: needed in later matches
1616 InitBackEnd3 P((void))
1618 GameMode initialMode;
1622 ParseFeatures(appData.features[0], &first);
1623 if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode && // mode involves only first engine
1624 !strcmp(appData.variant, "normal") && // no explicit variant request
1625 appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 && // no size overrides requested
1626 !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") && // but 'normal' won't work with engine
1627 !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1628 char c, *q = first.variants, *p = strchr(q, ',');
1629 if(p) *p = NULLCHAR;
1630 if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1632 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1633 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1634 ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1635 Reset(TRUE, FALSE); // and re-initialize
1640 InitChessProgram(&first, startedFromSetupPosition);
1642 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1643 free(programVersion);
1644 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1645 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1646 FloatToFront(&appData.recentEngineList, currentEngine[0] ? currentEngine[0] : appData.firstChessProgram);
1649 if (appData.icsActive) {
1651 /* [DM] Make a console window if needed [HGM] merged ifs */
1657 if (*appData.icsCommPort != NULLCHAR)
1658 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1659 appData.icsCommPort);
1661 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1662 appData.icsHost, appData.icsPort);
1664 if( (len >= MSG_SIZ) && appData.debugMode )
1665 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1667 DisplayFatalError(buf, err, 1);
1672 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1674 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1675 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1676 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1677 } else if (appData.noChessProgram) {
1683 if (*appData.cmailGameName != NULLCHAR) {
1685 OpenLoopback(&cmailPR);
1687 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1691 DisplayMessage("", "");
1692 if (StrCaseCmp(appData.initialMode, "") == 0) {
1693 initialMode = BeginningOfGame;
1694 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1695 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1696 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1697 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1700 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1701 initialMode = TwoMachinesPlay;
1702 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1703 initialMode = AnalyzeFile;
1704 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1705 initialMode = AnalyzeMode;
1706 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1707 initialMode = MachinePlaysWhite;
1708 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1709 initialMode = MachinePlaysBlack;
1710 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1711 initialMode = EditGame;
1712 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1713 initialMode = EditPosition;
1714 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1715 initialMode = Training;
1717 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1718 if( (len >= MSG_SIZ) && appData.debugMode )
1719 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1721 DisplayFatalError(buf, 0, 2);
1725 if (appData.matchMode) {
1726 if(appData.tourneyFile[0]) { // start tourney from command line
1728 if(f = fopen(appData.tourneyFile, "r")) {
1729 ParseArgsFromFile(f); // make sure tourney parmeters re known
1731 appData.clockMode = TRUE;
1733 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1736 } else if (*appData.cmailGameName != NULLCHAR) {
1737 /* Set up cmail mode */
1738 ReloadCmailMsgEvent(TRUE);
1740 /* Set up other modes */
1741 if (initialMode == AnalyzeFile) {
1742 if (*appData.loadGameFile == NULLCHAR) {
1743 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1747 if (*appData.loadGameFile != NULLCHAR) {
1748 (void) LoadGameFromFile(appData.loadGameFile,
1749 appData.loadGameIndex,
1750 appData.loadGameFile, TRUE);
1751 } else if (*appData.loadPositionFile != NULLCHAR) {
1752 (void) LoadPositionFromFile(appData.loadPositionFile,
1753 appData.loadPositionIndex,
1754 appData.loadPositionFile);
1755 /* [HGM] try to make self-starting even after FEN load */
1756 /* to allow automatic setup of fairy variants with wtm */
1757 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1758 gameMode = BeginningOfGame;
1759 setboardSpoiledMachineBlack = 1;
1761 /* [HGM] loadPos: make that every new game uses the setup */
1762 /* from file as long as we do not switch variant */
1763 if(!blackPlaysFirst) {
1764 startedFromPositionFile = TRUE;
1765 CopyBoard(filePosition, boards[0]);
1766 CopyBoard(initialPosition, boards[0]);
1768 } else if(*appData.fen != NULLCHAR) {
1769 if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
1770 startedFromPositionFile = TRUE;
1774 if (initialMode == AnalyzeMode) {
1775 if (appData.noChessProgram) {
1776 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1779 if (appData.icsActive) {
1780 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1784 } else if (initialMode == AnalyzeFile) {
1785 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1786 ShowThinkingEvent();
1788 AnalysisPeriodicEvent(1);
1789 } else if (initialMode == MachinePlaysWhite) {
1790 if (appData.noChessProgram) {
1791 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1795 if (appData.icsActive) {
1796 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1800 MachineWhiteEvent();
1801 } else if (initialMode == MachinePlaysBlack) {
1802 if (appData.noChessProgram) {
1803 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1807 if (appData.icsActive) {
1808 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1812 MachineBlackEvent();
1813 } else if (initialMode == TwoMachinesPlay) {
1814 if (appData.noChessProgram) {
1815 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1819 if (appData.icsActive) {
1820 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1825 } else if (initialMode == EditGame) {
1827 } else if (initialMode == EditPosition) {
1828 EditPositionEvent();
1829 } else if (initialMode == Training) {
1830 if (*appData.loadGameFile == NULLCHAR) {
1831 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1840 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1842 DisplayBook(current+1);
1844 MoveHistorySet( movelist, first, last, current, pvInfoList );
1846 EvalGraphSet( first, last, current, pvInfoList );
1848 MakeEngineOutputTitle();
1852 * Establish will establish a contact to a remote host.port.
1853 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1854 * used to talk to the host.
1855 * Returns 0 if okay, error code if not.
1862 if (*appData.icsCommPort != NULLCHAR) {
1863 /* Talk to the host through a serial comm port */
1864 return OpenCommPort(appData.icsCommPort, &icsPR);
1866 } else if (*appData.gateway != NULLCHAR) {
1867 if (*appData.remoteShell == NULLCHAR) {
1868 /* Use the rcmd protocol to run telnet program on a gateway host */
1869 snprintf(buf, sizeof(buf), "%s %s %s",
1870 appData.telnetProgram, appData.icsHost, appData.icsPort);
1871 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1874 /* Use the rsh program to run telnet program on a gateway host */
1875 if (*appData.remoteUser == NULLCHAR) {
1876 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1877 appData.gateway, appData.telnetProgram,
1878 appData.icsHost, appData.icsPort);
1880 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1881 appData.remoteShell, appData.gateway,
1882 appData.remoteUser, appData.telnetProgram,
1883 appData.icsHost, appData.icsPort);
1885 return StartChildProcess(buf, "", &icsPR);
1888 } else if (appData.useTelnet) {
1889 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1892 /* TCP socket interface differs somewhat between
1893 Unix and NT; handle details in the front end.
1895 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1900 EscapeExpand (char *p, char *q)
1901 { // [HGM] initstring: routine to shape up string arguments
1902 while(*p++ = *q++) if(p[-1] == '\\')
1904 case 'n': p[-1] = '\n'; break;
1905 case 'r': p[-1] = '\r'; break;
1906 case 't': p[-1] = '\t'; break;
1907 case '\\': p[-1] = '\\'; break;
1908 case 0: *p = 0; return;
1909 default: p[-1] = q[-1]; break;
1914 show_bytes (FILE *fp, char *buf, int count)
1917 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1918 fprintf(fp, "\\%03o", *buf & 0xff);
1927 /* Returns an errno value */
1929 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1931 char buf[8192], *p, *q, *buflim;
1932 int left, newcount, outcount;
1934 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1935 *appData.gateway != NULLCHAR) {
1936 if (appData.debugMode) {
1937 fprintf(debugFP, ">ICS: ");
1938 show_bytes(debugFP, message, count);
1939 fprintf(debugFP, "\n");
1941 return OutputToProcess(pr, message, count, outError);
1944 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1951 if (appData.debugMode) {
1952 fprintf(debugFP, ">ICS: ");
1953 show_bytes(debugFP, buf, newcount);
1954 fprintf(debugFP, "\n");
1956 outcount = OutputToProcess(pr, buf, newcount, outError);
1957 if (outcount < newcount) return -1; /* to be sure */
1964 } else if (((unsigned char) *p) == TN_IAC) {
1965 *q++ = (char) TN_IAC;
1972 if (appData.debugMode) {
1973 fprintf(debugFP, ">ICS: ");
1974 show_bytes(debugFP, buf, newcount);
1975 fprintf(debugFP, "\n");
1977 outcount = OutputToProcess(pr, buf, newcount, outError);
1978 if (outcount < newcount) return -1; /* to be sure */
1983 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1985 int outError, outCount;
1986 static int gotEof = 0;
1989 /* Pass data read from player on to ICS */
1992 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1993 if (outCount < count) {
1994 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1996 if(have_sent_ICS_logon == 2) {
1997 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1998 fprintf(ini, "%s", message);
1999 have_sent_ICS_logon = 3;
2001 have_sent_ICS_logon = 1;
2002 } else if(have_sent_ICS_logon == 3) {
2003 fprintf(ini, "%s", message);
2005 have_sent_ICS_logon = 1;
2007 } else if (count < 0) {
2008 RemoveInputSource(isr);
2009 DisplayFatalError(_("Error reading from keyboard"), error, 1);
2010 } else if (gotEof++ > 0) {
2011 RemoveInputSource(isr);
2012 DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
2018 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
2019 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
2020 connectionAlive = FALSE; // only sticks if no response to 'date' command.
2021 SendToICS("date\n");
2022 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
2025 /* added routine for printf style output to ics */
2027 ics_printf (char *format, ...)
2029 char buffer[MSG_SIZ];
2032 va_start(args, format);
2033 vsnprintf(buffer, sizeof(buffer), format, args);
2034 buffer[sizeof(buffer)-1] = '\0';
2042 int count, outCount, outError;
2044 if (icsPR == NoProc) return;
2047 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2048 if (outCount < count) {
2049 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2053 /* This is used for sending logon scripts to the ICS. Sending
2054 without a delay causes problems when using timestamp on ICC
2055 (at least on my machine). */
2057 SendToICSDelayed (char *s, long msdelay)
2059 int count, outCount, outError;
2061 if (icsPR == NoProc) return;
2064 if (appData.debugMode) {
2065 fprintf(debugFP, ">ICS: ");
2066 show_bytes(debugFP, s, count);
2067 fprintf(debugFP, "\n");
2069 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2071 if (outCount < count) {
2072 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2077 /* Remove all highlighting escape sequences in s
2078 Also deletes any suffix starting with '('
2081 StripHighlightAndTitle (char *s)
2083 static char retbuf[MSG_SIZ];
2086 while (*s != NULLCHAR) {
2087 while (*s == '\033') {
2088 while (*s != NULLCHAR && !isalpha(*s)) s++;
2089 if (*s != NULLCHAR) s++;
2091 while (*s != NULLCHAR && *s != '\033') {
2092 if (*s == '(' || *s == '[') {
2103 /* Remove all highlighting escape sequences in s */
2105 StripHighlight (char *s)
2107 static char retbuf[MSG_SIZ];
2110 while (*s != NULLCHAR) {
2111 while (*s == '\033') {
2112 while (*s != NULLCHAR && !isalpha(*s)) s++;
2113 if (*s != NULLCHAR) s++;
2115 while (*s != NULLCHAR && *s != '\033') {
2123 char engineVariant[MSG_SIZ];
2124 char *variantNames[] = VARIANT_NAMES;
2126 VariantName (VariantClass v)
2128 if(v == VariantUnknown || *engineVariant) return engineVariant;
2129 return variantNames[v];
2133 /* Identify a variant from the strings the chess servers use or the
2134 PGN Variant tag names we use. */
2136 StringToVariant (char *e)
2140 VariantClass v = VariantNormal;
2141 int i, found = FALSE;
2142 char buf[MSG_SIZ], c;
2147 /* [HGM] skip over optional board-size prefixes */
2148 if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2149 sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2150 while( *e++ != '_');
2153 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2157 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2158 if (p = StrCaseStr(e, variantNames[i])) {
2159 if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2160 v = (VariantClass) i;
2167 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2168 || StrCaseStr(e, "wild/fr")
2169 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2170 v = VariantFischeRandom;
2171 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2172 (i = 1, p = StrCaseStr(e, "w"))) {
2174 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2181 case 0: /* FICS only, actually */
2183 /* Castling legal even if K starts on d-file */
2184 v = VariantWildCastle;
2189 /* Castling illegal even if K & R happen to start in
2190 normal positions. */
2191 v = VariantNoCastle;
2204 /* Castling legal iff K & R start in normal positions */
2210 /* Special wilds for position setup; unclear what to do here */
2211 v = VariantLoadable;
2214 /* Bizarre ICC game */
2215 v = VariantTwoKings;
2218 v = VariantKriegspiel;
2224 v = VariantFischeRandom;
2227 v = VariantCrazyhouse;
2230 v = VariantBughouse;
2236 /* Not quite the same as FICS suicide! */
2237 v = VariantGiveaway;
2243 v = VariantShatranj;
2246 /* Temporary names for future ICC types. The name *will* change in
2247 the next xboard/WinBoard release after ICC defines it. */
2285 v = VariantCapablanca;
2288 v = VariantKnightmate;
2294 v = VariantCylinder;
2300 v = VariantCapaRandom;
2303 v = VariantBerolina;
2315 /* Found "wild" or "w" in the string but no number;
2316 must assume it's normal chess. */
2320 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2321 if( (len >= MSG_SIZ) && appData.debugMode )
2322 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2324 DisplayError(buf, 0);
2330 if (appData.debugMode) {
2331 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2332 e, wnum, VariantName(v));
2337 static int leftover_start = 0, leftover_len = 0;
2338 char star_match[STAR_MATCH_N][MSG_SIZ];
2340 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2341 advance *index beyond it, and set leftover_start to the new value of
2342 *index; else return FALSE. If pattern contains the character '*', it
2343 matches any sequence of characters not containing '\r', '\n', or the
2344 character following the '*' (if any), and the matched sequence(s) are
2345 copied into star_match.
2348 looking_at ( char *buf, int *index, char *pattern)
2350 char *bufp = &buf[*index], *patternp = pattern;
2352 char *matchp = star_match[0];
2355 if (*patternp == NULLCHAR) {
2356 *index = leftover_start = bufp - buf;
2360 if (*bufp == NULLCHAR) return FALSE;
2361 if (*patternp == '*') {
2362 if (*bufp == *(patternp + 1)) {
2364 matchp = star_match[++star_count];
2368 } else if (*bufp == '\n' || *bufp == '\r') {
2370 if (*patternp == NULLCHAR)
2375 *matchp++ = *bufp++;
2379 if (*patternp != *bufp) return FALSE;
2386 SendToPlayer (char *data, int length)
2388 int error, outCount;
2389 outCount = OutputToProcess(NoProc, data, length, &error);
2390 if (outCount < length) {
2391 DisplayFatalError(_("Error writing to display"), error, 1);
2396 PackHolding (char packed[], char *holding)
2406 switch (runlength) {
2417 sprintf(q, "%d", runlength);
2429 /* Telnet protocol requests from the front end */
2431 TelnetRequest (unsigned char ddww, unsigned char option)
2433 unsigned char msg[3];
2434 int outCount, outError;
2436 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2438 if (appData.debugMode) {
2439 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2455 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2464 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2467 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2472 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2474 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2481 if (!appData.icsActive) return;
2482 TelnetRequest(TN_DO, TN_ECHO);
2488 if (!appData.icsActive) return;
2489 TelnetRequest(TN_DONT, TN_ECHO);
2493 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2495 /* put the holdings sent to us by the server on the board holdings area */
2496 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2500 if(gameInfo.holdingsWidth < 2) return;
2501 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2502 return; // prevent overwriting by pre-board holdings
2504 if( (int)lowestPiece >= BlackPawn ) {
2507 holdingsStartRow = handSize-1;
2510 holdingsColumn = BOARD_WIDTH-1;
2511 countsColumn = BOARD_WIDTH-2;
2512 holdingsStartRow = 0;
2516 for(i=0; i<BOARD_RANKS-1; i++) { /* clear holdings */
2517 board[i][holdingsColumn] = EmptySquare;
2518 board[i][countsColumn] = (ChessSquare) 0;
2520 while( (p=*holdings++) != NULLCHAR ) {
2521 piece = CharToPiece( ToUpper(p) );
2522 if(piece == EmptySquare) continue;
2523 /*j = (int) piece - (int) WhitePawn;*/
2524 j = PieceToNumber(piece);
2525 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2526 if(j < 0) continue; /* should not happen */
2527 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2528 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2529 board[holdingsStartRow+j*direction][countsColumn]++;
2535 VariantSwitch (Board board, VariantClass newVariant)
2537 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2538 static Board oldBoard;
2540 startedFromPositionFile = FALSE;
2541 if(gameInfo.variant == newVariant) return;
2543 /* [HGM] This routine is called each time an assignment is made to
2544 * gameInfo.variant during a game, to make sure the board sizes
2545 * are set to match the new variant. If that means adding or deleting
2546 * holdings, we shift the playing board accordingly
2547 * This kludge is needed because in ICS observe mode, we get boards
2548 * of an ongoing game without knowing the variant, and learn about the
2549 * latter only later. This can be because of the move list we requested,
2550 * in which case the game history is refilled from the beginning anyway,
2551 * but also when receiving holdings of a crazyhouse game. In the latter
2552 * case we want to add those holdings to the already received position.
2556 if (appData.debugMode) {
2557 fprintf(debugFP, "Switch board from %s to %s\n",
2558 VariantName(gameInfo.variant), VariantName(newVariant));
2559 setbuf(debugFP, NULL);
2561 shuffleOpenings = 0; /* [HGM] shuffle */
2562 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2566 newWidth = 9; newHeight = 9;
2567 gameInfo.holdingsSize = 7;
2568 case VariantBughouse:
2569 case VariantCrazyhouse:
2570 newHoldingsWidth = 2; break;
2574 newHoldingsWidth = 2;
2575 gameInfo.holdingsSize = 8;
2578 case VariantCapablanca:
2579 case VariantCapaRandom:
2582 newHoldingsWidth = gameInfo.holdingsSize = 0;
2585 if(newWidth != gameInfo.boardWidth ||
2586 newHeight != gameInfo.boardHeight ||
2587 newHoldingsWidth != gameInfo.holdingsWidth ) {
2589 /* shift position to new playing area, if needed */
2590 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2591 for(i=0; i<BOARD_HEIGHT; i++)
2592 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2593 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2595 for(i=0; i<newHeight; i++) {
2596 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2597 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2599 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2600 for(i=0; i<BOARD_HEIGHT; i++)
2601 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2602 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2605 board[HOLDINGS_SET] = 0;
2606 gameInfo.boardWidth = newWidth;
2607 gameInfo.boardHeight = newHeight;
2608 gameInfo.holdingsWidth = newHoldingsWidth;
2609 gameInfo.variant = newVariant;
2610 InitDrawingSizes(-2, 0);
2611 } else gameInfo.variant = newVariant;
2612 CopyBoard(oldBoard, board); // remember correctly formatted board
2613 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2614 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2617 static int loggedOn = FALSE;
2619 /*-- Game start info cache: --*/
2621 char gs_kind[MSG_SIZ];
2622 static char player1Name[128] = "";
2623 static char player2Name[128] = "";
2624 static char cont_seq[] = "\n\\ ";
2625 static int player1Rating = -1;
2626 static int player2Rating = -1;
2627 /*----------------------------*/
2629 ColorClass curColor = ColorNormal;
2630 int suppressKibitz = 0;
2633 Boolean soughtPending = FALSE;
2634 Boolean seekGraphUp;
2635 #define MAX_SEEK_ADS 200
2637 char *seekAdList[MAX_SEEK_ADS];
2638 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2639 float tcList[MAX_SEEK_ADS];
2640 char colorList[MAX_SEEK_ADS];
2641 int nrOfSeekAds = 0;
2642 int minRating = 1010, maxRating = 2800;
2643 int hMargin = 10, vMargin = 20, h, w;
2644 extern int squareSize, lineGap;
2649 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2650 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2651 if(r < minRating+100 && r >=0 ) r = minRating+100;
2652 if(r > maxRating) r = maxRating;
2653 if(tc < 1.f) tc = 1.f;
2654 if(tc > 95.f) tc = 95.f;
2655 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2656 y = ((double)r - minRating)/(maxRating - minRating)
2657 * (h-vMargin-squareSize/8-1) + vMargin;
2658 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2659 if(strstr(seekAdList[i], " u ")) color = 1;
2660 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2661 !strstr(seekAdList[i], "bullet") &&
2662 !strstr(seekAdList[i], "blitz") &&
2663 !strstr(seekAdList[i], "standard") ) color = 2;
2664 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2665 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2669 PlotSingleSeekAd (int i)
2675 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2677 char buf[MSG_SIZ], *ext = "";
2678 VariantClass v = StringToVariant(type);
2679 if(strstr(type, "wild")) {
2680 ext = type + 4; // append wild number
2681 if(v == VariantFischeRandom) type = "chess960"; else
2682 if(v == VariantLoadable) type = "setup"; else
2683 type = VariantName(v);
2685 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2686 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2687 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2688 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2689 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2690 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2691 seekNrList[nrOfSeekAds] = nr;
2692 zList[nrOfSeekAds] = 0;
2693 seekAdList[nrOfSeekAds++] = StrSave(buf);
2694 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2699 EraseSeekDot (int i)
2701 int x = xList[i], y = yList[i], d=squareSize/4, k;
2702 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2703 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2704 // now replot every dot that overlapped
2705 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2706 int xx = xList[k], yy = yList[k];
2707 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2708 DrawSeekDot(xx, yy, colorList[k]);
2713 RemoveSeekAd (int nr)
2716 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2718 if(seekAdList[i]) free(seekAdList[i]);
2719 seekAdList[i] = seekAdList[--nrOfSeekAds];
2720 seekNrList[i] = seekNrList[nrOfSeekAds];
2721 ratingList[i] = ratingList[nrOfSeekAds];
2722 colorList[i] = colorList[nrOfSeekAds];
2723 tcList[i] = tcList[nrOfSeekAds];
2724 xList[i] = xList[nrOfSeekAds];
2725 yList[i] = yList[nrOfSeekAds];
2726 zList[i] = zList[nrOfSeekAds];
2727 seekAdList[nrOfSeekAds] = NULL;
2733 MatchSoughtLine (char *line)
2735 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2736 int nr, base, inc, u=0; char dummy;
2738 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2739 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2741 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2742 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2743 // match: compact and save the line
2744 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2754 if(!seekGraphUp) return FALSE;
2755 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2756 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap + 2*border;
2758 DrawSeekBackground(0, 0, w, h);
2759 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2760 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2761 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2762 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2764 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2767 snprintf(buf, MSG_SIZ, "%d", i);
2768 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2771 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2772 for(i=1; i<100; i+=(i<10?1:5)) {
2773 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2774 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2775 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2777 snprintf(buf, MSG_SIZ, "%d", i);
2778 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2781 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2786 SeekGraphClick (ClickType click, int x, int y, int moving)
2788 static int lastDown = 0, displayed = 0, lastSecond;
2789 if(y < 0) return FALSE;
2790 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2791 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2792 if(!seekGraphUp) return FALSE;
2793 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2794 DrawPosition(TRUE, NULL);
2797 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2798 if(click == Release || moving) return FALSE;
2800 soughtPending = TRUE;
2801 SendToICS(ics_prefix);
2802 SendToICS("sought\n"); // should this be "sought all"?
2803 } else { // issue challenge based on clicked ad
2804 int dist = 10000; int i, closest = 0, second = 0;
2805 for(i=0; i<nrOfSeekAds; i++) {
2806 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2807 if(d < dist) { dist = d; closest = i; }
2808 second += (d - zList[i] < 120); // count in-range ads
2809 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2813 second = (second > 1);
2814 if(displayed != closest || second != lastSecond) {
2815 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2816 lastSecond = second; displayed = closest;
2818 if(click == Press) {
2819 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2822 } // on press 'hit', only show info
2823 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2824 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2825 SendToICS(ics_prefix);
2827 return TRUE; // let incoming board of started game pop down the graph
2828 } else if(click == Release) { // release 'miss' is ignored
2829 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2830 if(moving == 2) { // right up-click
2831 nrOfSeekAds = 0; // refresh graph
2832 soughtPending = TRUE;
2833 SendToICS(ics_prefix);
2834 SendToICS("sought\n"); // should this be "sought all"?
2837 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2838 // press miss or release hit 'pop down' seek graph
2839 seekGraphUp = FALSE;
2840 DrawPosition(TRUE, NULL);
2846 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2848 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2849 #define STARTED_NONE 0
2850 #define STARTED_MOVES 1
2851 #define STARTED_BOARD 2
2852 #define STARTED_OBSERVE 3
2853 #define STARTED_HOLDINGS 4
2854 #define STARTED_CHATTER 5
2855 #define STARTED_COMMENT 6
2856 #define STARTED_MOVES_NOHIDE 7
2858 static int started = STARTED_NONE;
2859 static char parse[20000];
2860 static int parse_pos = 0;
2861 static char buf[BUF_SIZE + 1];
2862 static int firstTime = TRUE, intfSet = FALSE;
2863 static ColorClass prevColor = ColorNormal;
2864 static int savingComment = FALSE;
2865 static int cmatch = 0; // continuation sequence match
2872 int backup; /* [DM] For zippy color lines */
2874 char talker[MSG_SIZ]; // [HGM] chat
2875 int channel, collective=0;
2877 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2879 if (appData.debugMode) {
2881 fprintf(debugFP, "<ICS: ");
2882 show_bytes(debugFP, data, count);
2883 fprintf(debugFP, "\n");
2887 if (appData.debugMode) { int f = forwardMostMove;
2888 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2889 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2890 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2893 /* If last read ended with a partial line that we couldn't parse,
2894 prepend it to the new read and try again. */
2895 if (leftover_len > 0) {
2896 for (i=0; i<leftover_len; i++)
2897 buf[i] = buf[leftover_start + i];
2900 /* copy new characters into the buffer */
2901 bp = buf + leftover_len;
2902 buf_len=leftover_len;
2903 for (i=0; i<count; i++)
2906 if (data[i] == '\r')
2909 // join lines split by ICS?
2910 if (!appData.noJoin)
2913 Joining just consists of finding matches against the
2914 continuation sequence, and discarding that sequence
2915 if found instead of copying it. So, until a match
2916 fails, there's nothing to do since it might be the
2917 complete sequence, and thus, something we don't want
2920 if (data[i] == cont_seq[cmatch])
2923 if (cmatch == strlen(cont_seq))
2925 cmatch = 0; // complete match. just reset the counter
2928 it's possible for the ICS to not include the space
2929 at the end of the last word, making our [correct]
2930 join operation fuse two separate words. the server
2931 does this when the space occurs at the width setting.
2933 if (!buf_len || buf[buf_len-1] != ' ')
2944 match failed, so we have to copy what matched before
2945 falling through and copying this character. In reality,
2946 this will only ever be just the newline character, but
2947 it doesn't hurt to be precise.
2949 strncpy(bp, cont_seq, cmatch);
2961 buf[buf_len] = NULLCHAR;
2962 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2967 while (i < buf_len) {
2968 /* Deal with part of the TELNET option negotiation
2969 protocol. We refuse to do anything beyond the
2970 defaults, except that we allow the WILL ECHO option,
2971 which ICS uses to turn off password echoing when we are
2972 directly connected to it. We reject this option
2973 if localLineEditing mode is on (always on in xboard)
2974 and we are talking to port 23, which might be a real
2975 telnet server that will try to keep WILL ECHO on permanently.
2977 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2978 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2979 unsigned char option;
2981 switch ((unsigned char) buf[++i]) {
2983 if (appData.debugMode)
2984 fprintf(debugFP, "\n<WILL ");
2985 switch (option = (unsigned char) buf[++i]) {
2987 if (appData.debugMode)
2988 fprintf(debugFP, "ECHO ");
2989 /* Reply only if this is a change, according
2990 to the protocol rules. */
2991 if (remoteEchoOption) break;
2992 if (appData.localLineEditing &&
2993 atoi(appData.icsPort) == TN_PORT) {
2994 TelnetRequest(TN_DONT, TN_ECHO);
2997 TelnetRequest(TN_DO, TN_ECHO);
2998 remoteEchoOption = TRUE;
3002 if (appData.debugMode)
3003 fprintf(debugFP, "%d ", option);
3004 /* Whatever this is, we don't want it. */
3005 TelnetRequest(TN_DONT, option);
3010 if (appData.debugMode)
3011 fprintf(debugFP, "\n<WONT ");
3012 switch (option = (unsigned char) buf[++i]) {
3014 if (appData.debugMode)
3015 fprintf(debugFP, "ECHO ");
3016 /* Reply only if this is a change, according
3017 to the protocol rules. */
3018 if (!remoteEchoOption) break;
3020 TelnetRequest(TN_DONT, TN_ECHO);
3021 remoteEchoOption = FALSE;
3024 if (appData.debugMode)
3025 fprintf(debugFP, "%d ", (unsigned char) option);
3026 /* Whatever this is, it must already be turned
3027 off, because we never agree to turn on
3028 anything non-default, so according to the
3029 protocol rules, we don't reply. */
3034 if (appData.debugMode)
3035 fprintf(debugFP, "\n<DO ");
3036 switch (option = (unsigned char) buf[++i]) {
3038 /* Whatever this is, we refuse to do it. */
3039 if (appData.debugMode)
3040 fprintf(debugFP, "%d ", option);
3041 TelnetRequest(TN_WONT, option);
3046 if (appData.debugMode)
3047 fprintf(debugFP, "\n<DONT ");
3048 switch (option = (unsigned char) buf[++i]) {
3050 if (appData.debugMode)
3051 fprintf(debugFP, "%d ", option);
3052 /* Whatever this is, we are already not doing
3053 it, because we never agree to do anything
3054 non-default, so according to the protocol
3055 rules, we don't reply. */
3060 if (appData.debugMode)
3061 fprintf(debugFP, "\n<IAC ");
3062 /* Doubled IAC; pass it through */
3066 if (appData.debugMode)
3067 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3068 /* Drop all other telnet commands on the floor */
3071 if (oldi > next_out)
3072 SendToPlayer(&buf[next_out], oldi - next_out);
3078 /* OK, this at least will *usually* work */
3079 if (!loggedOn && looking_at(buf, &i, "ics%")) {
3083 if (loggedOn && !intfSet) {
3084 if (ics_type == ICS_ICC) {
3085 snprintf(str, MSG_SIZ,
3086 "/set-quietly interface %s\n/set-quietly style 12\n",
3088 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3089 strcat(str, "/set-2 51 1\n/set seek 1\n");
3090 } else if (ics_type == ICS_CHESSNET) {
3091 snprintf(str, MSG_SIZ, "/style 12\n");
3093 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3094 strcat(str, programVersion);
3095 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3096 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3097 strcat(str, "$iset seekremove 1\n$set seek 1\n");
3099 strcat(str, "$iset nohighlight 1\n");
3101 strcat(str, "$iset lock 1\n$style 12\n");
3104 NotifyFrontendLogin();
3108 if (started == STARTED_COMMENT) {
3109 /* Accumulate characters in comment */
3110 parse[parse_pos++] = buf[i];
3111 if (buf[i] == '\n') {
3112 parse[parse_pos] = NULLCHAR;
3113 if(chattingPartner>=0) {
3115 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3116 OutputChatMessage(chattingPartner, mess);
3117 if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3119 talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3120 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3121 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3122 OutputChatMessage(p, mess);
3126 chattingPartner = -1;
3127 if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3130 if(!suppressKibitz) // [HGM] kibitz
3131 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3132 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3133 int nrDigit = 0, nrAlph = 0, j;
3134 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3135 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3136 parse[parse_pos] = NULLCHAR;
3137 // try to be smart: if it does not look like search info, it should go to
3138 // ICS interaction window after all, not to engine-output window.
3139 for(j=0; j<parse_pos; j++) { // count letters and digits
3140 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3141 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3142 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3144 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3145 int depth=0; float score;
3146 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3147 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3148 pvInfoList[forwardMostMove-1].depth = depth;
3149 pvInfoList[forwardMostMove-1].score = 100*score;
3151 OutputKibitz(suppressKibitz, parse);
3154 if(gameMode == IcsObserving) // restore original ICS messages
3155 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3156 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3158 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3159 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3160 SendToPlayer(tmp, strlen(tmp));
3162 next_out = i+1; // [HGM] suppress printing in ICS window
3164 started = STARTED_NONE;
3166 /* Don't match patterns against characters in comment */
3171 if (started == STARTED_CHATTER) {
3172 if (buf[i] != '\n') {
3173 /* Don't match patterns against characters in chatter */
3177 started = STARTED_NONE;
3178 if(suppressKibitz) next_out = i+1;
3181 /* Kludge to deal with rcmd protocol */
3182 if (firstTime && looking_at(buf, &i, "\001*")) {
3183 DisplayFatalError(&buf[1], 0, 1);
3189 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3192 if (appData.debugMode)
3193 fprintf(debugFP, "ics_type %d\n", ics_type);
3196 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3197 ics_type = ICS_FICS;
3199 if (appData.debugMode)
3200 fprintf(debugFP, "ics_type %d\n", ics_type);
3203 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3204 ics_type = ICS_CHESSNET;
3206 if (appData.debugMode)
3207 fprintf(debugFP, "ics_type %d\n", ics_type);
3212 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3213 looking_at(buf, &i, "Logging you in as \"*\"") ||
3214 looking_at(buf, &i, "will be \"*\""))) {
3215 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3219 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3221 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3222 DisplayIcsInteractionTitle(buf);
3223 have_set_title = TRUE;
3226 /* skip finger notes */
3227 if (started == STARTED_NONE &&
3228 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3229 (buf[i] == '1' && buf[i+1] == '0')) &&
3230 buf[i+2] == ':' && buf[i+3] == ' ') {
3231 started = STARTED_CHATTER;
3237 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3238 if(appData.seekGraph) {
3239 if(soughtPending && MatchSoughtLine(buf+i)) {
3240 i = strstr(buf+i, "rated") - buf;
3241 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3242 next_out = leftover_start = i;
3243 started = STARTED_CHATTER;
3244 suppressKibitz = TRUE;
3247 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3248 && looking_at(buf, &i, "* ads displayed")) {
3249 soughtPending = FALSE;
3254 if(appData.autoRefresh) {
3255 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3256 int s = (ics_type == ICS_ICC); // ICC format differs
3258 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3259 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3260 looking_at(buf, &i, "*% "); // eat prompt
3261 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3262 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3263 next_out = i; // suppress
3266 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3267 char *p = star_match[0];
3269 if(seekGraphUp) RemoveSeekAd(atoi(p));
3270 while(*p && *p++ != ' '); // next
3272 looking_at(buf, &i, "*% "); // eat prompt
3273 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3280 /* skip formula vars */
3281 if (started == STARTED_NONE &&
3282 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3283 started = STARTED_CHATTER;
3288 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3289 if (appData.autoKibitz && started == STARTED_NONE &&
3290 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3291 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3292 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3293 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3294 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3295 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3296 suppressKibitz = TRUE;
3297 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3299 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3300 && (gameMode == IcsPlayingWhite)) ||
3301 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3302 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3303 started = STARTED_CHATTER; // own kibitz we simply discard
3305 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3306 parse_pos = 0; parse[0] = NULLCHAR;
3307 savingComment = TRUE;
3308 suppressKibitz = gameMode != IcsObserving ? 2 :
3309 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3313 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3314 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3315 && atoi(star_match[0])) {
3316 // suppress the acknowledgements of our own autoKibitz
3318 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3319 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3320 SendToPlayer(star_match[0], strlen(star_match[0]));
3321 if(looking_at(buf, &i, "*% ")) // eat prompt
3322 suppressKibitz = FALSE;
3326 } // [HGM] kibitz: end of patch
3328 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3330 // [HGM] chat: intercept tells by users for which we have an open chat window
3332 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3333 looking_at(buf, &i, "* whispers:") ||
3334 looking_at(buf, &i, "* kibitzes:") ||
3335 looking_at(buf, &i, "* shouts:") ||
3336 looking_at(buf, &i, "* c-shouts:") ||
3337 looking_at(buf, &i, "--> * ") ||
3338 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3339 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3340 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3341 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3343 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3344 chattingPartner = -1; collective = 0;
3346 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3347 for(p=0; p<MAX_CHAT; p++) {
3349 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3350 talker[0] = '['; strcat(talker, "] ");
3351 Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3352 chattingPartner = p; break;
3355 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3356 for(p=0; p<MAX_CHAT; p++) {
3358 if(!strcmp("kibitzes", chatPartner[p])) {
3359 talker[0] = '['; strcat(talker, "] ");
3360 chattingPartner = p; break;
3363 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3364 for(p=0; p<MAX_CHAT; p++) {
3366 if(!strcmp("whispers", chatPartner[p])) {
3367 talker[0] = '['; strcat(talker, "] ");
3368 chattingPartner = p; break;
3371 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3372 if(buf[i-8] == '-' && buf[i-3] == 't')
3373 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3375 if(!strcmp("c-shouts", chatPartner[p])) {
3376 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3377 chattingPartner = p; break;
3380 if(chattingPartner < 0)
3381 for(p=0; p<MAX_CHAT; p++) {
3383 if(!strcmp("shouts", chatPartner[p])) {
3384 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3385 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3386 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3387 chattingPartner = p; break;
3391 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3392 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3394 Colorize(ColorTell, FALSE);
3395 if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3397 chattingPartner = p; break;
3399 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3400 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3401 started = STARTED_COMMENT;
3402 parse_pos = 0; parse[0] = NULLCHAR;
3403 savingComment = 3 + chattingPartner; // counts as TRUE
3404 if(collective == 3) i = oldi; else {
3405 suppressKibitz = TRUE;
3406 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3407 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3411 } // [HGM] chat: end of patch
3414 if (appData.zippyTalk || appData.zippyPlay) {
3415 /* [DM] Backup address for color zippy lines */
3417 if (loggedOn == TRUE)
3418 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3419 (appData.zippyPlay && ZippyMatch(buf, &backup)))
3422 } // [DM] 'else { ' deleted
3424 /* Regular tells and says */
3425 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3426 looking_at(buf, &i, "* (your partner) tells you: ") ||
3427 looking_at(buf, &i, "* says: ") ||
3428 /* Don't color "message" or "messages" output */
3429 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3430 looking_at(buf, &i, "*. * at *:*: ") ||
3431 looking_at(buf, &i, "--* (*:*): ") ||
3432 /* Message notifications (same color as tells) */
3433 looking_at(buf, &i, "* has left a message ") ||
3434 looking_at(buf, &i, "* just sent you a message:\n") ||
3435 /* Whispers and kibitzes */
3436 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3437 looking_at(buf, &i, "* kibitzes: ") ||
3439 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3441 if (tkind == 1 && strchr(star_match[0], ':')) {
3442 /* Avoid "tells you:" spoofs in channels */
3445 if (star_match[0][0] == NULLCHAR ||
3446 strchr(star_match[0], ' ') ||
3447 (tkind == 3 && strchr(star_match[1], ' '))) {
3448 /* Reject bogus matches */
3451 if (appData.colorize) {
3452 if (oldi > next_out) {
3453 SendToPlayer(&buf[next_out], oldi - next_out);
3458 Colorize(ColorTell, FALSE);
3459 curColor = ColorTell;
3462 Colorize(ColorKibitz, FALSE);
3463 curColor = ColorKibitz;
3466 p = strrchr(star_match[1], '(');
3473 Colorize(ColorChannel1, FALSE);
3474 curColor = ColorChannel1;
3476 Colorize(ColorChannel, FALSE);
3477 curColor = ColorChannel;
3481 curColor = ColorNormal;
3485 if (started == STARTED_NONE && appData.autoComment &&
3486 (gameMode == IcsObserving ||
3487 gameMode == IcsPlayingWhite ||
3488 gameMode == IcsPlayingBlack)) {
3489 parse_pos = i - oldi;
3490 memcpy(parse, &buf[oldi], parse_pos);
3491 parse[parse_pos] = NULLCHAR;
3492 started = STARTED_COMMENT;
3493 savingComment = TRUE;
3494 } else if(collective != 3) {
3495 started = STARTED_CHATTER;
3496 savingComment = FALSE;
3503 if (looking_at(buf, &i, "* s-shouts: ") ||
3504 looking_at(buf, &i, "* c-shouts: ")) {
3505 if (appData.colorize) {
3506 if (oldi > next_out) {
3507 SendToPlayer(&buf[next_out], oldi - next_out);
3510 Colorize(ColorSShout, FALSE);
3511 curColor = ColorSShout;
3514 started = STARTED_CHATTER;
3518 if (looking_at(buf, &i, "--->")) {
3523 if (looking_at(buf, &i, "* shouts: ") ||
3524 looking_at(buf, &i, "--> ")) {
3525 if (appData.colorize) {
3526 if (oldi > next_out) {
3527 SendToPlayer(&buf[next_out], oldi - next_out);
3530 Colorize(ColorShout, FALSE);
3531 curColor = ColorShout;
3534 started = STARTED_CHATTER;
3538 if (looking_at( buf, &i, "Challenge:")) {
3539 if (appData.colorize) {
3540 if (oldi > next_out) {
3541 SendToPlayer(&buf[next_out], oldi - next_out);
3544 Colorize(ColorChallenge, FALSE);
3545 curColor = ColorChallenge;
3551 if (looking_at(buf, &i, "* offers you") ||
3552 looking_at(buf, &i, "* offers to be") ||
3553 looking_at(buf, &i, "* would like to") ||
3554 looking_at(buf, &i, "* requests to") ||
3555 looking_at(buf, &i, "Your opponent offers") ||
3556 looking_at(buf, &i, "Your opponent requests")) {
3558 if (appData.colorize) {
3559 if (oldi > next_out) {
3560 SendToPlayer(&buf[next_out], oldi - next_out);
3563 Colorize(ColorRequest, FALSE);
3564 curColor = ColorRequest;
3569 if (looking_at(buf, &i, "* (*) seeking")) {
3570 if (appData.colorize) {
3571 if (oldi > next_out) {
3572 SendToPlayer(&buf[next_out], oldi - next_out);
3575 Colorize(ColorSeek, FALSE);
3576 curColor = ColorSeek;
3581 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3583 if (looking_at(buf, &i, "\\ ")) {
3584 if (prevColor != ColorNormal) {
3585 if (oldi > next_out) {
3586 SendToPlayer(&buf[next_out], oldi - next_out);
3589 Colorize(prevColor, TRUE);
3590 curColor = prevColor;
3592 if (savingComment) {
3593 parse_pos = i - oldi;
3594 memcpy(parse, &buf[oldi], parse_pos);
3595 parse[parse_pos] = NULLCHAR;
3596 started = STARTED_COMMENT;
3597 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3598 chattingPartner = savingComment - 3; // kludge to remember the box
3600 started = STARTED_CHATTER;
3605 if (looking_at(buf, &i, "Black Strength :") ||
3606 looking_at(buf, &i, "<<< style 10 board >>>") ||
3607 looking_at(buf, &i, "<10>") ||
3608 looking_at(buf, &i, "#@#")) {
3609 /* Wrong board style */
3611 SendToICS(ics_prefix);
3612 SendToICS("set style 12\n");
3613 SendToICS(ics_prefix);
3614 SendToICS("refresh\n");
3618 if (looking_at(buf, &i, "login:")) {
3619 if (!have_sent_ICS_logon) {
3621 have_sent_ICS_logon = 1;
3622 else // no init script was found
3623 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3624 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3625 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3630 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3631 (looking_at(buf, &i, "\n<12> ") ||
3632 looking_at(buf, &i, "<12> "))) {
3634 if (oldi > next_out) {
3635 SendToPlayer(&buf[next_out], oldi - next_out);
3638 started = STARTED_BOARD;
3643 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3644 looking_at(buf, &i, "<b1> ")) {
3645 if (oldi > next_out) {
3646 SendToPlayer(&buf[next_out], oldi - next_out);
3649 started = STARTED_HOLDINGS;
3654 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3656 /* Header for a move list -- first line */
3658 switch (ics_getting_history) {
3662 case BeginningOfGame:
3663 /* User typed "moves" or "oldmoves" while we
3664 were idle. Pretend we asked for these
3665 moves and soak them up so user can step
3666 through them and/or save them.
3669 gameMode = IcsObserving;
3672 ics_getting_history = H_GOT_UNREQ_HEADER;
3674 case EditGame: /*?*/
3675 case EditPosition: /*?*/
3676 /* Should above feature work in these modes too? */
3677 /* For now it doesn't */
3678 ics_getting_history = H_GOT_UNWANTED_HEADER;
3681 ics_getting_history = H_GOT_UNWANTED_HEADER;
3686 /* Is this the right one? */
3687 if (gameInfo.white && gameInfo.black &&
3688 strcmp(gameInfo.white, star_match[0]) == 0 &&
3689 strcmp(gameInfo.black, star_match[2]) == 0) {
3691 ics_getting_history = H_GOT_REQ_HEADER;
3694 case H_GOT_REQ_HEADER:
3695 case H_GOT_UNREQ_HEADER:
3696 case H_GOT_UNWANTED_HEADER:
3697 case H_GETTING_MOVES:
3698 /* Should not happen */
3699 DisplayError(_("Error gathering move list: two headers"), 0);
3700 ics_getting_history = H_FALSE;
3704 /* Save player ratings into gameInfo if needed */
3705 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3706 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3707 (gameInfo.whiteRating == -1 ||
3708 gameInfo.blackRating == -1)) {
3710 gameInfo.whiteRating = string_to_rating(star_match[1]);
3711 gameInfo.blackRating = string_to_rating(star_match[3]);
3712 if (appData.debugMode)
3713 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3714 gameInfo.whiteRating, gameInfo.blackRating);
3719 if (looking_at(buf, &i,
3720 "* * match, initial time: * minute*, increment: * second")) {
3721 /* Header for a move list -- second line */
3722 /* Initial board will follow if this is a wild game */
3723 if (gameInfo.event != NULL) free(gameInfo.event);
3724 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3725 gameInfo.event = StrSave(str);
3726 /* [HGM] we switched variant. Translate boards if needed. */
3727 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3731 if (looking_at(buf, &i, "Move ")) {
3732 /* Beginning of a move list */
3733 switch (ics_getting_history) {
3735 /* Normally should not happen */
3736 /* Maybe user hit reset while we were parsing */
3739 /* Happens if we are ignoring a move list that is not
3740 * the one we just requested. Common if the user
3741 * tries to observe two games without turning off
3744 case H_GETTING_MOVES:
3745 /* Should not happen */
3746 DisplayError(_("Error gathering move list: nested"), 0);
3747 ics_getting_history = H_FALSE;
3749 case H_GOT_REQ_HEADER:
3750 ics_getting_history = H_GETTING_MOVES;
3751 started = STARTED_MOVES;
3753 if (oldi > next_out) {
3754 SendToPlayer(&buf[next_out], oldi - next_out);
3757 case H_GOT_UNREQ_HEADER:
3758 ics_getting_history = H_GETTING_MOVES;
3759 started = STARTED_MOVES_NOHIDE;
3762 case H_GOT_UNWANTED_HEADER:
3763 ics_getting_history = H_FALSE;
3769 if (looking_at(buf, &i, "% ") ||
3770 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3771 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3772 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3773 soughtPending = FALSE;
3777 if(suppressKibitz) next_out = i;
3778 savingComment = FALSE;
3782 case STARTED_MOVES_NOHIDE:
3783 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3784 parse[parse_pos + i - oldi] = NULLCHAR;
3785 ParseGameHistory(parse);
3787 if (appData.zippyPlay && first.initDone) {
3788 FeedMovesToProgram(&first, forwardMostMove);
3789 if (gameMode == IcsPlayingWhite) {
3790 if (WhiteOnMove(forwardMostMove)) {
3791 if (first.sendTime) {
3792 if (first.useColors) {
3793 SendToProgram("black\n", &first);
3795 SendTimeRemaining(&first, TRUE);
3797 if (first.useColors) {
3798 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3800 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3801 first.maybeThinking = TRUE;
3803 if (first.usePlayother) {
3804 if (first.sendTime) {
3805 SendTimeRemaining(&first, TRUE);
3807 SendToProgram("playother\n", &first);
3813 } else if (gameMode == IcsPlayingBlack) {
3814 if (!WhiteOnMove(forwardMostMove)) {
3815 if (first.sendTime) {
3816 if (first.useColors) {
3817 SendToProgram("white\n", &first);
3819 SendTimeRemaining(&first, FALSE);
3821 if (first.useColors) {
3822 SendToProgram("black\n", &first);
3824 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3825 first.maybeThinking = TRUE;
3827 if (first.usePlayother) {
3828 if (first.sendTime) {
3829 SendTimeRemaining(&first, FALSE);
3831 SendToProgram("playother\n", &first);
3840 if (gameMode == IcsObserving && ics_gamenum == -1) {
3841 /* Moves came from oldmoves or moves command
3842 while we weren't doing anything else.
3844 currentMove = forwardMostMove;
3845 ClearHighlights();/*!!could figure this out*/
3846 flipView = appData.flipView;
3847 DrawPosition(TRUE, boards[currentMove]);
3848 DisplayBothClocks();
3849 snprintf(str, MSG_SIZ, "%s %s %s",
3850 gameInfo.white, _("vs."), gameInfo.black);
3854 /* Moves were history of an active game */
3855 if (gameInfo.resultDetails != NULL) {
3856 free(gameInfo.resultDetails);
3857 gameInfo.resultDetails = NULL;
3860 HistorySet(parseList, backwardMostMove,
3861 forwardMostMove, currentMove-1);
3862 DisplayMove(currentMove - 1);
3863 if (started == STARTED_MOVES) next_out = i;
3864 started = STARTED_NONE;
3865 ics_getting_history = H_FALSE;
3868 case STARTED_OBSERVE:
3869 started = STARTED_NONE;
3870 SendToICS(ics_prefix);
3871 SendToICS("refresh\n");
3877 if(bookHit) { // [HGM] book: simulate book reply
3878 static char bookMove[MSG_SIZ]; // a bit generous?
3880 programStats.nodes = programStats.depth = programStats.time =
3881 programStats.score = programStats.got_only_move = 0;
3882 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3884 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3885 strcat(bookMove, bookHit);
3886 HandleMachineMove(bookMove, &first);
3891 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3892 started == STARTED_HOLDINGS ||
3893 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3894 /* Accumulate characters in move list or board */
3895 parse[parse_pos++] = buf[i];
3898 /* Start of game messages. Mostly we detect start of game
3899 when the first board image arrives. On some versions
3900 of the ICS, though, we need to do a "refresh" after starting
3901 to observe in order to get the current board right away. */
3902 if (looking_at(buf, &i, "Adding game * to observation list")) {
3903 started = STARTED_OBSERVE;
3907 /* Handle auto-observe */
3908 if (appData.autoObserve &&
3909 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3910 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3912 /* Choose the player that was highlighted, if any. */
3913 if (star_match[0][0] == '\033' ||
3914 star_match[1][0] != '\033') {
3915 player = star_match[0];
3917 player = star_match[2];
3919 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3920 ics_prefix, StripHighlightAndTitle(player));
3923 /* Save ratings from notify string */
3924 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3925 player1Rating = string_to_rating(star_match[1]);
3926 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3927 player2Rating = string_to_rating(star_match[3]);
3929 if (appData.debugMode)
3931 "Ratings from 'Game notification:' %s %d, %s %d\n",
3932 player1Name, player1Rating,
3933 player2Name, player2Rating);
3938 /* Deal with automatic examine mode after a game,
3939 and with IcsObserving -> IcsExamining transition */
3940 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3941 looking_at(buf, &i, "has made you an examiner of game *")) {
3943 int gamenum = atoi(star_match[0]);
3944 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3945 gamenum == ics_gamenum) {
3946 /* We were already playing or observing this game;
3947 no need to refetch history */
3948 gameMode = IcsExamining;
3950 pauseExamForwardMostMove = forwardMostMove;
3951 } else if (currentMove < forwardMostMove) {
3952 ForwardInner(forwardMostMove);
3955 /* I don't think this case really can happen */
3956 SendToICS(ics_prefix);
3957 SendToICS("refresh\n");
3962 /* Error messages */
3963 // if (ics_user_moved) {
3964 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3965 if (looking_at(buf, &i, "Illegal move") ||
3966 looking_at(buf, &i, "Not a legal move") ||
3967 looking_at(buf, &i, "Your king is in check") ||
3968 looking_at(buf, &i, "It isn't your turn") ||
3969 looking_at(buf, &i, "It is not your move")) {
3971 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3972 currentMove = forwardMostMove-1;
3973 DisplayMove(currentMove - 1); /* before DMError */
3974 DrawPosition(FALSE, boards[currentMove]);
3975 SwitchClocks(forwardMostMove-1); // [HGM] race
3976 DisplayBothClocks();
3978 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3984 if (looking_at(buf, &i, "still have time") ||
3985 looking_at(buf, &i, "not out of time") ||
3986 looking_at(buf, &i, "either player is out of time") ||
3987 looking_at(buf, &i, "has timeseal; checking")) {
3988 /* We must have called his flag a little too soon */
3989 whiteFlag = blackFlag = FALSE;
3993 if (looking_at(buf, &i, "added * seconds to") ||
3994 looking_at(buf, &i, "seconds were added to")) {
3995 /* Update the clocks */
3996 SendToICS(ics_prefix);
3997 SendToICS("refresh\n");
4001 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
4002 ics_clock_paused = TRUE;
4007 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
4008 ics_clock_paused = FALSE;
4013 /* Grab player ratings from the Creating: message.
4014 Note we have to check for the special case when
4015 the ICS inserts things like [white] or [black]. */
4016 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
4017 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
4019 0 player 1 name (not necessarily white)
4021 2 empty, white, or black (IGNORED)
4022 3 player 2 name (not necessarily black)
4025 The names/ratings are sorted out when the game
4026 actually starts (below).
4028 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4029 player1Rating = string_to_rating(star_match[1]);
4030 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4031 player2Rating = string_to_rating(star_match[4]);
4033 if (appData.debugMode)
4035 "Ratings from 'Creating:' %s %d, %s %d\n",
4036 player1Name, player1Rating,
4037 player2Name, player2Rating);
4042 /* Improved generic start/end-of-game messages */
4043 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4044 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4045 /* If tkind == 0: */
4046 /* star_match[0] is the game number */
4047 /* [1] is the white player's name */
4048 /* [2] is the black player's name */
4049 /* For end-of-game: */
4050 /* [3] is the reason for the game end */
4051 /* [4] is a PGN end game-token, preceded by " " */
4052 /* For start-of-game: */
4053 /* [3] begins with "Creating" or "Continuing" */
4054 /* [4] is " *" or empty (don't care). */
4055 int gamenum = atoi(star_match[0]);
4056 char *whitename, *blackname, *why, *endtoken;
4057 ChessMove endtype = EndOfFile;
4060 whitename = star_match[1];
4061 blackname = star_match[2];
4062 why = star_match[3];
4063 endtoken = star_match[4];
4065 whitename = star_match[1];
4066 blackname = star_match[3];
4067 why = star_match[5];
4068 endtoken = star_match[6];
4071 /* Game start messages */
4072 if (strncmp(why, "Creating ", 9) == 0 ||
4073 strncmp(why, "Continuing ", 11) == 0) {
4074 gs_gamenum = gamenum;
4075 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4076 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4077 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4079 if (appData.zippyPlay) {
4080 ZippyGameStart(whitename, blackname);
4083 partnerBoardValid = FALSE; // [HGM] bughouse
4087 /* Game end messages */
4088 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4089 ics_gamenum != gamenum) {
4092 while (endtoken[0] == ' ') endtoken++;
4093 switch (endtoken[0]) {
4096 endtype = GameUnfinished;
4099 endtype = BlackWins;
4102 if (endtoken[1] == '/')
4103 endtype = GameIsDrawn;
4105 endtype = WhiteWins;
4108 GameEnds(endtype, why, GE_ICS);
4110 if (appData.zippyPlay && first.initDone) {
4111 ZippyGameEnd(endtype, why);
4112 if (first.pr == NoProc) {
4113 /* Start the next process early so that we'll
4114 be ready for the next challenge */
4115 StartChessProgram(&first);
4117 /* Send "new" early, in case this command takes
4118 a long time to finish, so that we'll be ready
4119 for the next challenge. */
4120 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4124 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4128 if (looking_at(buf, &i, "Removing game * from observation") ||
4129 looking_at(buf, &i, "no longer observing game *") ||
4130 looking_at(buf, &i, "Game * (*) has no examiners")) {
4131 if (gameMode == IcsObserving &&
4132 atoi(star_match[0]) == ics_gamenum)
4134 /* icsEngineAnalyze */
4135 if (appData.icsEngineAnalyze) {
4142 ics_user_moved = FALSE;
4147 if (looking_at(buf, &i, "no longer examining game *")) {
4148 if (gameMode == IcsExamining &&
4149 atoi(star_match[0]) == ics_gamenum)
4153 ics_user_moved = FALSE;
4158 /* Advance leftover_start past any newlines we find,
4159 so only partial lines can get reparsed */
4160 if (looking_at(buf, &i, "\n")) {
4161 prevColor = curColor;
4162 if (curColor != ColorNormal) {
4163 if (oldi > next_out) {
4164 SendToPlayer(&buf[next_out], oldi - next_out);
4167 Colorize(ColorNormal, FALSE);
4168 curColor = ColorNormal;
4170 if (started == STARTED_BOARD) {
4171 started = STARTED_NONE;
4172 parse[parse_pos] = NULLCHAR;
4173 ParseBoard12(parse);
4176 /* Send premove here */
4177 if (appData.premove) {
4179 if (currentMove == 0 &&
4180 gameMode == IcsPlayingWhite &&
4181 appData.premoveWhite) {
4182 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4183 if (appData.debugMode)
4184 fprintf(debugFP, "Sending premove:\n");
4186 } else if (currentMove == 1 &&
4187 gameMode == IcsPlayingBlack &&
4188 appData.premoveBlack) {
4189 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4190 if (appData.debugMode)
4191 fprintf(debugFP, "Sending premove:\n");
4193 } else if (gotPremove) {
4194 int oldFMM = forwardMostMove;
4196 ClearPremoveHighlights();
4197 if (appData.debugMode)
4198 fprintf(debugFP, "Sending premove:\n");
4199 UserMoveEvent(premoveFromX, premoveFromY,
4200 premoveToX, premoveToY,
4202 if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4203 if(moveList[oldFMM-1][1] != '@')
4204 SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4205 moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4207 SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4212 /* Usually suppress following prompt */
4213 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4214 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4215 if (looking_at(buf, &i, "*% ")) {
4216 savingComment = FALSE;
4221 } else if (started == STARTED_HOLDINGS) {
4223 char new_piece[MSG_SIZ];
4224 started = STARTED_NONE;
4225 parse[parse_pos] = NULLCHAR;
4226 if (appData.debugMode)
4227 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4228 parse, currentMove);
4229 if (sscanf(parse, " game %d", &gamenum) == 1) {
4230 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4231 new_piece[0] = NULLCHAR;
4232 sscanf(parse, "game %d white [%s black [%s <- %s",
4233 &gamenum, white_holding, black_holding,
4235 white_holding[strlen(white_holding)-1] = NULLCHAR;
4236 black_holding[strlen(black_holding)-1] = NULLCHAR;
4237 if (gameInfo.variant == VariantNormal) {
4238 /* [HGM] We seem to switch variant during a game!
4239 * Presumably no holdings were displayed, so we have
4240 * to move the position two files to the right to
4241 * create room for them!
4243 VariantClass newVariant;
4244 switch(gameInfo.boardWidth) { // base guess on board width
4245 case 9: newVariant = VariantShogi; break;
4246 case 10: newVariant = VariantGreat; break;
4247 default: newVariant = VariantCrazyhouse;
4248 if(strchr(white_holding, 'E') || strchr(black_holding, 'E') ||
4249 strchr(white_holding, 'H') || strchr(black_holding, 'H') )
4250 newVariant = VariantSChess;
4252 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4253 /* Get a move list just to see the header, which
4254 will tell us whether this is really bug or zh */
4255 if (ics_getting_history == H_FALSE) {
4256 ics_getting_history = H_REQUESTED;
4257 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4261 /* [HGM] copy holdings to board holdings area */
4262 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4263 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4264 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4266 if (appData.zippyPlay && first.initDone) {
4267 ZippyHoldings(white_holding, black_holding,
4271 if (tinyLayout || smallLayout) {
4272 char wh[16], bh[16];
4273 PackHolding(wh, white_holding);
4274 PackHolding(bh, black_holding);
4275 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4276 gameInfo.white, gameInfo.black);
4278 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4279 gameInfo.white, white_holding, _("vs."),
4280 gameInfo.black, black_holding);
4282 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4283 DrawPosition(FALSE, boards[currentMove]);
4285 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4286 sscanf(parse, "game %d white [%s black [%s <- %s",
4287 &gamenum, white_holding, black_holding,
4289 white_holding[strlen(white_holding)-1] = NULLCHAR;
4290 black_holding[strlen(black_holding)-1] = NULLCHAR;
4291 /* [HGM] copy holdings to partner-board holdings area */
4292 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4293 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4294 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4295 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4296 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4299 /* Suppress following prompt */
4300 if (looking_at(buf, &i, "*% ")) {
4301 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4302 savingComment = FALSE;
4310 i++; /* skip unparsed character and loop back */
4313 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4314 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4315 // SendToPlayer(&buf[next_out], i - next_out);
4316 started != STARTED_HOLDINGS && leftover_start > next_out) {
4317 SendToPlayer(&buf[next_out], leftover_start - next_out);
4321 leftover_len = buf_len - leftover_start;
4322 /* if buffer ends with something we couldn't parse,
4323 reparse it after appending the next read */
4325 } else if (count == 0) {
4326 RemoveInputSource(isr);
4327 DisplayFatalError(_("Connection closed by ICS"), 0, 6666);
4329 DisplayFatalError(_("Error reading from ICS"), error, 1);
4334 /* Board style 12 looks like this:
4336 <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4338 * The "<12> " is stripped before it gets to this routine. The two
4339 * trailing 0's (flip state and clock ticking) are later addition, and
4340 * some chess servers may not have them, or may have only the first.
4341 * Additional trailing fields may be added in the future.
4344 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4346 #define RELATION_OBSERVING_PLAYED 0
4347 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4348 #define RELATION_PLAYING_MYMOVE 1
4349 #define RELATION_PLAYING_NOTMYMOVE -1
4350 #define RELATION_EXAMINING 2
4351 #define RELATION_ISOLATED_BOARD -3
4352 #define RELATION_STARTING_POSITION -4 /* FICS only */
4355 ParseBoard12 (char *string)
4359 char *bookHit = NULL; // [HGM] book
4361 GameMode newGameMode;
4362 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4363 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4364 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4365 char to_play, board_chars[200];
4366 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4367 char black[32], white[32];
4369 int prevMove = currentMove;
4372 int fromX, fromY, toX, toY;
4374 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4375 Boolean weird = FALSE, reqFlag = FALSE;
4377 fromX = fromY = toX = toY = -1;
4381 if (appData.debugMode)
4382 fprintf(debugFP, "Parsing board: %s\n", string);
4384 move_str[0] = NULLCHAR;
4385 elapsed_time[0] = NULLCHAR;
4386 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4388 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4389 if(string[i] == ' ') { ranks++; files = 0; }
4391 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4394 for(j = 0; j <i; j++) board_chars[j] = string[j];
4395 board_chars[i] = '\0';
4398 n = sscanf(string, PATTERN, &to_play, &double_push,
4399 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4400 &gamenum, white, black, &relation, &basetime, &increment,
4401 &white_stren, &black_stren, &white_time, &black_time,
4402 &moveNum, str, elapsed_time, move_str, &ics_flip,
4406 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4407 DisplayError(str, 0);
4411 /* Convert the move number to internal form */
4412 moveNum = (moveNum - 1) * 2;
4413 if (to_play == 'B') moveNum++;
4414 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4415 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4421 case RELATION_OBSERVING_PLAYED:
4422 case RELATION_OBSERVING_STATIC:
4423 if (gamenum == -1) {
4424 /* Old ICC buglet */
4425 relation = RELATION_OBSERVING_STATIC;
4427 newGameMode = IcsObserving;
4429 case RELATION_PLAYING_MYMOVE:
4430 case RELATION_PLAYING_NOTMYMOVE:
4432 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4433 IcsPlayingWhite : IcsPlayingBlack;
4434 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4436 case RELATION_EXAMINING:
4437 newGameMode = IcsExamining;
4439 case RELATION_ISOLATED_BOARD:
4441 /* Just display this board. If user was doing something else,
4442 we will forget about it until the next board comes. */
4443 newGameMode = IcsIdle;
4445 case RELATION_STARTING_POSITION:
4446 newGameMode = gameMode;
4450 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4451 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4452 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4453 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4454 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4455 static int lastBgGame = -1;
4457 for (k = 0; k < ranks; k++) {
4458 for (j = 0; j < files; j++)
4459 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4460 if(gameInfo.holdingsWidth > 1) {
4461 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4462 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4465 CopyBoard(partnerBoard, board);
4466 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4467 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4468 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4469 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4470 if(toSqr = strchr(str, '-')) {
4471 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4472 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4473 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4474 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4475 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4476 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4478 DisplayWhiteClock(white_time*fac, to_play == 'W');
4479 DisplayBlackClock(black_time*fac, to_play != 'W');
4480 activePartner = to_play;
4481 if(gamenum != lastBgGame) {
4483 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4486 lastBgGame = gamenum;
4487 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4488 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4489 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4490 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4491 if(!twoBoards) DisplayMessage(partnerStatus, "");
4492 partnerBoardValid = TRUE;
4496 if(appData.dualBoard && appData.bgObserve) {
4497 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4498 SendToICS(ics_prefix), SendToICS("pobserve\n");
4499 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4501 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4506 /* Modify behavior for initial board display on move listing
4509 switch (ics_getting_history) {
4513 case H_GOT_REQ_HEADER:
4514 case H_GOT_UNREQ_HEADER:
4515 /* This is the initial position of the current game */
4516 gamenum = ics_gamenum;
4517 moveNum = 0; /* old ICS bug workaround */
4518 if (to_play == 'B') {
4519 startedFromSetupPosition = TRUE;
4520 blackPlaysFirst = TRUE;
4522 if (forwardMostMove == 0) forwardMostMove = 1;
4523 if (backwardMostMove == 0) backwardMostMove = 1;
4524 if (currentMove == 0) currentMove = 1;
4526 newGameMode = gameMode;
4527 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4529 case H_GOT_UNWANTED_HEADER:
4530 /* This is an initial board that we don't want */
4532 case H_GETTING_MOVES:
4533 /* Should not happen */
4534 DisplayError(_("Error gathering move list: extra board"), 0);
4535 ics_getting_history = H_FALSE;
4539 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4540 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4541 weird && (int)gameInfo.variant < (int)VariantShogi) {
4542 /* [HGM] We seem to have switched variant unexpectedly
4543 * Try to guess new variant from board size
4545 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4546 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4547 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4548 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4549 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4550 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4551 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4552 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4553 /* Get a move list just to see the header, which
4554 will tell us whether this is really bug or zh */
4555 if (ics_getting_history == H_FALSE) {
4556 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4557 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4562 /* Take action if this is the first board of a new game, or of a
4563 different game than is currently being displayed. */
4564 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4565 relation == RELATION_ISOLATED_BOARD) {
4567 /* Forget the old game and get the history (if any) of the new one */
4568 if (gameMode != BeginningOfGame) {
4572 if (appData.autoRaiseBoard) BoardToTop();
4574 if (gamenum == -1) {
4575 newGameMode = IcsIdle;
4576 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4577 appData.getMoveList && !reqFlag) {
4578 /* Need to get game history */
4579 ics_getting_history = H_REQUESTED;
4580 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4584 /* Initially flip the board to have black on the bottom if playing
4585 black or if the ICS flip flag is set, but let the user change
4586 it with the Flip View button. */
4587 flipView = appData.autoFlipView ?
4588 (newGameMode == IcsPlayingBlack) || ics_flip :
4591 /* Done with values from previous mode; copy in new ones */
4592 gameMode = newGameMode;
4594 ics_gamenum = gamenum;
4595 if (gamenum == gs_gamenum) {
4596 int klen = strlen(gs_kind);
4597 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4598 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4599 gameInfo.event = StrSave(str);
4601 gameInfo.event = StrSave("ICS game");
4603 gameInfo.site = StrSave(appData.icsHost);
4604 gameInfo.date = PGNDate();
4605 gameInfo.round = StrSave("-");
4606 gameInfo.white = StrSave(white);
4607 gameInfo.black = StrSave(black);
4608 timeControl = basetime * 60 * 1000;
4610 timeIncrement = increment * 1000;
4611 movesPerSession = 0;
4612 gameInfo.timeControl = TimeControlTagValue();
4613 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4614 if (appData.debugMode) {
4615 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4616 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4617 setbuf(debugFP, NULL);
4620 gameInfo.outOfBook = NULL;
4622 /* Do we have the ratings? */
4623 if (strcmp(player1Name, white) == 0 &&
4624 strcmp(player2Name, black) == 0) {
4625 if (appData.debugMode)
4626 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4627 player1Rating, player2Rating);
4628 gameInfo.whiteRating = player1Rating;
4629 gameInfo.blackRating = player2Rating;
4630 } else if (strcmp(player2Name, white) == 0 &&
4631 strcmp(player1Name, black) == 0) {
4632 if (appData.debugMode)
4633 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4634 player2Rating, player1Rating);
4635 gameInfo.whiteRating = player2Rating;
4636 gameInfo.blackRating = player1Rating;
4638 player1Name[0] = player2Name[0] = NULLCHAR;
4640 /* Silence shouts if requested */
4641 if (appData.quietPlay &&
4642 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4643 SendToICS(ics_prefix);
4644 SendToICS("set shout 0\n");
4648 /* Deal with midgame name changes */
4650 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4651 if (gameInfo.white) free(gameInfo.white);
4652 gameInfo.white = StrSave(white);
4654 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4655 if (gameInfo.black) free(gameInfo.black);
4656 gameInfo.black = StrSave(black);
4660 /* Throw away game result if anything actually changes in examine mode */
4661 if (gameMode == IcsExamining && !newGame) {
4662 gameInfo.result = GameUnfinished;
4663 if (gameInfo.resultDetails != NULL) {
4664 free(gameInfo.resultDetails);
4665 gameInfo.resultDetails = NULL;
4669 /* In pausing && IcsExamining mode, we ignore boards coming
4670 in if they are in a different variation than we are. */
4671 if (pauseExamInvalid) return;
4672 if (pausing && gameMode == IcsExamining) {
4673 if (moveNum <= pauseExamForwardMostMove) {
4674 pauseExamInvalid = TRUE;
4675 forwardMostMove = pauseExamForwardMostMove;
4680 if (appData.debugMode) {
4681 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4683 /* Parse the board */
4684 for (k = 0; k < ranks; k++) {
4685 for (j = 0; j < files; j++)
4686 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4687 if(gameInfo.holdingsWidth > 1) {
4688 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4689 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4692 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4693 board[5][BOARD_RGHT+1] = WhiteAngel;
4694 board[6][BOARD_RGHT+1] = WhiteMarshall;
4695 board[1][0] = BlackMarshall;
4696 board[2][0] = BlackAngel;
4697 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4699 CopyBoard(boards[moveNum], board);
4700 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4702 startedFromSetupPosition =
4703 !CompareBoards(board, initialPosition);
4704 if(startedFromSetupPosition)
4705 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4708 /* [HGM] Set castling rights. Take the outermost Rooks,
4709 to make it also work for FRC opening positions. Note that board12
4710 is really defective for later FRC positions, as it has no way to
4711 indicate which Rook can castle if they are on the same side of King.
4712 For the initial position we grant rights to the outermost Rooks,
4713 and remember thos rights, and we then copy them on positions
4714 later in an FRC game. This means WB might not recognize castlings with
4715 Rooks that have moved back to their original position as illegal,
4716 but in ICS mode that is not its job anyway.
4718 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4719 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4721 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4722 if(board[0][i] == WhiteRook) j = i;
4723 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4724 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4725 if(board[0][i] == WhiteRook) j = i;
4726 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4727 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4728 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4729 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4730 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4731 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4732 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4734 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4735 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4736 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4737 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4738 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4739 if(board[BOARD_HEIGHT-1][k] == bKing)
4740 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4741 if(gameInfo.variant == VariantTwoKings) {
4742 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4743 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4744 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4747 r = boards[moveNum][CASTLING][0] = initialRights[0];
4748 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4749 r = boards[moveNum][CASTLING][1] = initialRights[1];
4750 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4751 r = boards[moveNum][CASTLING][3] = initialRights[3];
4752 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4753 r = boards[moveNum][CASTLING][4] = initialRights[4];
4754 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4755 /* wildcastle kludge: always assume King has rights */
4756 r = boards[moveNum][CASTLING][2] = initialRights[2];
4757 r = boards[moveNum][CASTLING][5] = initialRights[5];
4759 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4760 boards[moveNum][EP_STATUS] = EP_NONE;
4761 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4762 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4763 if(double_push != -1) {
4764 int dir = WhiteOnMove(moveNum) ? 1 : -1, last = BOARD_HEIGHT-1;
4765 boards[moveNum][EP_FILE] = // also set new e.p. variables
4766 boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4767 boards[moveNum][EP_RANK] = (last + 3*dir)/2;
4768 boards[moveNum][LAST_TO] = 128*(last + dir) + boards[moveNum][EP_FILE];
4769 } else boards[moveNum][EP_FILE] = boards[moveNum][EP_RANK] = 100;
4772 if (ics_getting_history == H_GOT_REQ_HEADER ||
4773 ics_getting_history == H_GOT_UNREQ_HEADER) {
4774 /* This was an initial position from a move list, not
4775 the current position */
4779 /* Update currentMove and known move number limits */
4780 newMove = newGame || moveNum > forwardMostMove;
4783 forwardMostMove = backwardMostMove = currentMove = moveNum;
4784 if (gameMode == IcsExamining && moveNum == 0) {
4785 /* Workaround for ICS limitation: we are not told the wild
4786 type when starting to examine a game. But if we ask for
4787 the move list, the move list header will tell us */
4788 ics_getting_history = H_REQUESTED;
4789 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4792 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4793 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4795 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4796 /* [HGM] applied this also to an engine that is silently watching */
4797 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4798 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4799 gameInfo.variant == currentlyInitializedVariant) {
4800 takeback = forwardMostMove - moveNum;
4801 for (i = 0; i < takeback; i++) {
4802 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4803 SendToProgram("undo\n", &first);
4808 forwardMostMove = moveNum;
4809 if (!pausing || currentMove > forwardMostMove)
4810 currentMove = forwardMostMove;
4812 /* New part of history that is not contiguous with old part */
4813 if (pausing && gameMode == IcsExamining) {
4814 pauseExamInvalid = TRUE;
4815 forwardMostMove = pauseExamForwardMostMove;
4818 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4820 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4821 // [HGM] when we will receive the move list we now request, it will be
4822 // fed to the engine from the first move on. So if the engine is not
4823 // in the initial position now, bring it there.
4824 InitChessProgram(&first, 0);
4827 ics_getting_history = H_REQUESTED;
4828 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4831 forwardMostMove = backwardMostMove = currentMove = moveNum;
4834 /* Update the clocks */
4835 if (strchr(elapsed_time, '.')) {
4837 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4838 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4840 /* Time is in seconds */
4841 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4842 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4847 if (appData.zippyPlay && newGame &&
4848 gameMode != IcsObserving && gameMode != IcsIdle &&
4849 gameMode != IcsExamining)
4850 ZippyFirstBoard(moveNum, basetime, increment);
4853 /* Put the move on the move list, first converting
4854 to canonical algebraic form. */
4856 if (appData.debugMode) {
4857 int f = forwardMostMove;
4858 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4859 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4860 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4861 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4862 fprintf(debugFP, "moveNum = %d\n", moveNum);
4863 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4864 setbuf(debugFP, NULL);
4866 if (moveNum <= backwardMostMove) {
4867 /* We don't know what the board looked like before
4869 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4870 strcat(parseList[moveNum - 1], " ");
4871 strcat(parseList[moveNum - 1], elapsed_time);
4872 moveList[moveNum - 1][0] = NULLCHAR;
4873 } else if (strcmp(move_str, "none") == 0) {
4874 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4875 /* Again, we don't know what the board looked like;
4876 this is really the start of the game. */
4877 parseList[moveNum - 1][0] = NULLCHAR;
4878 moveList[moveNum - 1][0] = NULLCHAR;
4879 backwardMostMove = moveNum;
4880 startedFromSetupPosition = TRUE;
4881 fromX = fromY = toX = toY = -1;
4883 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4884 // So we parse the long-algebraic move string in stead of the SAN move
4885 int valid; char buf[MSG_SIZ], *prom;
4887 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4888 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4889 // str looks something like "Q/a1-a2"; kill the slash
4891 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4892 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4893 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4894 strcat(buf, prom); // long move lacks promo specification!
4895 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4896 if(appData.debugMode)
4897 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4898 safeStrCpy(move_str, buf, MSG_SIZ);
4900 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4901 &fromX, &fromY, &toX, &toY, &promoChar)
4902 || ParseOneMove(buf, moveNum - 1, &moveType,
4903 &fromX, &fromY, &toX, &toY, &promoChar);
4904 // end of long SAN patch
4906 (void) CoordsToAlgebraic(boards[moveNum - 1],
4907 PosFlags(moveNum - 1),
4908 fromY, fromX, toY, toX, promoChar,
4909 parseList[moveNum-1]);
4910 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4916 if(!IS_SHOGI(gameInfo.variant))
4917 strcat(parseList[moveNum - 1], "+");
4920 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4921 strcat(parseList[moveNum - 1], "#");
4924 strcat(parseList[moveNum - 1], " ");
4925 strcat(parseList[moveNum - 1], elapsed_time);
4926 /* currentMoveString is set as a side-effect of ParseOneMove */
4927 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4928 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4929 strcat(moveList[moveNum - 1], "\n");
4931 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4932 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4933 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4934 ChessSquare old, new = boards[moveNum][k][j];
4935 if(new == EmptySquare || fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4936 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4937 if(old == new) continue;
4938 if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4939 else if(new == WhiteWazir || new == BlackWazir) {
4940 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4941 boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4942 else boards[moveNum][k][j] = old; // preserve type of Gold
4943 } else if(old == WhitePawn || old == BlackPawn) // Pawn promotions (but not e.p.capture!)
4944 boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4947 /* Move from ICS was illegal!? Punt. */
4948 if (appData.debugMode) {
4949 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4950 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4952 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4953 strcat(parseList[moveNum - 1], " ");
4954 strcat(parseList[moveNum - 1], elapsed_time);
4955 moveList[moveNum - 1][0] = NULLCHAR;
4956 fromX = fromY = toX = toY = -1;
4959 if (appData.debugMode) {
4960 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4961 setbuf(debugFP, NULL);
4965 /* Send move to chess program (BEFORE animating it). */
4966 if (appData.zippyPlay && !newGame && newMove &&
4967 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4969 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4970 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4971 if (moveList[moveNum - 1][0] == NULLCHAR) {
4972 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4974 DisplayError(str, 0);
4976 if (first.sendTime) {
4977 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4979 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4980 if (firstMove && !bookHit) {
4982 if (first.useColors) {
4983 SendToProgram(gameMode == IcsPlayingWhite ?
4985 "black\ngo\n", &first);
4987 SendToProgram("go\n", &first);
4989 first.maybeThinking = TRUE;
4992 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4993 if (moveList[moveNum - 1][0] == NULLCHAR) {
4994 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4995 DisplayError(str, 0);
4997 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4998 SendMoveToProgram(moveNum - 1, &first);
5005 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
5006 /* If move comes from a remote source, animate it. If it
5007 isn't remote, it will have already been animated. */
5008 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
5009 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
5011 if (!pausing && appData.highlightLastMove) {
5012 SetHighlights(fromX, fromY, toX, toY);
5016 /* Start the clocks */
5017 whiteFlag = blackFlag = FALSE;
5018 appData.clockMode = !(basetime == 0 && increment == 0);
5020 ics_clock_paused = TRUE;
5022 } else if (ticking == 1) {
5023 ics_clock_paused = FALSE;
5025 if (gameMode == IcsIdle ||
5026 relation == RELATION_OBSERVING_STATIC ||
5027 relation == RELATION_EXAMINING ||
5029 DisplayBothClocks();
5033 /* Display opponents and material strengths */
5034 if (gameInfo.variant != VariantBughouse &&
5035 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
5036 if (tinyLayout || smallLayout) {
5037 if(gameInfo.variant == VariantNormal)
5038 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5039 gameInfo.white, white_stren, gameInfo.black, black_stren,
5040 basetime, increment);
5042 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5043 gameInfo.white, white_stren, gameInfo.black, black_stren,
5044 basetime, increment, (int) gameInfo.variant);
5046 if(gameInfo.variant == VariantNormal)
5047 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5048 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5049 basetime, increment);
5051 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5052 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5053 basetime, increment, VariantName(gameInfo.variant));
5056 if (appData.debugMode) {
5057 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5062 /* Display the board */
5063 if (!pausing && !appData.noGUI) {
5065 if (appData.premove)
5067 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5068 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5069 ClearPremoveHighlights();
5071 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5072 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5073 DrawPosition(j, boards[currentMove]);
5075 DisplayMove(moveNum - 1);
5076 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5077 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5078 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
5079 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5083 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5085 if(bookHit) { // [HGM] book: simulate book reply
5086 static char bookMove[MSG_SIZ]; // a bit generous?
5088 programStats.nodes = programStats.depth = programStats.time =
5089 programStats.score = programStats.got_only_move = 0;
5090 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5092 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5093 strcat(bookMove, bookHit);
5094 HandleMachineMove(bookMove, &first);
5103 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5104 ics_getting_history = H_REQUESTED;
5105 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5111 SendToBoth (char *msg)
5112 { // to make it easy to keep two engines in step in dual analysis
5113 SendToProgram(msg, &first);
5114 if(second.analyzing) SendToProgram(msg, &second);
5118 AnalysisPeriodicEvent (int force)
5120 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5121 && !force) || !appData.periodicUpdates)
5124 /* Send . command to Crafty to collect stats */
5127 /* Don't send another until we get a response (this makes
5128 us stop sending to old Crafty's which don't understand
5129 the "." command (sending illegal cmds resets node count & time,
5130 which looks bad)) */
5131 programStats.ok_to_send = 0;
5135 ics_update_width (int new_width)
5137 ics_printf("set width %d\n", new_width);
5141 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5145 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5146 if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5147 sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5148 SendToProgram(buf, cps);
5151 // null move in variant where engine does not understand it (for analysis purposes)
5152 SendBoard(cps, moveNum + 1); // send position after move in stead.
5155 if (cps->useUsermove) {
5156 SendToProgram("usermove ", cps);
5160 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5161 int len = space - parseList[moveNum];
5162 memcpy(buf, parseList[moveNum], len);
5164 buf[len] = NULLCHAR;
5166 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5168 SendToProgram(buf, cps);
5170 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5171 AlphaRank(moveList[moveNum], 4);
5172 SendToProgram(moveList[moveNum], cps);
5173 AlphaRank(moveList[moveNum], 4); // and back
5175 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5176 * the engine. It would be nice to have a better way to identify castle
5178 if(appData.fischerCastling && cps->useOOCastle) {
5179 int fromX = moveList[moveNum][0] - AAA;
5180 int fromY = moveList[moveNum][1] - ONE;
5181 int toX = moveList[moveNum][2] - AAA;
5182 int toY = moveList[moveNum][3] - ONE;
5183 if((boards[moveNum][fromY][fromX] == WhiteKing
5184 && boards[moveNum][toY][toX] == WhiteRook)
5185 || (boards[moveNum][fromY][fromX] == BlackKing
5186 && boards[moveNum][toY][toX] == BlackRook)) {
5187 if(toX > fromX) SendToProgram("O-O\n", cps);
5188 else SendToProgram("O-O-O\n", cps);
5190 else SendToProgram(moveList[moveNum], cps);
5192 if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5193 char *m = moveList[moveNum];
5195 *c = m[7]; if(*c == '\n') *c = NULLCHAR; // promoChar
5196 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
5197 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5200 m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5201 else if(*c && m[8] != '\n') { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5202 *c = m[9]; if(*c == '\n') *c = NULLCHAR;
5203 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to three moves
5208 m[2], m[3] - '0', c);
5210 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5213 m[2], m[3] - '0', c);
5214 SendToProgram(buf, cps);
5216 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5217 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5218 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5219 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5220 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5222 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5223 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5224 SendToProgram(buf, cps);
5226 else SendToProgram(moveList[moveNum], cps);
5227 /* End of additions by Tord */
5230 /* [HGM] setting up the opening has brought engine in force mode! */
5231 /* Send 'go' if we are in a mode where machine should play. */
5232 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5233 (gameMode == TwoMachinesPlay ||
5235 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5237 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5238 SendToProgram("go\n", cps);
5239 if (appData.debugMode) {
5240 fprintf(debugFP, "(extra)\n");
5243 setboardSpoiledMachineBlack = 0;
5247 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5249 char user_move[MSG_SIZ];
5252 if(gameInfo.variant == VariantSChess && promoChar) {
5253 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5254 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5255 } else suffix[0] = NULLCHAR;
5259 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5260 (int)moveType, fromX, fromY, toX, toY);
5261 DisplayError(user_move + strlen("say "), 0);
5263 case WhiteKingSideCastle:
5264 case BlackKingSideCastle:
5265 case WhiteQueenSideCastleWild:
5266 case BlackQueenSideCastleWild:
5268 case WhiteHSideCastleFR:
5269 case BlackHSideCastleFR:
5271 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5273 case WhiteQueenSideCastle:
5274 case BlackQueenSideCastle:
5275 case WhiteKingSideCastleWild:
5276 case BlackKingSideCastleWild:
5278 case WhiteASideCastleFR:
5279 case BlackASideCastleFR:
5281 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5283 case WhiteNonPromotion:
5284 case BlackNonPromotion:
5285 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5287 case WhitePromotion:
5288 case BlackPromotion:
5289 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5290 gameInfo.variant == VariantMakruk)
5291 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5292 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5293 PieceToChar(WhiteFerz));
5294 else if(gameInfo.variant == VariantGreat)
5295 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5296 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5297 PieceToChar(WhiteMan));
5299 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5300 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5306 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5307 ToUpper(PieceToChar((ChessSquare) fromX)),
5308 AAA + toX, ONE + toY);
5310 case IllegalMove: /* could be a variant we don't quite understand */
5311 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5313 case WhiteCapturesEnPassant:
5314 case BlackCapturesEnPassant:
5315 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5316 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5319 SendToICS(user_move);
5320 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5321 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5326 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5327 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5328 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5329 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5330 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5333 if(gameMode != IcsExamining) { // is this ever not the case?
5334 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5336 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5337 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5338 } else { // on FICS we must first go to general examine mode
5339 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5341 if(gameInfo.variant != VariantNormal) {
5342 // try figure out wild number, as xboard names are not always valid on ICS
5343 for(i=1; i<=36; i++) {
5344 snprintf(buf, MSG_SIZ, "wild/%d", i);
5345 if(StringToVariant(buf) == gameInfo.variant) break;
5347 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5348 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5349 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5350 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5351 SendToICS(ics_prefix);
5353 if(startedFromSetupPosition || backwardMostMove != 0) {
5354 fen = PositionToFEN(backwardMostMove, NULL, 1);
5355 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5356 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5358 } else { // FICS: everything has to set by separate bsetup commands
5359 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5360 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5362 if(!WhiteOnMove(backwardMostMove)) {
5363 SendToICS("bsetup tomove black\n");
5365 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5366 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5368 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5369 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5371 i = boards[backwardMostMove][EP_STATUS];
5372 if(i >= 0) { // set e.p.
5373 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5379 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5380 SendToICS("bsetup done\n"); // switch to normal examining.
5382 for(i = backwardMostMove; i<last; i++) {
5384 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5385 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5386 int len = strlen(moveList[i]);
5387 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5388 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5392 SendToICS(ics_prefix);
5393 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5396 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5400 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5402 if (rf == DROP_RANK) {
5403 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5404 sprintf(move, "%c@%c%c\n",
5405 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5407 if (promoChar == 'x' || promoChar == NULLCHAR) {
5408 sprintf(move, "%c%c%c%c\n",
5409 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5410 if(killX >= 0 && killY >= 0) {
5411 sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5412 if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5415 sprintf(move, "%c%c%c%c%c\n",
5416 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5417 if(killX >= 0 && killY >= 0) {
5418 sprintf(move+4, ";%c%c%c\n", AAA + killX, ONE + killY, promoChar);
5419 if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5426 ProcessICSInitScript (FILE *f)
5430 while (fgets(buf, MSG_SIZ, f)) {
5431 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5438 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5440 static ClickType lastClickType;
5443 PieceInString (char *s, ChessSquare piece)
5445 char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5446 while((p = strchr(s, ID))) {
5447 if(!suffix || p[1] == suffix) return TRUE;
5454 Partner (ChessSquare *p)
5455 { // change piece into promotion partner if one shogi-promotes to the other
5456 ChessSquare partner = promoPartner[*p];
5457 if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5458 if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5466 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5467 static int toggleFlag;
5468 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5469 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5470 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5471 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5472 if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5473 if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5475 if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5476 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5477 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5478 else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5479 else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5480 if(!step) step = -1;
5481 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5482 !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5483 promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it
5484 promoSweep == pawn ||
5485 appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5486 (promoSweep == WhiteLion || promoSweep == BlackLion)));
5488 int victim = boards[currentMove][toY][toX];
5489 boards[currentMove][toY][toX] = promoSweep;
5490 DrawPosition(FALSE, boards[currentMove]);
5491 boards[currentMove][toY][toX] = victim;
5493 ChangeDragPiece(promoSweep);
5497 PromoScroll (int x, int y)
5501 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5502 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5503 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5504 if(!step) return FALSE;
5505 lastX = x; lastY = y;
5506 if((promoSweep < BlackPawn) == flipView) step = -step;
5507 if(step > 0) selectFlag = 1;
5508 if(!selectFlag) Sweep(step);
5513 NextPiece (int step)
5515 ChessSquare piece = boards[currentMove][toY][toX];
5518 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5519 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5520 if(!step) step = -1;
5521 } while(PieceToChar(pieceSweep) == '.');
5522 boards[currentMove][toY][toX] = pieceSweep;
5523 DrawPosition(FALSE, boards[currentMove]);
5524 boards[currentMove][toY][toX] = piece;
5526 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5528 AlphaRank (char *move, int n)
5530 // char *p = move, c; int x, y;
5532 if (appData.debugMode) {
5533 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5537 move[2]>='0' && move[2]<='9' &&
5538 move[3]>='a' && move[3]<='x' ) {
5540 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5541 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5543 if(move[0]>='0' && move[0]<='9' &&
5544 move[1]>='a' && move[1]<='x' &&
5545 move[2]>='0' && move[2]<='9' &&
5546 move[3]>='a' && move[3]<='x' ) {
5547 /* input move, Shogi -> normal */
5548 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5549 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5550 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5551 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5554 move[3]>='0' && move[3]<='9' &&
5555 move[2]>='a' && move[2]<='x' ) {
5557 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5558 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5561 move[0]>='a' && move[0]<='x' &&
5562 move[3]>='0' && move[3]<='9' &&
5563 move[2]>='a' && move[2]<='x' ) {
5564 /* output move, normal -> Shogi */
5565 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5566 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5567 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5568 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5569 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5571 if (appData.debugMode) {
5572 fprintf(debugFP, " out = '%s'\n", move);
5576 char yy_textstr[8000];
5578 /* Parser for moves from gnuchess, ICS, or user typein box */
5580 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5582 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5584 switch (*moveType) {
5585 case WhitePromotion:
5586 case BlackPromotion:
5587 case WhiteNonPromotion:
5588 case BlackNonPromotion:
5591 case WhiteCapturesEnPassant:
5592 case BlackCapturesEnPassant:
5593 case WhiteKingSideCastle:
5594 case WhiteQueenSideCastle:
5595 case BlackKingSideCastle:
5596 case BlackQueenSideCastle:
5597 case WhiteKingSideCastleWild:
5598 case WhiteQueenSideCastleWild:
5599 case BlackKingSideCastleWild:
5600 case BlackQueenSideCastleWild:
5601 /* Code added by Tord: */
5602 case WhiteHSideCastleFR:
5603 case WhiteASideCastleFR:
5604 case BlackHSideCastleFR:
5605 case BlackASideCastleFR:
5606 /* End of code added by Tord */
5607 case IllegalMove: /* bug or odd chess variant */
5608 if(currentMoveString[1] == '@') { // illegal drop
5609 *fromX = WhiteOnMove(moveNum) ?
5610 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5611 (int) CharToPiece(ToLower(currentMoveString[0]));
5614 *fromX = currentMoveString[0] - AAA;
5615 *fromY = currentMoveString[1] - ONE;
5616 *toX = currentMoveString[2] - AAA;
5617 *toY = currentMoveString[3] - ONE;
5618 *promoChar = currentMoveString[4];
5619 if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5620 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5621 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5622 if (appData.debugMode) {
5623 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5625 *fromX = *fromY = *toX = *toY = 0;
5628 if (appData.testLegality) {
5629 return (*moveType != IllegalMove);
5631 return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5632 // [HGM] lion: if this is a double move we are less critical
5633 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5638 *fromX = *moveType == WhiteDrop ?
5639 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5640 (int) CharToPiece(ToLower(currentMoveString[0]));
5643 *toX = currentMoveString[2] - AAA;
5644 *toY = currentMoveString[3] - ONE;
5645 *promoChar = NULLCHAR;
5649 case ImpossibleMove:
5659 if (appData.debugMode) {
5660 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5663 *fromX = *fromY = *toX = *toY = 0;
5664 *promoChar = NULLCHAR;
5669 Boolean pushed = FALSE;
5670 char *lastParseAttempt;
5673 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5674 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5675 int fromX, fromY, toX, toY; char promoChar;
5680 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5681 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5682 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5685 endPV = forwardMostMove;
5687 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5688 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5689 lastParseAttempt = pv;
5690 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5691 if(!valid && nr == 0 &&
5692 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5693 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5694 // Hande case where played move is different from leading PV move
5695 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5696 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5697 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5698 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5699 endPV += 2; // if position different, keep this
5700 moveList[endPV-1][0] = fromX + AAA;
5701 moveList[endPV-1][1] = fromY + ONE;
5702 moveList[endPV-1][2] = toX + AAA;
5703 moveList[endPV-1][3] = toY + ONE;
5704 parseList[endPV-1][0] = NULLCHAR;
5705 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5708 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5709 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5710 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5711 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5712 valid++; // allow comments in PV
5716 if(endPV+1 > framePtr) break; // no space, truncate
5719 CopyBoard(boards[endPV], boards[endPV-1]);
5720 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5721 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5722 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5723 CoordsToAlgebraic(boards[endPV - 1],
5724 PosFlags(endPV - 1),
5725 fromY, fromX, toY, toX, promoChar,
5726 parseList[endPV - 1]);
5728 if(atEnd == 2) return; // used hidden, for PV conversion
5729 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5730 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5731 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5732 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5733 DrawPosition(TRUE, boards[currentMove]);
5737 MultiPV (ChessProgramState *cps, int kind)
5738 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5740 for(i=0; i<cps->nrOptions; i++) {
5741 char *s = cps->option[i].name;
5742 if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5743 if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5744 && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5749 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5750 static int multi, pv_margin;
5751 static ChessProgramState *activeCps;
5754 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5756 int startPV, lineStart, origIndex = index;
5757 char *p, buf2[MSG_SIZ];
5758 ChessProgramState *cps = (pane ? &second : &first);
5760 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5761 lastX = x; lastY = y;
5762 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5763 lineStart = startPV = index;
5764 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5765 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5767 do{ while(buf[index] && buf[index] != '\n') index++;
5768 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5770 if(lineStart == 0 && gameMode == AnalyzeMode) {
5772 if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5773 if(n == 0) { // click not on "fewer" or "more"
5774 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5775 pv_margin = cps->option[multi].value;
5776 activeCps = cps; // non-null signals margin adjustment
5778 } else if((multi = MultiPV(cps, 1)) >= 0) {
5779 n += cps->option[multi].value; if(n < 1) n = 1;
5780 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5781 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5782 cps->option[multi].value = n;
5786 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5787 ExcludeClick(origIndex - lineStart);
5789 } else if(!strncmp(buf+lineStart, "dep\t", 4)) { // column headers clicked
5790 Collapse(origIndex - lineStart);
5793 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5794 *start = startPV; *end = index-1;
5795 extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5802 static char buf[10*MSG_SIZ];
5803 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5805 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5806 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5807 for(i = forwardMostMove; i<endPV; i++){
5808 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5809 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5812 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5813 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5814 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5820 LoadPV (int x, int y)
5821 { // called on right mouse click to load PV
5822 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5823 lastX = x; lastY = y;
5824 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5832 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5834 if(pv_margin != activeCps->option[multi].value) {
5836 snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5837 SendToProgram(buf, activeCps);
5838 activeCps->option[multi].value = pv_margin;
5843 if(endPV < 0) return;
5844 if(appData.autoCopyPV) CopyFENToClipboard();
5846 if(extendGame && currentMove > forwardMostMove) {
5847 Boolean saveAnimate = appData.animate;
5849 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5850 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5851 } else storedGames--; // abandon shelved tail of original game
5854 forwardMostMove = currentMove;
5855 currentMove = oldFMM;
5856 appData.animate = FALSE;
5857 ToNrEvent(forwardMostMove);
5858 appData.animate = saveAnimate;
5860 currentMove = forwardMostMove;
5861 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5862 ClearPremoveHighlights();
5863 DrawPosition(TRUE, boards[currentMove]);
5867 MovePV (int x, int y, int h)
5868 { // step through PV based on mouse coordinates (called on mouse move)
5869 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5871 if(activeCps) { // adjusting engine's multi-pv margin
5872 if(x > lastX) pv_margin++; else
5873 if(x < lastX) pv_margin -= (pv_margin > 0);
5876 snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5877 DisplayMessage(buf, "");
5882 // we must somehow check if right button is still down (might be released off board!)
5883 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5884 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5885 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5887 lastX = x; lastY = y;
5889 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5890 if(endPV < 0) return;
5891 if(y < margin) step = 1; else
5892 if(y > h - margin) step = -1;
5893 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5894 currentMove += step;
5895 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5896 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5897 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5898 DrawPosition(FALSE, boards[currentMove]);
5902 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5903 // All positions will have equal probability, but the current method will not provide a unique
5904 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5910 int piecesLeft[(int)BlackPawn];
5911 int seed, nrOfShuffles;
5914 GetPositionNumber ()
5915 { // sets global variable seed
5918 seed = appData.defaultFrcPosition;
5919 if(seed < 0) { // randomize based on time for negative FRC position numbers
5920 for(i=0; i<50; i++) seed += random();
5921 seed = random() ^ random() >> 8 ^ random() << 8;
5922 if(seed<0) seed = -seed;
5927 put (Board board, int pieceType, int rank, int n, int shade)
5928 // put the piece on the (n-1)-th empty squares of the given shade
5932 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5933 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5934 board[rank][i] = (ChessSquare) pieceType;
5935 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5937 piecesLeft[pieceType]--;
5946 AddOnePiece (Board board, int pieceType, int rank, int shade)
5947 // calculate where the next piece goes, (any empty square), and put it there
5951 i = seed % squaresLeft[shade];
5952 nrOfShuffles *= squaresLeft[shade];
5953 seed /= squaresLeft[shade];
5954 put(board, pieceType, rank, i, shade);
5958 AddTwoPieces (Board board, int pieceType, int rank)
5959 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5961 int i, n=squaresLeft[ANY], j=n-1, k;
5963 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5964 i = seed % k; // pick one
5967 while(i >= j) i -= j--;
5968 j = n - 1 - j; i += j;
5969 put(board, pieceType, rank, j, ANY);
5970 put(board, pieceType, rank, i, ANY);
5974 SetUpShuffle (Board board, int number)
5978 GetPositionNumber(); nrOfShuffles = 1;
5980 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5981 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5982 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5984 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5986 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5987 p = (int) board[0][i];
5988 if(p < (int) BlackPawn) piecesLeft[p] ++;
5989 board[0][i] = EmptySquare;
5992 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5993 // shuffles restricted to allow normal castling put KRR first
5994 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5995 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5996 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5997 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5998 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5999 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
6000 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
6001 put(board, WhiteRook, 0, 0, ANY);
6002 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
6005 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
6006 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
6007 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
6008 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
6009 while(piecesLeft[p] >= 2) {
6010 AddOnePiece(board, p, 0, LITE);
6011 AddOnePiece(board, p, 0, DARK);
6013 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
6016 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
6017 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
6018 // but we leave King and Rooks for last, to possibly obey FRC restriction
6019 if(p == (int)WhiteRook) continue;
6020 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
6021 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
6024 // now everything is placed, except perhaps King (Unicorn) and Rooks
6026 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
6027 // Last King gets castling rights
6028 while(piecesLeft[(int)WhiteUnicorn]) {
6029 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6030 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
6033 while(piecesLeft[(int)WhiteKing]) {
6034 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6035 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
6040 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
6041 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6044 // Only Rooks can be left; simply place them all
6045 while(piecesLeft[(int)WhiteRook]) {
6046 i = put(board, WhiteRook, 0, 0, ANY);
6047 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6050 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
6052 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
6055 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6056 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6059 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6063 ptclen (const char *s, char *escapes)
6066 if(!*escapes) return strlen(s);
6067 while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6072 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6073 /* [HGM] moved here from winboard.c because of its general usefulness */
6074 /* Basically a safe strcpy that uses the last character as King */
6076 int result = FALSE; int NrPieces;
6077 unsigned char partner[EmptySquare];
6079 if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6080 && NrPieces >= 12 && !(NrPieces&1)) {
6081 int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6083 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6084 for( i=offs=0; i<NrPieces/2-1; i++ ) {
6086 if(map[j] == '/') offs = WhitePBishop - i, j++;
6087 if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6088 table[i+offs] = map[j++];
6089 if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6090 if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6091 if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6093 table[(int) WhiteKing] = map[j++];
6094 for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6096 if(map[j] == '/') offs = WhitePBishop - ii, j++;
6097 i = WHITE_TO_BLACK ii;
6098 if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6099 table[i+offs] = map[j++];
6100 if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6101 if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6102 if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6104 table[(int) BlackKing] = map[j++];
6107 if(*escapes) { // set up promotion pairing
6108 for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6109 // pieceToChar entirely filled, so we can look up specified partners
6110 for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6112 if(c == '^' || c == '-') { // has specified partner
6114 for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6115 if(c == '^') table[i] = '+';
6116 if(p < EmptySquare) {
6117 if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6118 if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6119 promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6121 } else if(c == '*') {
6122 table[i] = partner[i];
6123 promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6135 SetCharTable (unsigned char *table, const char * map)
6137 return SetCharTableEsc(table, map, "");
6141 Prelude (Board board)
6142 { // [HGM] superchess: random selection of exo-pieces
6143 int i, j, k; ChessSquare p;
6144 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6146 GetPositionNumber(); // use FRC position number
6148 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6149 SetCharTable(pieceToChar, appData.pieceToCharTable);
6150 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6151 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6154 j = seed%4; seed /= 4;
6155 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6156 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6157 board[handSize-1-k][0] = WHITE_TO_BLACK p; board[handSize-1-k][1]++;
6158 j = seed%3 + (seed%3 >= j); seed /= 3;
6159 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6160 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6161 board[handSize-1-k][0] = WHITE_TO_BLACK p; board[handSize-1-k][1]++;
6162 j = seed%3; seed /= 3;
6163 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6164 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6165 board[handSize-1-k][0] = WHITE_TO_BLACK p; board[handSize-1-k][1]++;
6166 j = seed%2 + (seed%2 >= j); seed /= 2;
6167 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6168 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6169 board[handSize-1-k][0] = WHITE_TO_BLACK p; board[handSize-1-k][1]++;
6170 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
6171 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
6172 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6173 put(board, exoPieces[0], 0, 0, ANY);
6174 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6178 InitPosition (int redraw)
6180 ChessSquare (* pieces)[BOARD_FILES];
6181 int i, j, pawnRow=1, pieceRows=1, overrule,
6182 oldx = gameInfo.boardWidth,
6183 oldy = gameInfo.boardHeight,
6184 oldh = gameInfo.holdingsWidth;
6187 if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6189 /* [AS] Initialize pv info list [HGM] and game status */
6191 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6192 pvInfoList[i].depth = 0;
6193 boards[i][EP_STATUS] = EP_NONE;
6194 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6197 initialRulePlies = 0; /* 50-move counter start */
6199 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6200 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6204 /* [HGM] logic here is completely changed. In stead of full positions */
6205 /* the initialized data only consist of the two backranks. The switch */
6206 /* selects which one we will use, which is than copied to the Board */
6207 /* initialPosition, which for the rest is initialized by Pawns and */
6208 /* empty squares. This initial position is then copied to boards[0], */
6209 /* possibly after shuffling, so that it remains available. */
6211 gameInfo.holdingsWidth = 0; /* default board sizes */
6212 gameInfo.boardWidth = 8;
6213 gameInfo.boardHeight = 8;
6214 gameInfo.holdingsSize = 0;
6215 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6216 for(i=0; i<BOARD_FILES-6; i++)
6217 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6218 initialPosition[EP_STATUS] = EP_NONE;
6219 initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6220 SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6221 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6222 SetCharTable(pieceNickName, appData.pieceNickNames);
6223 else SetCharTable(pieceNickName, "............");
6226 switch (gameInfo.variant) {
6227 case VariantFischeRandom:
6228 shuffleOpenings = TRUE;
6229 appData.fischerCastling = TRUE;
6232 case VariantShatranj:
6233 pieces = ShatranjArray;
6234 nrCastlingRights = 0;
6235 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6238 pieces = makrukArray;
6239 nrCastlingRights = 0;
6240 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6243 pieces = aseanArray;
6244 nrCastlingRights = 0;
6245 SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6247 case VariantTwoKings:
6248 pieces = twoKingsArray;
6251 pieces = GrandArray;
6252 nrCastlingRights = 0;
6253 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6254 gameInfo.boardWidth = 10;
6255 gameInfo.boardHeight = 10;
6256 gameInfo.holdingsSize = 7;
6258 case VariantCapaRandom:
6259 shuffleOpenings = TRUE;
6260 appData.fischerCastling = TRUE;
6261 case VariantCapablanca:
6262 pieces = CapablancaArray;
6263 gameInfo.boardWidth = 10;
6264 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6267 pieces = GothicArray;
6268 gameInfo.boardWidth = 10;
6269 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6272 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6273 gameInfo.holdingsSize = 7;
6274 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6277 pieces = JanusArray;
6278 gameInfo.boardWidth = 10;
6279 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6280 nrCastlingRights = 6;
6281 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6282 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6283 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6284 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6285 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6286 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6289 pieces = FalconArray;
6290 gameInfo.boardWidth = 10;
6291 SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6293 case VariantXiangqi:
6294 pieces = XiangqiArray;
6295 gameInfo.boardWidth = 9;
6296 gameInfo.boardHeight = 10;
6297 nrCastlingRights = 0;
6298 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6301 pieces = ShogiArray;
6302 gameInfo.boardWidth = 9;
6303 gameInfo.boardHeight = 9;
6304 gameInfo.holdingsSize = 7;
6305 nrCastlingRights = 0;
6306 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6309 pieces = ChuArray; pieceRows = 3;
6310 gameInfo.boardWidth = 12;
6311 gameInfo.boardHeight = 12;
6312 nrCastlingRights = 0;
6313 // SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6314 // "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6315 SetCharTableEsc(pieceToChar, "P.BRQSEXOG...HD..^DLI^HNV........^T..^L.C...A^AFT/^F^G^M.^E^X^O^I.^P.^B^R..M^S^C^VK"
6316 "p.brqsexog...hd..^dli^hnv........^t..^l.c...a^aft/^f^g^m.^e^x^o^i.^p.^b^r..m^s^c^vk", SUFFIXES);
6318 case VariantCourier:
6319 pieces = CourierArray;
6320 gameInfo.boardWidth = 12;
6321 nrCastlingRights = 0;
6322 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6324 case VariantKnightmate:
6325 pieces = KnightmateArray;
6326 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6328 case VariantSpartan:
6329 pieces = SpartanArray;
6330 SetCharTable(pieceToChar, "PNBRQ.....................K......lw......g...h......ck");
6334 SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6336 case VariantChuChess:
6337 pieces = ChuChessArray;
6338 gameInfo.boardWidth = 10;
6339 gameInfo.boardHeight = 10;
6340 SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6343 pieces = fairyArray;
6344 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6347 pieces = GreatArray;
6348 gameInfo.boardWidth = 10;
6349 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6350 gameInfo.holdingsSize = 8;
6354 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6355 gameInfo.holdingsSize = 8;
6356 startedFromSetupPosition = TRUE;
6358 case VariantCrazyhouse:
6359 case VariantBughouse:
6361 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6362 gameInfo.holdingsSize = 5;
6364 case VariantWildCastle:
6366 /* !!?shuffle with kings guaranteed to be on d or e file */
6367 shuffleOpenings = 1;
6369 case VariantNoCastle:
6370 /* !!?unconstrained back-rank shuffle */
6371 shuffleOpenings = 1;
6372 case VariantSuicide:
6374 nrCastlingRights = 0;
6379 if(appData.NrFiles >= 0) {
6380 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6381 gameInfo.boardWidth = appData.NrFiles;
6383 if(appData.NrRanks >= 0) {
6384 gameInfo.boardHeight = appData.NrRanks;
6386 if(appData.holdingsSize >= 0) {
6387 i = appData.holdingsSize;
6388 // if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6389 gameInfo.holdingsSize = i;
6391 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6392 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6393 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6395 if(!handSize) handSize = BOARD_HEIGHT;
6396 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6397 if(pawnRow < 1) pawnRow = 1;
6398 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6399 gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6400 if(gameInfo.variant == VariantChu) pawnRow = 3;
6402 /* User pieceToChar list overrules defaults */
6403 if(appData.pieceToCharTable != NULL)
6404 SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6406 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6408 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6409 s = (ChessSquare) 0; /* account holding counts in guard band */
6410 for( i=0; i<BOARD_HEIGHT; i++ )
6411 initialPosition[i][j] = s;
6413 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6414 initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6415 initialPosition[pawnRow][j] = WhitePawn;
6416 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6417 if(gameInfo.variant == VariantXiangqi) {
6419 initialPosition[pawnRow][j] =
6420 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6421 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6422 initialPosition[2][j] = WhiteCannon;
6423 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6427 if(gameInfo.variant == VariantChu) {
6428 if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6429 initialPosition[pawnRow+1][j] = WhiteCobra,
6430 initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6431 for(i=1; i<pieceRows; i++) {
6432 initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6433 initialPosition[BOARD_HEIGHT-1-i][j] = pieces[2*i+1][j-gameInfo.holdingsWidth];
6436 if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6437 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6438 initialPosition[0][j] = WhiteRook;
6439 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6442 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] = pieces[1][j-gameInfo.holdingsWidth];
6444 if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6445 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6448 initialPosition[1][j] = WhiteBishop;
6449 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6451 initialPosition[1][j] = WhiteRook;
6452 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6455 if( nrCastlingRights == -1) {
6456 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6457 /* This sets default castling rights from none to normal corners */
6458 /* Variants with other castling rights must set them themselves above */
6459 nrCastlingRights = 6;
6461 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6462 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6463 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6464 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6465 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6466 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6469 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6470 if(gameInfo.variant == VariantGreat) { // promotion commoners
6471 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6472 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6473 initialPosition[handSize-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6474 initialPosition[handSize-1-PieceToNumber(WhiteMan)][1] = 9;
6476 if( gameInfo.variant == VariantSChess ) {
6477 initialPosition[1][0] = BlackMarshall;
6478 initialPosition[2][0] = BlackAngel;
6479 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6480 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6481 initialPosition[1][1] = initialPosition[2][1] =
6482 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6484 initialPosition[CHECK_COUNT] = (gameInfo.variant == Variant3Check ? 0x303 : 0);
6485 if (appData.debugMode) {
6486 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6488 if(shuffleOpenings) {
6489 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6490 startedFromSetupPosition = TRUE;
6492 if(startedFromPositionFile) {
6493 /* [HGM] loadPos: use PositionFile for every new game */
6494 CopyBoard(initialPosition, filePosition);
6495 for(i=0; i<nrCastlingRights; i++)
6496 initialRights[i] = filePosition[CASTLING][i];
6497 startedFromSetupPosition = TRUE;
6499 if(*appData.men) LoadPieceDesc(appData.men);
6501 CopyBoard(boards[0], initialPosition);
6503 if(oldx != gameInfo.boardWidth ||
6504 oldy != gameInfo.boardHeight ||
6505 oldv != gameInfo.variant ||
6506 oldh != gameInfo.holdingsWidth
6508 InitDrawingSizes(-2 ,0);
6510 oldv = gameInfo.variant;
6512 DrawPosition(TRUE, boards[currentMove]);
6516 SendBoard (ChessProgramState *cps, int moveNum)
6518 char message[MSG_SIZ];
6520 if (cps->useSetboard) {
6521 char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6522 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6523 SendToProgram(message, cps);
6528 int i, j, left=0, right=BOARD_WIDTH;
6529 /* Kludge to set black to move, avoiding the troublesome and now
6530 * deprecated "black" command.
6532 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6533 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn && !pieceDesc[WhitePawn] ? "a2a3\n" : "black\nforce\n", cps);
6535 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6537 SendToProgram("edit\n", cps);
6538 SendToProgram("#\n", cps);
6539 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6540 bp = &boards[moveNum][i][left];
6541 for (j = left; j < right; j++, bp++) {
6542 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6543 if ((int) *bp < (int) BlackPawn) {
6544 if(j == BOARD_RGHT+1)
6545 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6546 else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6547 if(message[0] == '+' || message[0] == '~') {
6548 snprintf(message, MSG_SIZ,"%c%c%d+\n",
6549 PieceToChar((ChessSquare)(DEMOTED(*bp))),
6550 AAA + j, ONE + i - '0');
6552 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6553 message[1] = BOARD_RGHT - 1 - j + '1';
6554 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6556 SendToProgram(message, cps);
6561 SendToProgram("c\n", cps);
6562 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6563 bp = &boards[moveNum][i][left];
6564 for (j = left; j < right; j++, bp++) {
6565 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6566 if (((int) *bp != (int) EmptySquare)
6567 && ((int) *bp >= (int) BlackPawn)) {
6568 if(j == BOARD_LEFT-2)
6569 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6570 else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6571 AAA + j, ONE + i - '0');
6572 if(message[0] == '+' || message[0] == '~') {
6573 snprintf(message, MSG_SIZ,"%c%c%d+\n",
6574 PieceToChar((ChessSquare)(DEMOTED(*bp))),
6575 AAA + j, ONE + i - '0');
6577 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6578 message[1] = BOARD_RGHT - 1 - j + '1';
6579 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6581 SendToProgram(message, cps);
6586 SendToProgram(".\n", cps);
6588 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6591 char exclusionHeader[MSG_SIZ];
6592 int exCnt, excludePtr;
6593 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6594 static Exclusion excluTab[200];
6595 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6601 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6602 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6608 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6609 excludePtr = 24; exCnt = 0;
6614 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6615 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6616 char buf[2*MOVE_LEN], *p;
6617 Exclusion *e = excluTab;
6619 for(i=0; i<exCnt; i++)
6620 if(e[i].ff == fromX && e[i].fr == fromY &&
6621 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6622 if(i == exCnt) { // was not in exclude list; add it
6623 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6624 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6625 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6628 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6629 excludePtr++; e[i].mark = excludePtr++;
6630 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6633 exclusionHeader[e[i].mark] = state;
6637 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6638 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6642 if((signed char)promoChar == -1) { // kludge to indicate best move
6643 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6644 return 1; // if unparsable, abort
6646 // update exclusion map (resolving toggle by consulting existing state)
6647 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6649 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6650 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6651 excludeMap[k] |= 1<<j;
6652 else excludeMap[k] &= ~(1<<j);
6654 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6656 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6657 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6659 return (state == '+');
6663 ExcludeClick (int index)
6666 Exclusion *e = excluTab;
6667 if(index < 25) { // none, best or tail clicked
6668 if(index < 13) { // none: include all
6669 WriteMap(0); // clear map
6670 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6671 SendToBoth("include all\n"); // and inform engine
6672 } else if(index > 18) { // tail
6673 if(exclusionHeader[19] == '-') { // tail was excluded
6674 SendToBoth("include all\n");
6675 WriteMap(0); // clear map completely
6676 // now re-exclude selected moves
6677 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6678 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6679 } else { // tail was included or in mixed state
6680 SendToBoth("exclude all\n");
6681 WriteMap(0xFF); // fill map completely
6682 // now re-include selected moves
6683 j = 0; // count them
6684 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6685 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6686 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6689 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6692 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6693 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6694 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6701 DefaultPromoChoice (int white)
6704 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6705 gameInfo.variant == VariantMakruk)
6706 result = WhiteFerz; // no choice
6707 else if(gameInfo.variant == VariantASEAN)
6708 result = WhiteRook; // no choice
6709 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6710 result= WhiteKing; // in Suicide Q is the last thing we want
6711 else if(gameInfo.variant == VariantSpartan)
6712 result = white ? WhiteQueen : WhiteAngel;
6713 else result = WhiteQueen;
6714 if(!white) result = WHITE_TO_BLACK result;
6718 static int autoQueen; // [HGM] oneclick
6721 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6723 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6724 /* [HGM] add Shogi promotions */
6725 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6726 ChessSquare piece, partner;
6730 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6731 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6733 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6734 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6737 if(legal[toY][toX] == 4) return FALSE;
6739 piece = boards[currentMove][fromY][fromX];
6740 if(gameInfo.variant == VariantChu) {
6741 promotionZoneSize = (BOARD_HEIGHT - deadRanks)/3;
6742 if(legal[toY][toX] == 6) return FALSE; // no promotion if highlights deny it
6743 highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6744 } else if(gameInfo.variant == VariantShogi) {
6745 promotionZoneSize = (BOARD_HEIGHT- deadRanks)/3 +(BOARD_HEIGHT == 8);
6746 highestPromotingPiece = (int)WhiteAlfil;
6747 if(PieceToChar(piece) != '+' || PieceToChar(CHUPROMOTED(piece)) == '+') highestPromotingPiece = piece;
6748 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6749 promotionZoneSize = 3;
6752 // Treat Lance as Pawn when it is not representing Amazon or Lance
6753 if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6754 if(piece == WhiteLance) piece = WhitePawn; else
6755 if(piece == BlackLance) piece = BlackPawn;
6758 // next weed out all moves that do not touch the promotion zone at all
6759 if((int)piece >= BlackPawn) {
6760 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6762 if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6763 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6765 if( toY < BOARD_HEIGHT - deadRanks - promotionZoneSize &&
6766 fromY < BOARD_HEIGHT - deadRanks - promotionZoneSize) return FALSE;
6767 if(fromY >= BOARD_HEIGHT - deadRanks - promotionZoneSize && gameInfo.variant == VariantChuChess)
6771 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6773 // weed out mandatory Shogi promotions
6774 if(gameInfo.variant == VariantShogi) {
6775 if(piece >= BlackPawn) {
6776 if(toY == 0 && piece == BlackPawn ||
6777 toY == 0 && piece == BlackQueen ||
6778 toY <= 1 && piece == BlackKnight) {
6783 if(toY == BOARD_HEIGHT-deadRanks-1 && piece == WhitePawn ||
6784 toY == BOARD_HEIGHT-deadRanks-1 && piece == WhiteQueen ||
6785 toY >= BOARD_HEIGHT-deadRanks-2 && piece == WhiteKnight) {
6792 // weed out obviously illegal Pawn moves
6793 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6794 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6795 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6796 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6797 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6798 // note we are not allowed to test for valid (non-)capture, due to premove
6801 // we either have a choice what to promote to, or (in Shogi) whether to promote
6802 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6803 gameInfo.variant == VariantMakruk) {
6804 ChessSquare p=BlackFerz; // no choice
6805 while(p < EmptySquare) { //but make sure we use piece that exists
6806 *promoChoice = PieceToChar(p++);
6807 if(*promoChoice != '.') break;
6809 if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6811 // no sense asking what we must promote to if it is going to explode...
6812 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6813 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6816 // give caller the default choice even if we will not make it
6817 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6818 partner = piece; // pieces can promote if the pieceToCharTable says so
6819 if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6820 else if(Partner(&partner)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6821 if( sweepSelect && gameInfo.variant != VariantGreat
6822 && gameInfo.variant != VariantGrand
6823 && gameInfo.variant != VariantSuper) return FALSE;
6824 if(autoQueen) return FALSE; // predetermined
6826 // suppress promotion popup on illegal moves that are not premoves
6827 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6828 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6829 if(appData.testLegality && !premove) {
6830 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6831 fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6832 if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6833 if(moveType != WhitePromotion && moveType != BlackPromotion)
6841 InPalace (int row, int column)
6842 { /* [HGM] for Xiangqi */
6843 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6844 column < (BOARD_WIDTH + 4)/2 &&
6845 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6850 PieceForSquare (int x, int y)
6852 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT) return -1;
6853 if(x == BOARD_RGHT+1 && handOffsets & 1) y += handSize - BOARD_HEIGHT;
6854 if(x == BOARD_LEFT-2 && !(handOffsets & 2)) y += handSize - BOARD_HEIGHT;
6855 return boards[currentMove][y][x];
6859 More (Board board, int col, int start, int end)
6862 for(k=start; k<end; k++) if(board[k][col]) return (col == 1 ? WhiteMonarch : BlackMonarch); // arrow image
6867 DrawPosition (int repaint, Board board)
6869 Board compactedBoard;
6870 if(handSize > BOARD_HEIGHT && board) {
6872 CopyBoard(compactedBoard, board);
6873 if(handOffsets & 1) {
6874 for(k=0; k<BOARD_HEIGHT; k++) {
6875 compactedBoard[k][BOARD_WIDTH-1] = board[k+handSize-BOARD_HEIGHT][BOARD_WIDTH-1];
6876 compactedBoard[k][BOARD_WIDTH-2] = board[k+handSize-BOARD_HEIGHT][BOARD_WIDTH-2];
6878 compactedBoard[0][BOARD_WIDTH-1] = More(board, BOARD_WIDTH-2, 0, handSize-BOARD_HEIGHT+1);
6879 } else compactedBoard[BOARD_HEIGHT-1][BOARD_WIDTH-1] = More(board, BOARD_WIDTH-2, BOARD_HEIGHT-1, handSize);
6880 if(!(handOffsets & 2)) {
6881 for(k=0; k<BOARD_HEIGHT; k++) {
6882 compactedBoard[k][0] = board[k+handSize-BOARD_HEIGHT][0];
6883 compactedBoard[k][1] = board[k+handSize-BOARD_HEIGHT][1];
6885 compactedBoard[0][0] = More(board, 1, 0, handSize-BOARD_HEIGHT+1);
6886 } else compactedBoard[BOARD_HEIGHT-1][0] = More(board, 1, BOARD_HEIGHT-1, handSize);
6887 DrawPositionX(TRUE, compactedBoard);
6888 } else DrawPositionX(repaint, board);
6892 OKToStartUserMove (int x, int y)
6894 ChessSquare from_piece;
6897 if (matchMode) return FALSE;
6898 if (gameMode == EditPosition) return TRUE;
6900 if (x >= 0 && y >= 0)
6901 from_piece = boards[currentMove][y][x];
6903 from_piece = EmptySquare;
6905 if (from_piece == EmptySquare) return FALSE;
6907 white_piece = (int)from_piece >= (int)WhitePawn &&
6908 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6912 case TwoMachinesPlay:
6920 case MachinePlaysWhite:
6921 case IcsPlayingBlack:
6922 if (appData.zippyPlay) return FALSE;
6924 DisplayMoveError(_("You are playing Black"));
6929 case MachinePlaysBlack:
6930 case IcsPlayingWhite:
6931 if (appData.zippyPlay) return FALSE;
6933 DisplayMoveError(_("You are playing White"));
6938 case PlayFromGameFile:
6939 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6942 if (!white_piece && WhiteOnMove(currentMove)) {
6943 DisplayMoveError(_("It is White's turn"));
6946 if (white_piece && !WhiteOnMove(currentMove)) {
6947 DisplayMoveError(_("It is Black's turn"));
6950 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6951 /* Editing correspondence game history */
6952 /* Could disallow this or prompt for confirmation */
6957 case BeginningOfGame:
6958 if (appData.icsActive) return FALSE;
6959 if (!appData.noChessProgram) {
6961 DisplayMoveError(_("You are playing White"));
6968 if (!white_piece && WhiteOnMove(currentMove)) {
6969 DisplayMoveError(_("It is White's turn"));
6972 if (white_piece && !WhiteOnMove(currentMove)) {
6973 DisplayMoveError(_("It is Black's turn"));
6982 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6983 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6984 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6985 && gameMode != AnalyzeFile && gameMode != Training) {
6986 DisplayMoveError(_("Displayed position is not current"));
6993 OnlyMove (int *x, int *y, Boolean captures)
6995 DisambiguateClosure cl;
6996 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6998 case MachinePlaysBlack:
6999 case IcsPlayingWhite:
7000 case BeginningOfGame:
7001 if(!WhiteOnMove(currentMove)) return FALSE;
7003 case MachinePlaysWhite:
7004 case IcsPlayingBlack:
7005 if(WhiteOnMove(currentMove)) return FALSE;
7012 cl.pieceIn = EmptySquare;
7017 cl.promoCharIn = NULLCHAR;
7018 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
7019 if( cl.kind == NormalMove ||
7020 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
7021 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
7022 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
7029 if(cl.kind != ImpossibleMove) return FALSE;
7030 cl.pieceIn = EmptySquare;
7035 cl.promoCharIn = NULLCHAR;
7036 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
7037 if( cl.kind == NormalMove ||
7038 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
7039 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
7040 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
7045 autoQueen = TRUE; // act as if autoQueen on when we click to-square
7051 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
7052 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
7053 int lastLoadGameUseList = FALSE;
7054 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
7055 ChessMove lastLoadGameStart = EndOfFile;
7057 Boolean addToBookFlag;
7058 static Board rightsBoard, nullBoard;
7061 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
7065 int ff=fromX, rf=fromY, ft=toX, rt=toY;
7067 /* Check if the user is playing in turn. This is complicated because we
7068 let the user "pick up" a piece before it is his turn. So the piece he
7069 tried to pick up may have been captured by the time he puts it down!
7070 Therefore we use the color the user is supposed to be playing in this
7071 test, not the color of the piece that is currently on the starting
7072 square---except in EditGame mode, where the user is playing both
7073 sides; fortunately there the capture race can't happen. (It can
7074 now happen in IcsExamining mode, but that's just too bad. The user
7075 will get a somewhat confusing message in that case.)
7080 case TwoMachinesPlay:
7084 /* We switched into a game mode where moves are not accepted,
7085 perhaps while the mouse button was down. */
7088 case MachinePlaysWhite:
7089 /* User is moving for Black */
7090 if (WhiteOnMove(currentMove)) {
7091 DisplayMoveError(_("It is White's turn"));
7096 case MachinePlaysBlack:
7097 /* User is moving for White */
7098 if (!WhiteOnMove(currentMove)) {
7099 DisplayMoveError(_("It is Black's turn"));
7104 case PlayFromGameFile:
7105 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7108 case BeginningOfGame:
7111 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7112 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7113 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7114 /* User is moving for Black */
7115 if (WhiteOnMove(currentMove)) {
7116 DisplayMoveError(_("It is White's turn"));
7120 /* User is moving for White */
7121 if (!WhiteOnMove(currentMove)) {
7122 DisplayMoveError(_("It is Black's turn"));
7128 case IcsPlayingBlack:
7129 /* User is moving for Black */
7130 if (WhiteOnMove(currentMove)) {
7131 if (!appData.premove) {
7132 DisplayMoveError(_("It is White's turn"));
7133 } else if (toX >= 0 && toY >= 0) {
7136 premoveFromX = fromX;
7137 premoveFromY = fromY;
7138 premovePromoChar = promoChar;
7140 if (appData.debugMode)
7141 fprintf(debugFP, "Got premove: fromX %d,"
7142 "fromY %d, toX %d, toY %d\n",
7143 fromX, fromY, toX, toY);
7145 DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7150 case IcsPlayingWhite:
7151 /* User is moving for White */
7152 if (!WhiteOnMove(currentMove)) {
7153 if (!appData.premove) {
7154 DisplayMoveError(_("It is Black's turn"));
7155 } else if (toX >= 0 && toY >= 0) {
7158 premoveFromX = fromX;
7159 premoveFromY = fromY;
7160 premovePromoChar = promoChar;
7162 if (appData.debugMode)
7163 fprintf(debugFP, "Got premove: fromX %d,"
7164 "fromY %d, toX %d, toY %d\n",
7165 fromX, fromY, toX, toY);
7167 DrawPosition(TRUE, boards[currentMove]);
7176 /* EditPosition, empty square, or different color piece;
7177 click-click move is possible */
7178 if (toX == -2 || toY == -2) {
7179 boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7180 DrawPosition(FALSE, boards[currentMove]);
7182 } else if (toX >= 0 && toY >= 0) {
7183 if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7184 ChessSquare p = boards[0][rf][ff];
7185 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7186 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7187 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
7188 int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7189 DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7192 } else rightsBoard[toY][toX] = 0; // revoke rights on moving
7193 boards[0][toY][toX] = boards[0][fromY][fromX];
7194 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7195 if(boards[0][fromY][0] != EmptySquare) {
7196 if(boards[0][fromY][1]) boards[0][fromY][1]--;
7197 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
7200 if(fromX == BOARD_RGHT+1) {
7201 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7202 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7203 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7206 boards[0][fromY][fromX] = gatingPiece;
7208 DrawPosition(FALSE, boards[currentMove]);
7214 if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7215 pup = boards[currentMove][toY][toX];
7217 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7218 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7219 if( pup != EmptySquare ) return;
7220 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7221 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7222 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7223 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7224 if(fromX == 0) fromY = handSize-1 - fromY; // black holdings upside-down
7225 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7226 while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7230 /* [HGM] always test for legality, to get promotion info */
7231 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7232 fromY, fromX, toY, toX, promoChar);
7234 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7236 if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7238 /* [HGM] but possibly ignore an IllegalMove result */
7239 if (appData.testLegality) {
7240 if (moveType == IllegalMove || moveType == ImpossibleMove) {
7241 DisplayMoveError(_("Illegal move"));
7246 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7247 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7248 ClearPremoveHighlights(); // was included
7249 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
7250 DrawPosition(FALSE, NULL);
7254 if(addToBookFlag) { // adding moves to book
7255 char buf[MSG_SIZ], move[MSG_SIZ];
7256 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7257 if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7258 killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7259 snprintf(buf, MSG_SIZ, " 0.0%% 1 %s\n", move);
7261 addToBookFlag = FALSE;
7266 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7269 /* Common tail of UserMoveEvent and DropMenuEvent */
7271 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7275 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7276 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7277 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7278 if(WhiteOnMove(currentMove)) {
7279 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7281 if(!boards[currentMove][handSize-1-k][1]) return 0;
7285 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7286 move type in caller when we know the move is a legal promotion */
7287 if(moveType == NormalMove && promoChar)
7288 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7290 /* [HGM] <popupFix> The following if has been moved here from
7291 UserMoveEvent(). Because it seemed to belong here (why not allow
7292 piece drops in training games?), and because it can only be
7293 performed after it is known to what we promote. */
7294 if (gameMode == Training) {
7295 /* compare the move played on the board to the next move in the
7296 * game. If they match, display the move and the opponent's response.
7297 * If they don't match, display an error message.
7301 CopyBoard(testBoard, boards[currentMove]);
7302 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7304 if (CompareBoards(testBoard, boards[currentMove+1])) {
7305 ForwardInner(currentMove+1);
7307 /* Autoplay the opponent's response.
7308 * if appData.animate was TRUE when Training mode was entered,
7309 * the response will be animated.
7311 saveAnimate = appData.animate;
7312 appData.animate = animateTraining;
7313 ForwardInner(currentMove+1);
7314 appData.animate = saveAnimate;
7316 /* check for the end of the game */
7317 if (currentMove >= forwardMostMove) {
7318 gameMode = PlayFromGameFile;
7320 SetTrainingModeOff();
7321 DisplayInformation(_("End of game"));
7324 DisplayError(_("Incorrect move"), 0);
7329 /* Ok, now we know that the move is good, so we can kill
7330 the previous line in Analysis Mode */
7331 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7332 && currentMove < forwardMostMove) {
7333 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7334 else forwardMostMove = currentMove;
7339 /* If we need the chess program but it's dead, restart it */
7340 ResurrectChessProgram();
7342 /* A user move restarts a paused game*/
7346 thinkOutput[0] = NULLCHAR;
7348 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7350 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7351 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7355 if (gameMode == BeginningOfGame) {
7356 if (appData.noChessProgram) {
7357 gameMode = EditGame;
7361 gameMode = MachinePlaysBlack;
7364 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7366 if (first.sendName) {
7367 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7368 SendToProgram(buf, &first);
7375 /* Relay move to ICS or chess engine */
7376 if (appData.icsActive) {
7377 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7378 gameMode == IcsExamining) {
7379 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7380 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7382 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7384 // also send plain move, in case ICS does not understand atomic claims
7385 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7389 if (first.sendTime && (gameMode == BeginningOfGame ||
7390 gameMode == MachinePlaysWhite ||
7391 gameMode == MachinePlaysBlack)) {
7392 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7394 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7395 // [HGM] book: if program might be playing, let it use book
7396 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7397 first.maybeThinking = TRUE;
7398 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7399 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7400 SendBoard(&first, currentMove+1);
7401 if(second.analyzing) {
7402 if(!second.useSetboard) SendToProgram("undo\n", &second);
7403 SendBoard(&second, currentMove+1);
7406 SendMoveToProgram(forwardMostMove-1, &first);
7407 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7409 if (currentMove == cmailOldMove + 1) {
7410 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7414 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7418 if(appData.testLegality)
7419 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7425 if (WhiteOnMove(currentMove)) {
7426 GameEnds(BlackWins, "Black mates", GE_PLAYER);
7428 GameEnds(WhiteWins, "White mates", GE_PLAYER);
7432 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7437 case MachinePlaysBlack:
7438 case MachinePlaysWhite:
7439 /* disable certain menu options while machine is thinking */
7440 SetMachineThinkingEnables();
7447 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7448 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7450 if(bookHit) { // [HGM] book: simulate book reply
7451 static char bookMove[MSG_SIZ]; // a bit generous?
7453 programStats.nodes = programStats.depth = programStats.time =
7454 programStats.score = programStats.got_only_move = 0;
7455 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7457 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7458 strcat(bookMove, bookHit);
7459 HandleMachineMove(bookMove, &first);
7465 MarkByFEN(char *fen)
7468 if(!appData.markers || !appData.highlightDragging) return;
7469 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = marker[r][f] = 0;
7470 r=BOARD_HEIGHT-1-deadRanks; f=BOARD_LEFT;
7473 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7474 if(*fen == 'B') legal[r][f] = 4; else // request auto-promotion to victim
7475 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 6; else
7476 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7477 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7478 if(*fen == 'T') marker[r][f++] = 0; else
7479 if(*fen == 'Y') marker[r][f++] = 1; else
7480 if(*fen == 'G') marker[r][f++] = 3; else
7481 if(*fen == 'B') marker[r][f++] = 4; else
7482 if(*fen == 'C') marker[r][f++] = 5; else
7483 if(*fen == 'M') marker[r][f++] = 6; else
7484 if(*fen == 'W') marker[r][f++] = 7; else
7485 if(*fen == 'D') marker[r][f++] = 8; else
7486 if(*fen == 'R') marker[r][f++] = 2; else {
7487 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7490 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7494 DrawPosition(TRUE, NULL);
7497 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7500 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7502 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7503 Markers *m = (Markers *) closure;
7504 if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7505 kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7506 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7507 || kind == WhiteCapturesEnPassant
7508 || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7509 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7512 static int hoverSavedValid;
7515 MarkTargetSquares (int clear)
7518 if(clear) { // no reason to ever suppress clearing
7519 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7520 hoverSavedValid = 0;
7521 if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7524 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7525 !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7526 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7527 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7528 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7530 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = legal[y][x] = 0;
7533 DrawPosition(FALSE, NULL);
7537 Explode (Board board, int fromX, int fromY, int toX, int toY)
7539 if(gameInfo.variant == VariantAtomic &&
7540 (board[toY][toX] != EmptySquare || // capture?
7541 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7542 board[fromY][fromX] == BlackPawn )
7544 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7550 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7553 CanPromote (ChessSquare piece, int y)
7555 int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7556 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7557 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7558 if(IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantXiangqi ||
7559 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7560 (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7561 gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7562 return (piece == BlackPawn && y <= zone ||
7563 piece == WhitePawn && y >= BOARD_HEIGHT-1-deadRanks-zone ||
7564 piece == BlackLance && y <= zone ||
7565 piece == WhiteLance && y >= BOARD_HEIGHT-1-deadRanks-zone );
7569 HoverEvent (int xPix, int yPix, int x, int y)
7571 static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7573 if(!first.highlight) return;
7574 if(fromX != oldFromX || fromY != oldFromY) oldX = oldY = -1; // kludge to fake entry on from-click
7575 if(x == oldX && y == oldY) return; // only do something if we enter new square
7576 oldFromX = fromX; oldFromY = fromY;
7577 if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7578 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7579 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7580 hoverSavedValid = 1;
7581 } else if(oldX != x || oldY != y) {
7582 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7583 if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7584 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7585 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7586 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7588 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7589 SendToProgram(buf, &first);
7592 // SetHighlights(fromX, fromY, x, y);
7596 void ReportClick(char *action, int x, int y)
7598 char buf[MSG_SIZ]; // Inform engine of what user does
7600 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7601 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7602 legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7603 if(!first.highlight || gameMode == EditPosition) return;
7604 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7605 SendToProgram(buf, &first);
7608 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7609 Boolean deferChoice;
7610 int createX = -1, createY = -1; // square where we last created a piece in EditPosition mode
7613 LeftClick (ClickType clickType, int xPix, int yPix)
7616 static Boolean saveAnimate;
7617 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7618 char promoChoice = NULLCHAR;
7620 static TimeMark lastClickTime, prevClickTime;
7622 if(flashing) return;
7624 if(!deferChoice) { // when called for a retry, skip everything to the point where we left off
7625 x = EventToSquare(xPix, BOARD_WIDTH);
7626 y = EventToSquare(yPix, BOARD_HEIGHT);
7627 if (!flipView && y >= 0) {
7628 y = BOARD_HEIGHT - 1 - y;
7630 if (flipView && x >= 0) {
7631 x = BOARD_WIDTH - 1 - x;
7634 // map clicks in offsetted holdings back to true coords (or switch the offset)
7635 if(x == BOARD_RGHT+1) {
7636 if(handOffsets & 1) {
7637 if(y == 0) { handOffsets &= ~1; DrawPosition(TRUE, boards[currentMove]); return; }
7638 y += handSize - BOARD_HEIGHT;
7639 } else if(y == BOARD_HEIGHT-1) { handOffsets |= 1; DrawPosition(TRUE, boards[currentMove]); return; }
7641 if(x == BOARD_LEFT-2) {
7642 if(!(handOffsets & 2)) {
7643 if(y == 0) { handOffsets |= 2; DrawPosition(TRUE, boards[currentMove]); return; }
7644 y += handSize - BOARD_HEIGHT;
7645 } else if(y == BOARD_HEIGHT-1) { handOffsets &= ~2; DrawPosition(TRUE, boards[currentMove]); return; }
7648 if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press &&
7649 (boards[currentMove][y][x] == EmptySquare || x == createX && y == createY) ) {
7651 RightClick(clickType, xPix, yPix, &dummy, &dummy);
7656 createX = createY = -1;
7658 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7660 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7662 if (clickType == Press) ErrorPopDown();
7663 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7665 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7666 defaultPromoChoice = promoSweep;
7667 promoSweep = EmptySquare; // terminate sweep
7668 promoDefaultAltered = TRUE;
7669 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7672 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7673 if(clickType == Release) return; // ignore upclick of click-click destination
7674 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7675 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7676 if(gameInfo.holdingsWidth &&
7677 (WhiteOnMove(currentMove)
7678 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7679 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7680 // click in right holdings, for determining promotion piece
7681 ChessSquare p = boards[currentMove][y][x];
7682 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7683 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7684 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7685 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7690 DrawPosition(FALSE, boards[currentMove]);
7694 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7695 if(clickType == Press
7696 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7697 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7698 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7701 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7702 // could be static click on premove from-square: abort premove
7704 ClearPremoveHighlights();
7707 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7708 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7710 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7711 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7712 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7713 defaultPromoChoice = DefaultPromoChoice(side);
7716 autoQueen = appData.alwaysPromoteToQueen;
7720 gatingPiece = EmptySquare;
7721 if (clickType != Press) {
7722 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7723 DragPieceEnd(xPix, yPix); dragging = 0;
7724 DrawPosition(FALSE, NULL);
7728 doubleClick = FALSE;
7729 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7730 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7732 fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7733 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7734 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7735 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7737 if (OKToStartUserMove(fromX, fromY)) {
7739 ReportClick("lift", x, y);
7740 MarkTargetSquares(0);
7741 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7742 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7743 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7744 promoSweep = defaultPromoChoice;
7745 selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7746 Sweep(0); // Pawn that is going to promote: preview promotion piece
7747 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7749 if (appData.highlightDragging) {
7750 SetHighlights(fromX, fromY, -1, -1);
7754 } else fromX = fromY = -1;
7760 if (clickType == Press && gameMode != EditPosition) {
7765 // ignore off-board to clicks
7766 if(y < 0 || x < 0) return;
7768 /* Check if clicking again on the same color piece */
7769 fromP = boards[currentMove][fromY][fromX];
7770 toP = boards[currentMove][y][x];
7771 frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7772 if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7773 marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7774 ((WhitePawn <= fromP && fromP <= WhiteKing &&
7775 WhitePawn <= toP && toP <= WhiteKing &&
7776 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7777 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7778 (BlackPawn <= fromP && fromP <= BlackKing &&
7779 BlackPawn <= toP && toP <= BlackKing &&
7780 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7781 !(fromP == BlackKing && toP == BlackRook && frc)))) {
7782 /* Clicked again on same color piece -- changed his mind */
7783 second = (x == fromX && y == fromY);
7784 killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7785 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7786 second = FALSE; // first double-click rather than scond click
7787 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7789 promoDefaultAltered = FALSE;
7790 if(!second) MarkTargetSquares(1);
7791 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7792 if (appData.highlightDragging) {
7793 SetHighlights(x, y, -1, -1);
7797 if (OKToStartUserMove(x, y)) {
7798 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7799 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7800 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7801 gatingPiece = boards[currentMove][fromY][fromX];
7802 else gatingPiece = doubleClick ? fromP : EmptySquare;
7804 fromY = y; dragging = 1;
7805 if(!second) ReportClick("lift", x, y);
7806 MarkTargetSquares(0);
7807 DragPieceBegin(xPix, yPix, FALSE);
7808 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7809 promoSweep = defaultPromoChoice;
7810 selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7811 Sweep(0); // Pawn that is going to promote: preview promotion piece
7815 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7818 // ignore clicks on holdings
7819 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7822 if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7823 gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7824 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7828 if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7829 DragPieceEnd(xPix, yPix); dragging = 0;
7831 // a deferred attempt to click-click move an empty square on top of a piece
7832 boards[currentMove][y][x] = EmptySquare;
7834 DrawPosition(FALSE, boards[currentMove]);
7835 fromX = fromY = -1; clearFlag = 0;
7838 if (appData.animateDragging) {
7839 /* Undo animation damage if any */
7840 DrawPosition(FALSE, NULL);
7843 /* Second up/down in same square; just abort move */
7846 gatingPiece = EmptySquare;
7849 ClearPremoveHighlights();
7850 MarkTargetSquares(-1);
7851 DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7853 /* First upclick in same square; start click-click mode */
7854 SetHighlights(x, y, -1, -1);
7861 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7862 fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7863 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7864 DisplayMessage(_("only marked squares are legal"),"");
7865 DrawPosition(TRUE, NULL);
7866 return; // ignore to-click
7869 /* we now have a different from- and (possibly off-board) to-square */
7870 /* Completed move */
7871 if(!sweepSelecting) {
7876 piece = boards[currentMove][fromY][fromX];
7878 saveAnimate = appData.animate;
7879 if (clickType == Press) {
7880 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7881 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7882 // must be Edit Position mode with empty-square selected
7883 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7884 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7887 if(dragging == 2) { // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7890 if(x == killX && y == killY) { // second click on this square, which was selected as first-leg target
7891 killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1; // this informs us no second leg is coming, so treat as to-click without intermediate
7893 if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7894 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7895 if(appData.sweepSelect) {
7896 promoSweep = defaultPromoChoice;
7897 if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7898 selectFlag = 0; lastX = xPix; lastY = yPix;
7899 ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7900 saveFlash = appData.flashCount; appData.flashCount = 0;
7901 Sweep(0); // Pawn that is going to promote: preview promotion piece
7903 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7904 MarkTargetSquares(1);
7906 return; // promo popup appears on up-click
7908 /* Finish clickclick move */
7909 if (appData.animate || appData.highlightLastMove) {
7910 SetHighlights(fromX, fromY, toX, toY);
7914 MarkTargetSquares(1);
7915 } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7916 sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7917 *promoRestrict = 0; appData.flashCount = saveFlash;
7918 if (appData.animate || appData.highlightLastMove) {
7919 SetHighlights(fromX, fromY, toX, toY);
7923 MarkTargetSquares(1);
7926 // [HGM] this must be done after the move is made, as with arrow it could lead to a board redraw with piece still on from square
7927 /* Finish drag move */
7928 if (appData.highlightLastMove) {
7929 SetHighlights(fromX, fromY, toX, toY);
7934 if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7935 defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7936 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7937 if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7938 dragging *= 2; // flag button-less dragging if we are dragging
7939 MarkTargetSquares(1);
7940 if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7942 kill2X = killX; kill2Y = killY;
7943 killX = x; killY = y; // remember this square as intermediate
7944 ReportClick("put", x, y); // and inform engine
7945 ReportClick("lift", x, y);
7946 MarkTargetSquares(0);
7950 DragPieceEnd(xPix, yPix); dragging = 0;
7951 /* Don't animate move and drag both */
7952 appData.animate = FALSE;
7953 MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7956 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7957 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7958 ChessSquare piece = boards[currentMove][fromY][fromX];
7959 if(gameMode == EditPosition && piece != EmptySquare &&
7960 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7963 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7964 n = PieceToNumber(piece - (int)BlackPawn);
7965 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7966 boards[currentMove][handSize-1 - n][0] = piece;
7967 boards[currentMove][handSize-1 - n][1]++;
7969 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7970 n = PieceToNumber(piece);
7971 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7972 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7973 boards[currentMove][n][BOARD_WIDTH-2]++;
7975 boards[currentMove][fromY][fromX] = EmptySquare;
7979 MarkTargetSquares(1);
7980 DrawPosition(TRUE, boards[currentMove]);
7984 // off-board moves should not be highlighted
7985 if(x < 0 || y < 0) {
7987 DrawPosition(FALSE, NULL);
7988 } else ReportClick("put", x, y);
7990 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7993 if(legal[toY][toX] == 2) { // highlight-induced promotion
7994 if(piece == defaultPromoChoice) promoChoice = NULLCHAR; // deferral
7995 else promoChoice = ToLower(PieceToChar(defaultPromoChoice));
7996 } else if(legal[toY][toX] == 4) { // blue target square: engine must supply promotion choice
7997 if(!*promoRestrict) { // but has not done that yet
7998 deferChoice = TRUE; // set up retry for when it does
7999 return; // and wait for that
8001 promoChoice = ToLower(*promoRestrict); // force engine's choice
8002 deferChoice = FALSE;
8005 if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
8006 SetHighlights(fromX, fromY, toX, toY);
8007 MarkTargetSquares(1);
8008 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
8009 // [HGM] super: promotion to captured piece selected from holdings
8010 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
8011 promotionChoice = TRUE;
8012 // kludge follows to temporarily execute move on display, without promoting yet
8013 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
8014 boards[currentMove][toY][toX] = p;
8015 DrawPosition(FALSE, boards[currentMove]);
8016 boards[currentMove][fromY][fromX] = p; // take back, but display stays
8017 boards[currentMove][toY][toX] = q;
8018 DisplayMessage("Click in holdings to choose piece", "");
8021 DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
8022 PromotionPopUp(promoChoice);
8024 int oldMove = currentMove;
8025 flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
8026 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
8027 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
8028 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
8029 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
8030 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
8031 DrawPosition(TRUE, boards[currentMove]);
8032 else DrawPosition(FALSE, NULL);
8036 appData.animate = saveAnimate;
8037 if (appData.animate || appData.animateDragging) {
8038 /* Undo animation damage if needed */
8039 // DrawPosition(FALSE, NULL);
8044 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
8045 { // front-end-free part taken out of PieceMenuPopup
8046 int whichMenu; int xSqr, ySqr;
8048 if(seekGraphUp) { // [HGM] seekgraph
8049 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
8050 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
8054 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
8055 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
8056 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
8057 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
8058 if(action == Press) {
8059 originalFlip = flipView;
8060 flipView = !flipView; // temporarily flip board to see game from partners perspective
8061 DrawPosition(TRUE, partnerBoard);
8062 DisplayMessage(partnerStatus, "");
8064 } else if(action == Release) {
8065 flipView = originalFlip;
8066 DrawPosition(TRUE, boards[currentMove]);
8072 xSqr = EventToSquare(x, BOARD_WIDTH);
8073 ySqr = EventToSquare(y, BOARD_HEIGHT);
8074 if (action == Release) {
8075 if(pieceSweep != EmptySquare) {
8076 EditPositionMenuEvent(pieceSweep, toX, toY);
8077 pieceSweep = EmptySquare;
8078 } else UnLoadPV(); // [HGM] pv
8080 if (action != Press) return -2; // return code to be ignored
8083 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
8085 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
8086 if (xSqr < 0 || ySqr < 0) return -1;
8087 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
8088 if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8089 if(xSqr == createX && ySqr == createY && xSqr != BOARD_LEFT-2 && xSqr != BOARD_RGHT+1) {
8090 ChessSquare p = boards[currentMove][ySqr][xSqr];
8091 do { if(++p == EmptySquare) p = WhitePawn; } while(PieceToChar(p) == '.');
8092 boards[currentMove][ySqr][xSqr] = p; DrawPosition(FALSE, boards[currentMove]);
8095 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
8096 createX = toX = xSqr; createY = toY = ySqr; lastX = x, lastY = y;
8100 if(!appData.icsEngineAnalyze) return -1;
8101 case IcsPlayingWhite:
8102 case IcsPlayingBlack:
8103 if(!appData.zippyPlay) goto noZip;
8106 case MachinePlaysWhite:
8107 case MachinePlaysBlack:
8108 case TwoMachinesPlay: // [HGM] pv: use for showing PV
8109 if (!appData.dropMenu) {
8111 return 2; // flag front-end to grab mouse events
8113 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8114 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8117 if (xSqr < 0 || ySqr < 0) return -1;
8118 if (!appData.dropMenu || appData.testLegality &&
8119 gameInfo.variant != VariantBughouse &&
8120 gameInfo.variant != VariantCrazyhouse) return -1;
8121 whichMenu = 1; // drop menu
8127 if (((*fromX = xSqr) < 0) ||
8128 ((*fromY = ySqr) < 0)) {
8129 *fromX = *fromY = -1;
8133 *fromX = BOARD_WIDTH - 1 - *fromX;
8135 *fromY = BOARD_HEIGHT - 1 - *fromY;
8141 Wheel (int dir, int x, int y)
8143 if(gameMode == EditPosition) {
8144 int xSqr = EventToSquare(x, BOARD_WIDTH);
8145 int ySqr = EventToSquare(y, BOARD_HEIGHT);
8146 if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8147 if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8149 boards[currentMove][ySqr][xSqr] += dir;
8150 if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8151 if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8152 } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8153 DrawPosition(FALSE, boards[currentMove]);
8154 } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8158 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8160 // char * hint = lastHint;
8161 FrontEndProgramStats stats;
8163 stats.which = cps == &first ? 0 : 1;
8164 stats.depth = cpstats->depth;
8165 stats.nodes = cpstats->nodes;
8166 stats.score = cpstats->score;
8167 stats.time = cpstats->time;
8168 stats.pv = cpstats->movelist;
8169 stats.hint = lastHint;
8170 stats.an_move_index = 0;
8171 stats.an_move_count = 0;
8173 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8174 stats.hint = cpstats->move_name;
8175 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8176 stats.an_move_count = cpstats->nr_moves;
8179 if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
8181 if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8182 && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8184 SetProgramStats( &stats );
8188 ClearEngineOutputPane (int which)
8190 static FrontEndProgramStats dummyStats;
8191 dummyStats.which = which;
8192 dummyStats.pv = "#";
8193 SetProgramStats( &dummyStats );
8196 #define MAXPLAYERS 500
8199 TourneyStandings (int display)
8201 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8202 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8203 char result, *p, *names[MAXPLAYERS];
8205 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8206 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8207 names[0] = p = strdup(appData.participants);
8208 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8210 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8212 while(result = appData.results[nr]) {
8213 color = Pairing(nr, nPlayers, &w, &b, &dummy);
8214 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8215 wScore = bScore = 0;
8217 case '+': wScore = 2; break;
8218 case '-': bScore = 2; break;
8219 case '=': wScore = bScore = 1; break;
8221 case '*': return strdup("busy"); // tourney not finished
8229 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8230 for(w=0; w<nPlayers; w++) {
8232 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8233 ranking[w] = b; points[w] = bScore; score[b] = -2;
8235 p = malloc(nPlayers*34+1);
8236 for(w=0; w<nPlayers && w<display; w++)
8237 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8243 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8244 { // count all piece types
8246 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8247 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8248 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8251 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8252 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8253 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8254 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8255 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
8256 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8261 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8263 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8264 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8266 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8267 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8268 if(myPawns == 2 && nMine == 3) // KPP
8269 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8270 if(myPawns == 1 && nMine == 2) // KP
8271 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8272 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8273 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8274 if(myPawns) return FALSE;
8275 if(pCnt[WhiteRook+side])
8276 return pCnt[BlackRook-side] ||
8277 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8278 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8279 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8280 if(pCnt[WhiteCannon+side]) {
8281 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8282 return majorDefense || pCnt[BlackAlfil-side] >= 2;
8284 if(pCnt[WhiteKnight+side])
8285 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8290 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8292 VariantClass v = gameInfo.variant;
8294 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8295 if(v == VariantShatranj) return TRUE; // always winnable through baring
8296 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8297 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8299 if(v == VariantXiangqi) {
8300 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8302 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8303 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8304 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8305 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8306 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8307 if(stale) // we have at least one last-rank P plus perhaps C
8308 return majors // KPKX
8309 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8311 return pCnt[WhiteFerz+side] // KCAK
8312 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8313 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8314 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8316 } else if(v == VariantKnightmate) {
8317 if(nMine == 1) return FALSE;
8318 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8319 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8320 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8322 if(nMine == 1) return FALSE; // bare King
8323 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
8324 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8325 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8326 // by now we have King + 1 piece (or multiple Bishops on the same color)
8327 if(pCnt[WhiteKnight+side])
8328 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8329 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8330 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8332 return (pCnt[BlackKnight-side]); // KBKN, KFKN
8333 if(pCnt[WhiteAlfil+side])
8334 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8335 if(pCnt[WhiteWazir+side])
8336 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8343 CompareWithRights (Board b1, Board b2)
8346 if(!CompareBoards(b1, b2)) return FALSE;
8347 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8348 /* compare castling rights */
8349 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8350 rights++; /* King lost rights, while rook still had them */
8351 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8352 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8353 rights++; /* but at least one rook lost them */
8355 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8357 if( b1[CASTLING][5] != NoRights ) {
8358 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8365 Adjudicate (ChessProgramState *cps)
8366 { // [HGM] some adjudications useful with buggy engines
8367 // [HGM] adjudicate: made into separate routine, which now can be called after every move
8368 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8369 // Actually ending the game is now based on the additional internal condition canAdjudicate.
8370 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8371 int k, drop, count = 0; static int bare = 1;
8372 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8373 Boolean canAdjudicate = !appData.icsActive;
8375 // most tests only when we understand the game, i.e. legality-checking on
8376 if( appData.testLegality )
8377 { /* [HGM] Some more adjudications for obstinate engines */
8378 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8379 static int moveCount = 6;
8381 char *reason = NULL;
8383 /* Count what is on board. */
8384 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8386 /* Some material-based adjudications that have to be made before stalemate test */
8387 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8388 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8389 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8390 if(canAdjudicate && appData.checkMates) {
8392 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8393 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8394 "Xboard adjudication: King destroyed", GE_XBOARD );
8399 /* Bare King in Shatranj (loses) or Losers (wins) */
8400 if( nrW == 1 || nrB == 1) {
8401 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8402 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
8403 if(canAdjudicate && appData.checkMates) {
8405 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8406 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8407 "Xboard adjudication: Bare king", GE_XBOARD );
8411 if( gameInfo.variant == VariantShatranj && --bare < 0)
8413 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8414 if(canAdjudicate && appData.checkMates) {
8415 /* but only adjudicate if adjudication enabled */
8417 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8418 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8419 "Xboard adjudication: Bare king", GE_XBOARD );
8426 // don't wait for engine to announce game end if we can judge ourselves
8427 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8429 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8430 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
8431 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8432 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8435 reason = "Xboard adjudication: 3rd check";
8436 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8447 reason = "Xboard adjudication: Stalemate";
8448 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8449 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
8450 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8451 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
8452 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8453 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8454 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8455 EP_CHECKMATE : EP_WINS);
8456 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8457 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8461 reason = "Xboard adjudication: Checkmate";
8462 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8463 if(gameInfo.variant == VariantShogi) {
8464 if(forwardMostMove > backwardMostMove
8465 && moveList[forwardMostMove-1][1] == '@'
8466 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8467 reason = "XBoard adjudication: pawn-drop mate";
8468 boards[forwardMostMove][EP_STATUS] = EP_WINS;
8474 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8476 result = GameIsDrawn; break;
8478 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8480 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8484 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8486 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8487 GameEnds( result, reason, GE_XBOARD );
8491 /* Next absolutely insufficient mating material. */
8492 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8493 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8494 { /* includes KBK, KNK, KK of KBKB with like Bishops */
8496 /* always flag draws, for judging claims */
8497 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8499 if(canAdjudicate && appData.materialDraws) {
8500 /* but only adjudicate them if adjudication enabled */
8501 if(engineOpponent) {
8502 SendToProgram("force\n", engineOpponent); // suppress reply
8503 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8505 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8510 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8511 if(gameInfo.variant == VariantXiangqi ?
8512 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8514 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8515 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
8516 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
8517 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8519 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8520 { /* if the first 3 moves do not show a tactical win, declare draw */
8521 if(engineOpponent) {
8522 SendToProgram("force\n", engineOpponent); // suppress reply
8523 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8525 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8528 } else moveCount = 6;
8530 if(gameInfo.variant == VariantMakruk && // Makruk counting rules
8531 (nrW == 1 || nrB == 1 || nr[WhitePawn] + nr[BlackPawn] == 0)) { // which only kick in when pawnless or bare King
8532 int maxcnt, his, mine, c, wom = WhiteOnMove(forwardMostMove);
8533 count = forwardMostMove;
8534 while(count >= backwardMostMove) {
8535 int np = nr[WhitePawn] + nr[BlackPawn];
8536 if(wom) mine = nrW, his = nrB, c = BlackPawn;
8537 else mine = nrB, his = nrW, c = WhitePawn;
8538 if(mine > 1 && np) { count++; break; }
8539 if(mine > 1) maxcnt = 64; else
8540 maxcnt = (nr[WhiteRook+c] > 1 ? 8 : nr[WhiteRook+c] ? 16 : nr[WhiteMan+c] > 1 ? 22 :
8541 nr[WhiteKnight+c] > 1 ? 32 : nr[WhiteMan+c] ? 44 : 64) - his - 1;
8542 while(boards[count][EP_STATUS] != EP_CAPTURE && count > backwardMostMove) count--; // seek previous character
8543 if(count == backwardMostMove) break;
8544 if(forwardMostMove - count >= 2*maxcnt + 1 - (mine == 1)) break;
8545 Count(boards[--count], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8547 if(forwardMostMove - count >= 2*maxcnt + 1 - (mine == 1)) {
8548 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8549 if(canAdjudicate && appData.ruleMoves >= 0) {
8550 GameEnds( GameIsDrawn, "Xboard adjudication: counting rule", GE_XBOARD );
8557 // Repetition draws and 50-move rule can be applied independently of legality testing
8559 /* Check for rep-draws */
8561 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8562 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8563 for(k = forwardMostMove-2;
8564 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8565 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8566 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8569 if(CompareBoards(boards[k], boards[forwardMostMove])) {
8570 /* compare castling rights */
8571 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8572 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8573 rights++; /* King lost rights, while rook still had them */
8574 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8575 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8576 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8577 rights++; /* but at least one rook lost them */
8579 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8580 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8582 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8583 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8584 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8587 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8588 && appData.drawRepeats > 1) {
8589 /* adjudicate after user-specified nr of repeats */
8590 int result = GameIsDrawn;
8591 char *details = "XBoard adjudication: repetition draw";
8592 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8593 // [HGM] xiangqi: check for forbidden perpetuals
8594 int m, ourPerpetual = 1, hisPerpetual = 1;
8595 for(m=forwardMostMove; m>k; m-=2) {
8596 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8597 ourPerpetual = 0; // the current mover did not always check
8598 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8599 hisPerpetual = 0; // the opponent did not always check
8601 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8602 ourPerpetual, hisPerpetual);
8603 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8604 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8605 details = "Xboard adjudication: perpetual checking";
8607 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8608 break; // (or we would have caught him before). Abort repetition-checking loop.
8610 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8611 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8613 details = "Xboard adjudication: repetition";
8615 } else // it must be XQ
8616 // Now check for perpetual chases
8617 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8618 hisPerpetual = PerpetualChase(k, forwardMostMove);
8619 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8620 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8621 static char resdet[MSG_SIZ];
8622 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8624 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8626 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8627 break; // Abort repetition-checking loop.
8629 // if neither of us is checking or chasing all the time, or both are, it is draw
8631 if(engineOpponent) {
8632 SendToProgram("force\n", engineOpponent); // suppress reply
8633 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8635 GameEnds( result, details, GE_XBOARD );
8638 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8639 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8643 /* Now we test for 50-move draws. Determine ply count */
8644 count = forwardMostMove;
8645 /* look for last irreversble move */
8646 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8648 /* if we hit starting position, add initial plies */
8649 if( count == backwardMostMove )
8650 count -= initialRulePlies;
8651 count = forwardMostMove - count;
8652 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8653 // adjust reversible move counter for checks in Xiangqi
8654 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8655 if(i < backwardMostMove) i = backwardMostMove;
8656 while(i <= forwardMostMove) {
8657 lastCheck = inCheck; // check evasion does not count
8658 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8659 if(inCheck || lastCheck) count--; // check does not count
8663 if( count >= 100 && gameInfo.variant != VariantMakruk) // do not accept 50-move claims in Makruk
8664 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8665 /* this is used to judge if draw claims are legal */
8666 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8667 if(engineOpponent) {
8668 SendToProgram("force\n", engineOpponent); // suppress reply
8669 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8671 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8675 /* if draw offer is pending, treat it as a draw claim
8676 * when draw condition present, to allow engines a way to
8677 * claim draws before making their move to avoid a race
8678 * condition occurring after their move
8680 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8682 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8683 p = "Draw claim: 50-move rule";
8684 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8685 p = "Draw claim: 3-fold repetition";
8686 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8687 p = "Draw claim: insufficient mating material";
8688 if( p != NULL && canAdjudicate) {
8689 if(engineOpponent) {
8690 SendToProgram("force\n", engineOpponent); // suppress reply
8691 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8693 GameEnds( GameIsDrawn, p, GE_XBOARD );
8698 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8699 if(engineOpponent) {
8700 SendToProgram("force\n", engineOpponent); // suppress reply
8701 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8703 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8709 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8710 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8711 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8716 int pieces[10], squares[10], cnt=0, r, f, res;
8718 static PPROBE_EGBB probeBB;
8719 if(!appData.testLegality) return 10;
8720 if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8721 if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8722 if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8723 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8724 ChessSquare piece = boards[forwardMostMove][r][f];
8725 int black = (piece >= BlackPawn);
8726 int type = piece - black*BlackPawn;
8727 if(piece == EmptySquare) continue;
8728 if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8729 if(type == WhiteKing) type = WhiteQueen + 1;
8730 type = egbbCode[type];
8731 squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8732 pieces[cnt] = type + black*6;
8733 if(++cnt > 5) return 11;
8735 pieces[cnt] = squares[cnt] = 0;
8737 if(loaded == 2) return 13; // loading failed before
8739 char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8742 loaded = 2; // prepare for failure
8743 if(!path) return 13; // no egbb installed
8744 strncpy(buf, path + 8, MSG_SIZ);
8745 if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8746 snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8747 lib = LoadLibrary(buf);
8748 if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8749 loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8750 probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8751 if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8752 p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8753 loaded = 1; // success!
8755 res = probeBB(forwardMostMove & 1, pieces, squares);
8756 return res > 0 ? 1 : res < 0 ? -1 : 0;
8760 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8761 { // [HGM] book: this routine intercepts moves to simulate book replies
8762 char *bookHit = NULL;
8764 if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8766 snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8767 SendToProgram(buf, cps);
8769 //first determine if the incoming move brings opponent into his book
8770 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8771 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8772 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8773 if(bookHit != NULL && !cps->bookSuspend) {
8774 // make sure opponent is not going to reply after receiving move to book position
8775 SendToProgram("force\n", cps);
8776 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8778 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8779 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8780 // now arrange restart after book miss
8782 // after a book hit we never send 'go', and the code after the call to this routine
8783 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8784 char buf[MSG_SIZ], *move = bookHit;
8786 int fromX, fromY, toX, toY;
8790 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8791 &fromX, &fromY, &toX, &toY, &promoChar)) {
8792 (void) CoordsToAlgebraic(boards[forwardMostMove],
8793 PosFlags(forwardMostMove),
8794 fromY, fromX, toY, toX, promoChar, move);
8796 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8800 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8801 SendToProgram(buf, cps);
8802 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8803 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8804 SendToProgram("go\n", cps);
8805 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8806 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8807 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8808 SendToProgram("go\n", cps);
8809 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8811 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8815 LoadError (char *errmess, ChessProgramState *cps)
8816 { // unloads engine and switches back to -ncp mode if it was first
8817 if(cps->initDone) return FALSE;
8818 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8819 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8822 appData.noChessProgram = TRUE;
8823 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8824 gameMode = BeginningOfGame; ModeHighlight();
8827 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8828 DisplayMessage("", ""); // erase waiting message
8829 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8834 ChessProgramState *savedState;
8836 DeferredBookMove (void)
8838 if(savedState->lastPing != savedState->lastPong)
8839 ScheduleDelayedEvent(DeferredBookMove, 10);
8841 HandleMachineMove(savedMessage, savedState);
8844 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8845 static ChessProgramState *stalledEngine;
8846 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8849 HandleMachineMove (char *message, ChessProgramState *cps)
8851 static char firstLeg[20], legs;
8852 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8853 char realname[MSG_SIZ];
8854 int fromX, fromY, toX, toY;
8856 char promoChar, roar;
8861 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8862 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8863 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8864 DisplayError(_("Invalid pairing from pairing engine"), 0);
8867 pairingReceived = 1;
8869 return; // Skim the pairing messages here.
8872 oldError = cps->userError; cps->userError = 0;
8874 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8876 * Kludge to ignore BEL characters
8878 while (*message == '\007') message++;
8881 * [HGM] engine debug message: ignore lines starting with '#' character
8883 if(cps->debug && *message == '#') return;
8886 * Look for book output
8888 if (cps == &first && bookRequested) {
8889 if (message[0] == '\t' || message[0] == ' ') {
8890 /* Part of the book output is here; append it */
8891 strcat(bookOutput, message);
8892 strcat(bookOutput, " \n");
8894 } else if (bookOutput[0] != NULLCHAR) {
8895 /* All of book output has arrived; display it */
8896 char *p = bookOutput;
8897 while (*p != NULLCHAR) {
8898 if (*p == '\t') *p = ' ';
8901 DisplayInformation(bookOutput);
8902 bookRequested = FALSE;
8903 /* Fall through to parse the current output */
8908 * Look for machine move.
8910 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8911 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8913 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8914 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8915 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8916 stalledEngine = cps;
8917 if(appData.ponderNextMove) { // bring opponent out of ponder
8918 if(gameMode == TwoMachinesPlay) {
8919 if(cps->other->pause)
8920 PauseEngine(cps->other);
8922 SendToProgram("easy\n", cps->other);
8931 /* This method is only useful on engines that support ping */
8932 if(abortEngineThink) {
8933 if (appData.debugMode) {
8934 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8936 SendToProgram("undo\n", cps);
8940 if (cps->lastPing != cps->lastPong) {
8941 /* Extra move from before last new; ignore */
8942 if (appData.debugMode) {
8943 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8950 int machineWhite = FALSE;
8953 case BeginningOfGame:
8954 /* Extra move from before last reset; ignore */
8955 if (appData.debugMode) {
8956 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8963 /* Extra move after we tried to stop. The mode test is
8964 not a reliable way of detecting this problem, but it's
8965 the best we can do on engines that don't support ping.
8967 if (appData.debugMode) {
8968 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8969 cps->which, gameMode);
8971 SendToProgram("undo\n", cps);
8974 case MachinePlaysWhite:
8975 case IcsPlayingWhite:
8976 machineWhite = TRUE;
8979 case MachinePlaysBlack:
8980 case IcsPlayingBlack:
8981 machineWhite = FALSE;
8984 case TwoMachinesPlay:
8985 machineWhite = (cps->twoMachinesColor[0] == 'w');
8988 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8989 if (appData.debugMode) {
8991 "Ignoring move out of turn by %s, gameMode %d"
8992 ", forwardMost %d\n",
8993 cps->which, gameMode, forwardMostMove);
8999 if(cps->alphaRank) AlphaRank(machineMove, 4);
9001 // [HGM] lion: (some very limited) support for Alien protocol
9002 killX = killY = kill2X = kill2Y = -1;
9003 if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
9004 if(legs++) return; // middle leg contains only redundant info, ignore (but count it)
9005 safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
9008 if(p = strchr(machineMove, ',')) { // we got both legs in one (happens on book move)
9009 char *q = strchr(p+1, ','); // second comma?
9010 safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
9011 if(q) legs = 2, p = q; else legs = 1; // with 3-leg move we clipof first two legs!
9012 safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
9014 if(firstLeg[0]) { // there was a previous leg;
9015 // only support case where same piece makes two step
9016 char buf[20], *p = machineMove+1, *q = buf+1, f;
9017 safeStrCpy(buf, machineMove, 20);
9018 while(isdigit(*q)) q++; // find start of to-square
9019 safeStrCpy(machineMove, firstLeg, 20);
9020 while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
9021 if(legs == 2) sscanf(p, "%c%d", &f, &kill2Y), kill2X = f - AAA, kill2Y -= ONE - '0'; // in 3-leg move 2nd kill is to-sqr of 1st leg
9022 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)
9023 safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
9024 sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
9025 firstLeg[0] = NULLCHAR; legs = 0;
9028 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
9029 &fromX, &fromY, &toX, &toY, &promoChar)) {
9030 /* Machine move could not be parsed; ignore it. */
9031 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
9032 machineMove, _(cps->which));
9033 DisplayMoveError(buf1);
9034 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
9035 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
9036 if (gameMode == TwoMachinesPlay) {
9037 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9043 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
9044 /* So we have to redo legality test with true e.p. status here, */
9045 /* to make sure an illegal e.p. capture does not slip through, */
9046 /* to cause a forfeit on a justified illegal-move complaint */
9047 /* of the opponent. */
9048 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
9050 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
9051 fromY, fromX, toY, toX, promoChar);
9052 if(moveType == IllegalMove) {
9053 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
9054 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
9055 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9058 } else if(!appData.fischerCastling)
9059 /* [HGM] Kludge to handle engines that send FRC-style castling
9060 when they shouldn't (like TSCP-Gothic) */
9062 case WhiteASideCastleFR:
9063 case BlackASideCastleFR:
9065 currentMoveString[2]++;
9067 case WhiteHSideCastleFR:
9068 case BlackHSideCastleFR:
9070 currentMoveString[2]--;
9072 default: ; // nothing to do, but suppresses warning of pedantic compilers
9075 hintRequested = FALSE;
9076 lastHint[0] = NULLCHAR;
9077 bookRequested = FALSE;
9078 /* Program may be pondering now */
9079 cps->maybeThinking = TRUE;
9080 if (cps->sendTime == 2) cps->sendTime = 1;
9081 if (cps->offeredDraw) cps->offeredDraw--;
9083 /* [AS] Save move info*/
9084 pvInfoList[ forwardMostMove ].score = programStats.score;
9085 pvInfoList[ forwardMostMove ].depth = programStats.depth;
9086 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
9088 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
9090 /* Test suites abort the 'game' after one move */
9091 if(*appData.finger) {
9093 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
9094 if(!f) f = fopen(appData.finger, "w");
9095 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
9096 else { DisplayFatalError("Bad output file", errno, 0); return; }
9098 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9101 if(solvingTime >= 0) {
9102 snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
9103 totalTime += solvingTime; first.matchWins++; solvingTime = -1;
9105 snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
9106 if(solvingTime == -2) second.matchWins++;
9108 OutputKibitz(2, buf1);
9109 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9112 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
9113 if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
9116 while( count < adjudicateLossPlies ) {
9117 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
9120 score = -score; /* Flip score for winning side */
9123 if( score > appData.adjudicateLossThreshold ) {
9130 if( count >= adjudicateLossPlies ) {
9131 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9133 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
9134 "Xboard adjudication",
9141 if(Adjudicate(cps)) {
9142 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9143 return; // [HGM] adjudicate: for all automatic game ends
9147 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9149 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9150 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9152 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9154 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9156 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9157 char buf[3*MSG_SIZ];
9159 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9160 programStats.score / 100.,
9162 programStats.time / 100.,
9163 (unsigned int)programStats.nodes,
9164 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9165 programStats.movelist);
9171 /* [AS] Clear stats for next move */
9172 ClearProgramStats();
9173 thinkOutput[0] = NULLCHAR;
9174 hiddenThinkOutputState = 0;
9177 if (gameMode == TwoMachinesPlay) {
9178 /* [HGM] relaying draw offers moved to after reception of move */
9179 /* and interpreting offer as claim if it brings draw condition */
9180 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9181 SendToProgram("draw\n", cps->other);
9183 if (cps->other->sendTime) {
9184 SendTimeRemaining(cps->other,
9185 cps->other->twoMachinesColor[0] == 'w');
9187 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9188 if (firstMove && !bookHit) {
9190 if (cps->other->useColors) {
9191 SendToProgram(cps->other->twoMachinesColor, cps->other);
9193 SendToProgram("go\n", cps->other);
9195 cps->other->maybeThinking = TRUE;
9198 roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9200 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9202 if (!pausing && appData.ringBellAfterMoves) {
9203 if(!roar) RingBell();
9207 * Reenable menu items that were disabled while
9208 * machine was thinking
9210 if (gameMode != TwoMachinesPlay)
9211 SetUserThinkingEnables();
9213 // [HGM] book: after book hit opponent has received move and is now in force mode
9214 // force the book reply into it, and then fake that it outputted this move by jumping
9215 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9217 static char bookMove[MSG_SIZ]; // a bit generous?
9219 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9220 strcat(bookMove, bookHit);
9223 programStats.nodes = programStats.depth = programStats.time =
9224 programStats.score = programStats.got_only_move = 0;
9225 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9227 if(cps->lastPing != cps->lastPong) {
9228 savedMessage = message; // args for deferred call
9230 ScheduleDelayedEvent(DeferredBookMove, 10);
9239 /* Set special modes for chess engines. Later something general
9240 * could be added here; for now there is just one kludge feature,
9241 * needed because Crafty 15.10 and earlier don't ignore SIGINT
9242 * when "xboard" is given as an interactive command.
9244 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9245 cps->useSigint = FALSE;
9246 cps->useSigterm = FALSE;
9248 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9249 ParseFeatures(message+8, cps); if(tryNr < 3) tryNr = 3;
9250 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9253 if (!strncmp(message, "setup ", 6) &&
9254 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9255 NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9256 ) { // [HGM] allow first engine to define opening position
9257 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9258 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9260 if(sscanf(message, "setup (%s", buf) == 1) {
9261 s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9262 ASSIGN(appData.pieceToCharTable, buf);
9264 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9266 while(message[s] && message[s++] != ' ');
9267 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9268 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9269 // if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
9270 if(hand > h) handSize = hand; else handSize = h;
9271 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9272 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
9273 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9274 if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9275 startedFromSetupPosition = FALSE;
9278 if(startedFromSetupPosition) return;
9279 ParseFEN(boards[0], &dummy, message+s, FALSE);
9280 DrawPosition(TRUE, boards[0]);
9281 CopyBoard(initialPosition, boards[0]);
9282 startedFromSetupPosition = TRUE;
9285 if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9286 ChessSquare piece = WhitePawn;
9287 char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0;
9288 if(*p == '+') promoted++, ID = *++p;
9289 if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9290 piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece);
9291 if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9292 /* always accept definition of */ && piece != WhiteFalcon && piece != BlackFalcon
9293 /* wild-card pieces. */ && piece != WhiteCobra && piece != BlackCobra
9294 /* For variants we don't have */ && gameInfo.variant != VariantBerolina
9295 /* correct rules for, we cannot */ && gameInfo.variant != VariantCylinder
9296 /* enforce legality on our own! */ && gameInfo.variant != VariantUnknown
9297 && gameInfo.variant != VariantGreat
9298 && gameInfo.variant != VariantFairy ) return;
9299 if(piece < EmptySquare) {
9301 ASSIGN(pieceDesc[piece], buf1);
9302 if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9306 if(!appData.testLegality && sscanf(message, "choice %s", promoRestrict) == 1) {
9308 LeftClick(Press, 0, 0); // finish the click that was interrupted
9309 } else if(promoSweep != EmptySquare) {
9310 promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9311 if(strlen(promoRestrict) > 1) Sweep(0);
9315 /* [HGM] Allow engine to set up a position. Don't ask me why one would
9316 * want this, I was asked to put it in, and obliged.
9318 if (!strncmp(message, "setboard ", 9)) {
9319 Board initial_position;
9321 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9323 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9324 DisplayError(_("Bad FEN received from engine"), 0);
9328 CopyBoard(boards[0], initial_position);
9329 initialRulePlies = FENrulePlies;
9330 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9331 else gameMode = MachinePlaysBlack;
9332 DrawPosition(FALSE, boards[currentMove]);
9338 * Look for communication commands
9340 if (!strncmp(message, "telluser ", 9)) {
9341 if(message[9] == '\\' && message[10] == '\\')
9342 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9344 DisplayNote(message + 9);
9347 if (!strncmp(message, "tellusererror ", 14)) {
9349 if(message[14] == '\\' && message[15] == '\\')
9350 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9352 DisplayError(message + 14, 0);
9355 if (!strncmp(message, "tellopponent ", 13)) {
9356 if (appData.icsActive) {
9358 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9362 DisplayNote(message + 13);
9366 if (!strncmp(message, "tellothers ", 11)) {
9367 if (appData.icsActive) {
9369 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9372 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9375 if (!strncmp(message, "tellall ", 8)) {
9376 if (appData.icsActive) {
9378 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9382 DisplayNote(message + 8);
9386 if (strncmp(message, "warning", 7) == 0) {
9387 /* Undocumented feature, use tellusererror in new code */
9388 DisplayError(message, 0);
9391 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9392 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9393 strcat(realname, " query");
9394 AskQuestion(realname, buf2, buf1, cps->pr);
9397 /* Commands from the engine directly to ICS. We don't allow these to be
9398 * sent until we are logged on. Crafty kibitzes have been known to
9399 * interfere with the login process.
9402 if (!strncmp(message, "tellics ", 8)) {
9403 SendToICS(message + 8);
9407 if (!strncmp(message, "tellicsnoalias ", 15)) {
9408 SendToICS(ics_prefix);
9409 SendToICS(message + 15);
9413 /* The following are for backward compatibility only */
9414 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9415 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9416 SendToICS(ics_prefix);
9422 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9423 if(initPing == cps->lastPong) {
9424 if(gameInfo.variant == VariantUnknown) {
9425 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9426 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9427 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9431 if(cps->lastPing == cps->lastPong && abortEngineThink) {
9432 abortEngineThink = FALSE;
9433 DisplayMessage("", "");
9438 if(!strncmp(message, "highlight ", 10)) {
9439 if(appData.testLegality && !*engineVariant && appData.markers) return;
9440 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9443 if(!strncmp(message, "click ", 6)) {
9444 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9445 if(appData.testLegality || !appData.oneClick) return;
9446 sscanf(message+6, "%c%d%c", &f, &y, &c);
9447 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9448 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9449 x = x*squareSize + (x+1)*lineGap + squareSize/2;
9450 y = y*squareSize + (y+1)*lineGap + squareSize/2;
9451 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9452 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9453 LeftClick(Release, lastLeftX, lastLeftY);
9454 controlKey = (c == ',');
9455 LeftClick(Press, x, y);
9456 LeftClick(Release, x, y);
9457 first.highlight = f;
9460 if(strncmp(message, "uciok", 5) == 0) { // response to "uci" probe
9461 int nr = (cps == &second);
9462 appData.isUCI[nr] = isUCI = 1;
9463 ReplaceEngine(cps, nr); // retry install as UCI
9467 * If the move is illegal, cancel it and redraw the board.
9468 * Also deal with other error cases. Matching is rather loose
9469 * here to accommodate engines written before the spec.
9471 if (strncmp(message + 1, "llegal move", 11) == 0 ||
9472 strncmp(message, "Error", 5) == 0) {
9473 if (StrStr(message, "name") ||
9474 StrStr(message, "rating") || StrStr(message, "?") ||
9475 StrStr(message, "result") || StrStr(message, "board") ||
9476 StrStr(message, "bk") || StrStr(message, "computer") ||
9477 StrStr(message, "variant") || StrStr(message, "hint") ||
9478 StrStr(message, "random") || StrStr(message, "depth") ||
9479 StrStr(message, "accepted")) {
9482 if (StrStr(message, "protover")) {
9483 /* Program is responding to input, so it's apparently done
9484 initializing, and this error message indicates it is
9485 protocol version 1. So we don't need to wait any longer
9486 for it to initialize and send feature commands. */
9487 FeatureDone(cps, 1);
9488 cps->protocolVersion = 1;
9491 cps->maybeThinking = FALSE;
9493 if (StrStr(message, "draw")) {
9494 /* Program doesn't have "draw" command */
9495 cps->sendDrawOffers = 0;
9498 if (cps->sendTime != 1 &&
9499 (StrStr(message, "time") || StrStr(message, "otim"))) {
9500 /* Program apparently doesn't have "time" or "otim" command */
9504 if (StrStr(message, "analyze")) {
9505 cps->analysisSupport = FALSE;
9506 cps->analyzing = FALSE;
9507 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9508 EditGameEvent(); // [HGM] try to preserve loaded game
9509 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9510 DisplayError(buf2, 0);
9513 if (StrStr(message, "(no matching move)st")) {
9514 /* Special kludge for GNU Chess 4 only */
9515 cps->stKludge = TRUE;
9516 SendTimeControl(cps, movesPerSession, timeControl,
9517 timeIncrement, appData.searchDepth,
9521 if (StrStr(message, "(no matching move)sd")) {
9522 /* Special kludge for GNU Chess 4 only */
9523 cps->sdKludge = TRUE;
9524 SendTimeControl(cps, movesPerSession, timeControl,
9525 timeIncrement, appData.searchDepth,
9529 if (!StrStr(message, "llegal")) {
9532 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9533 gameMode == IcsIdle) return;
9534 if (forwardMostMove <= backwardMostMove) return;
9535 if (pausing) PauseEvent();
9536 if(appData.forceIllegal) {
9537 // [HGM] illegal: machine refused move; force position after move into it
9538 SendToProgram("force\n", cps);
9539 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9540 // we have a real problem now, as SendBoard will use the a2a3 kludge
9541 // when black is to move, while there might be nothing on a2 or black
9542 // might already have the move. So send the board as if white has the move.
9543 // But first we must change the stm of the engine, as it refused the last move
9544 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9545 if(WhiteOnMove(forwardMostMove)) {
9546 SendToProgram("a7a6\n", cps); // for the engine black still had the move
9547 SendBoard(cps, forwardMostMove); // kludgeless board
9549 SendToProgram("a2a3\n", cps); // for the engine white still had the move
9550 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9551 SendBoard(cps, forwardMostMove+1); // kludgeless board
9553 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9554 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9555 gameMode == TwoMachinesPlay)
9556 SendToProgram("go\n", cps);
9559 if (gameMode == PlayFromGameFile) {
9560 /* Stop reading this game file */
9561 gameMode = EditGame;
9564 /* [HGM] illegal-move claim should forfeit game when Xboard */
9565 /* only passes fully legal moves */
9566 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9567 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9568 "False illegal-move claim", GE_XBOARD );
9569 return; // do not take back move we tested as valid
9571 currentMove = forwardMostMove-1;
9572 DisplayMove(currentMove-1); /* before DisplayMoveError */
9573 SwitchClocks(forwardMostMove-1); // [HGM] race
9574 DisplayBothClocks();
9575 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9576 parseList[currentMove], _(cps->which));
9577 DisplayMoveError(buf1);
9578 DrawPosition(FALSE, boards[currentMove]);
9580 SetUserThinkingEnables();
9583 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9584 /* Program has a broken "time" command that
9585 outputs a string not ending in newline.
9589 if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9590 if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9591 sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1 ) return;
9595 * If chess program startup fails, exit with an error message.
9596 * Attempts to recover here are futile. [HGM] Well, we try anyway
9598 if ((StrStr(message, "unknown host") != NULL)
9599 || (StrStr(message, "No remote directory") != NULL)
9600 || (StrStr(message, "not found") != NULL)
9601 || (StrStr(message, "No such file") != NULL)
9602 || (StrStr(message, "can't alloc") != NULL)
9603 || (StrStr(message, "Permission denied") != NULL)) {
9605 cps->maybeThinking = FALSE;
9606 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9607 _(cps->which), cps->program, cps->host, message);
9608 RemoveInputSource(cps->isr);
9609 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9610 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9611 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9617 * Look for hint output
9619 if (sscanf(message, "Hint: %s", buf1) == 1) {
9620 if (cps == &first && hintRequested) {
9621 hintRequested = FALSE;
9622 if (ParseOneMove(buf1, forwardMostMove, &moveType,
9623 &fromX, &fromY, &toX, &toY, &promoChar)) {
9624 (void) CoordsToAlgebraic(boards[forwardMostMove],
9625 PosFlags(forwardMostMove),
9626 fromY, fromX, toY, toX, promoChar, buf1);
9627 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9628 DisplayInformation(buf2);
9630 /* Hint move could not be parsed!? */
9631 snprintf(buf2, sizeof(buf2),
9632 _("Illegal hint move \"%s\"\nfrom %s chess program"),
9633 buf1, _(cps->which));
9634 DisplayError(buf2, 0);
9637 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9643 * Ignore other messages if game is not in progress
9645 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9646 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9649 * look for win, lose, draw, or draw offer
9651 if (strncmp(message, "1-0", 3) == 0) {
9652 char *p, *q, *r = "";
9653 p = strchr(message, '{');
9661 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9663 } else if (strncmp(message, "0-1", 3) == 0) {
9664 char *p, *q, *r = "";
9665 p = strchr(message, '{');
9673 /* Kludge for Arasan 4.1 bug */
9674 if (strcmp(r, "Black resigns") == 0) {
9675 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9678 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9680 } else if (strncmp(message, "1/2", 3) == 0) {
9681 char *p, *q, *r = "";
9682 p = strchr(message, '{');
9691 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9694 } else if (strncmp(message, "White resign", 12) == 0) {
9695 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9697 } else if (strncmp(message, "Black resign", 12) == 0) {
9698 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9700 } else if (strncmp(message, "White matches", 13) == 0 ||
9701 strncmp(message, "Black matches", 13) == 0 ) {
9702 /* [HGM] ignore GNUShogi noises */
9704 } else if (strncmp(message, "White", 5) == 0 &&
9705 message[5] != '(' &&
9706 StrStr(message, "Black") == NULL) {
9707 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9709 } else if (strncmp(message, "Black", 5) == 0 &&
9710 message[5] != '(') {
9711 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9713 } else if (strcmp(message, "resign") == 0 ||
9714 strcmp(message, "computer resigns") == 0) {
9716 case MachinePlaysBlack:
9717 case IcsPlayingBlack:
9718 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9720 case MachinePlaysWhite:
9721 case IcsPlayingWhite:
9722 GameEnds(BlackWins, "White resigns", GE_ENGINE);
9724 case TwoMachinesPlay:
9725 if (cps->twoMachinesColor[0] == 'w')
9726 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9728 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9735 } else if (strncmp(message, "opponent mates", 14) == 0) {
9737 case MachinePlaysBlack:
9738 case IcsPlayingBlack:
9739 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9741 case MachinePlaysWhite:
9742 case IcsPlayingWhite:
9743 GameEnds(BlackWins, "Black mates", GE_ENGINE);
9745 case TwoMachinesPlay:
9746 if (cps->twoMachinesColor[0] == 'w')
9747 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9749 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9756 } else if (strncmp(message, "computer mates", 14) == 0) {
9758 case MachinePlaysBlack:
9759 case IcsPlayingBlack:
9760 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9762 case MachinePlaysWhite:
9763 case IcsPlayingWhite:
9764 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9766 case TwoMachinesPlay:
9767 if (cps->twoMachinesColor[0] == 'w')
9768 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9770 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9777 } else if (strncmp(message, "checkmate", 9) == 0) {
9778 if (WhiteOnMove(forwardMostMove)) {
9779 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9781 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9784 } else if (strstr(message, "Draw") != NULL ||
9785 strstr(message, "game is a draw") != NULL) {
9786 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9788 } else if (strstr(message, "offer") != NULL &&
9789 strstr(message, "draw") != NULL) {
9791 if (appData.zippyPlay && first.initDone) {
9792 /* Relay offer to ICS */
9793 SendToICS(ics_prefix);
9794 SendToICS("draw\n");
9797 cps->offeredDraw = 2; /* valid until this engine moves twice */
9798 if (gameMode == TwoMachinesPlay) {
9799 if (cps->other->offeredDraw) {
9800 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9801 /* [HGM] in two-machine mode we delay relaying draw offer */
9802 /* until after we also have move, to see if it is really claim */
9804 } else if (gameMode == MachinePlaysWhite ||
9805 gameMode == MachinePlaysBlack) {
9806 if (userOfferedDraw) {
9807 DisplayInformation(_("Machine accepts your draw offer"));
9808 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9810 DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9817 * Look for thinking output
9819 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9820 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9822 int plylev, mvleft, mvtot, curscore, time;
9823 char mvname[MOVE_LEN];
9827 int prefixHint = FALSE;
9828 mvname[0] = NULLCHAR;
9831 case MachinePlaysBlack:
9832 case IcsPlayingBlack:
9833 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9835 case MachinePlaysWhite:
9836 case IcsPlayingWhite:
9837 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9842 case IcsObserving: /* [DM] icsEngineAnalyze */
9843 if (!appData.icsEngineAnalyze) ignore = TRUE;
9845 case TwoMachinesPlay:
9846 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9856 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9859 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9860 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9861 char score_buf[MSG_SIZ];
9863 if(nodes>>32 == u64Const(0xFFFFFFFF)) // [HGM] negative node count read
9864 nodes += u64Const(0x100000000);
9866 if (plyext != ' ' && plyext != '\t') {
9870 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9871 if( cps->scoreIsAbsolute &&
9872 ( gameMode == MachinePlaysBlack ||
9873 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9874 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9875 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9876 !WhiteOnMove(currentMove)
9879 curscore = -curscore;
9882 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9884 if(*bestMove) { // rememer time best EPD move was first found
9885 int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9886 ChessMove mt; char *p = bestMove;
9887 int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9889 while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9890 if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9891 solvingTime = (solvingTime < 0 ? time : solvingTime);
9895 while(*p && *p != ' ') p++;
9896 while(*p == ' ') p++;
9898 if(!solved) solvingTime = -1;
9900 if(*avoidMove && !solved) {
9901 int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9902 ChessMove mt; char *p = avoidMove, solved = 1;
9903 int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9904 while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9905 if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9906 solved = 0; solvingTime = -2;
9909 while(*p && *p != ' ') p++;
9910 while(*p == ' ') p++;
9912 if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9915 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9918 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9919 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9920 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9921 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9922 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9923 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9927 /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9928 DisplayError(_("failed writing PV"), 0);
9931 tempStats.depth = plylev;
9932 tempStats.nodes = nodes;
9933 tempStats.time = time;
9934 tempStats.score = curscore;
9935 tempStats.got_only_move = 0;
9937 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9940 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9941 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9942 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9943 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9944 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9945 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9946 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9947 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9950 /* Buffer overflow protection */
9951 if (pv[0] != NULLCHAR) {
9952 if (strlen(pv) >= sizeof(tempStats.movelist)
9953 && appData.debugMode) {
9955 "PV is too long; using the first %u bytes.\n",
9956 (unsigned) sizeof(tempStats.movelist) - 1);
9959 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9961 sprintf(tempStats.movelist, " no PV\n");
9964 if (tempStats.seen_stat) {
9965 tempStats.ok_to_send = 1;
9968 if (strchr(tempStats.movelist, '(') != NULL) {
9969 tempStats.line_is_book = 1;
9970 tempStats.nr_moves = 0;
9971 tempStats.moves_left = 0;
9973 tempStats.line_is_book = 0;
9976 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9977 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9979 SendProgramStatsToFrontend( cps, &tempStats );
9982 [AS] Protect the thinkOutput buffer from overflow... this
9983 is only useful if buf1 hasn't overflowed first!
9985 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9986 if(curscore >= MATE_SCORE)
9987 snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9988 else if(curscore <= -MATE_SCORE)
9989 snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9991 snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9992 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9994 (gameMode == TwoMachinesPlay ?
9995 ToUpper(cps->twoMachinesColor[0]) : ' '),
9997 prefixHint ? lastHint : "",
9998 prefixHint ? " " : "" );
10000 if( buf1[0] != NULLCHAR ) {
10001 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
10003 if( strlen(pv) > max_len ) {
10004 if( appData.debugMode) {
10005 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
10007 pv[max_len+1] = '\0';
10010 strcat( thinkOutput, pv);
10013 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
10014 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
10015 DisplayMove(currentMove - 1);
10019 } else if ((p=StrStr(message, "(only move)")) != NULL) {
10020 /* crafty (9.25+) says "(only move) <move>"
10021 * if there is only 1 legal move
10023 sscanf(p, "(only move) %s", buf1);
10024 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
10025 sprintf(programStats.movelist, "%s (only move)", buf1);
10026 programStats.depth = 1;
10027 programStats.nr_moves = 1;
10028 programStats.moves_left = 1;
10029 programStats.nodes = 1;
10030 programStats.time = 1;
10031 programStats.got_only_move = 1;
10033 /* Not really, but we also use this member to
10034 mean "line isn't going to change" (Crafty
10035 isn't searching, so stats won't change) */
10036 programStats.line_is_book = 1;
10038 SendProgramStatsToFrontend( cps, &programStats );
10040 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
10041 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
10042 DisplayMove(currentMove - 1);
10045 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
10046 &time, &nodes, &plylev, &mvleft,
10047 &mvtot, mvname) >= 5) {
10048 /* The stat01: line is from Crafty (9.29+) in response
10049 to the "." command */
10050 programStats.seen_stat = 1;
10051 cps->maybeThinking = TRUE;
10053 if (programStats.got_only_move || !appData.periodicUpdates)
10056 programStats.depth = plylev;
10057 programStats.time = time;
10058 programStats.nodes = nodes;
10059 programStats.moves_left = mvleft;
10060 programStats.nr_moves = mvtot;
10061 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
10062 programStats.ok_to_send = 1;
10063 programStats.movelist[0] = '\0';
10065 SendProgramStatsToFrontend( cps, &programStats );
10069 } else if (strncmp(message,"++",2) == 0) {
10070 /* Crafty 9.29+ outputs this */
10071 programStats.got_fail = 2;
10074 } else if (strncmp(message,"--",2) == 0) {
10075 /* Crafty 9.29+ outputs this */
10076 programStats.got_fail = 1;
10079 } else if (thinkOutput[0] != NULLCHAR &&
10080 strncmp(message, " ", 4) == 0) {
10081 unsigned message_len;
10084 while (*p && *p == ' ') p++;
10086 message_len = strlen( p );
10088 /* [AS] Avoid buffer overflow */
10089 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
10090 strcat(thinkOutput, " ");
10091 strcat(thinkOutput, p);
10094 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
10095 strcat(programStats.movelist, " ");
10096 strcat(programStats.movelist, p);
10099 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
10100 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
10101 DisplayMove(currentMove - 1);
10107 buf1[0] = NULLCHAR;
10109 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
10110 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
10112 ChessProgramStats cpstats;
10114 if (plyext != ' ' && plyext != '\t') {
10118 /* [AS] Negate score if machine is playing black and reporting absolute scores */
10119 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
10120 curscore = -curscore;
10123 cpstats.depth = plylev;
10124 cpstats.nodes = nodes;
10125 cpstats.time = time;
10126 cpstats.score = curscore;
10127 cpstats.got_only_move = 0;
10128 cpstats.movelist[0] = '\0';
10130 if (buf1[0] != NULLCHAR) {
10131 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
10134 cpstats.ok_to_send = 0;
10135 cpstats.line_is_book = 0;
10136 cpstats.nr_moves = 0;
10137 cpstats.moves_left = 0;
10139 SendProgramStatsToFrontend( cps, &cpstats );
10146 /* Parse a game score from the character string "game", and
10147 record it as the history of the current game. The game
10148 score is NOT assumed to start from the standard position.
10149 The display is not updated in any way.
10152 ParseGameHistory (char *game)
10154 ChessMove moveType;
10155 int fromX, fromY, toX, toY, boardIndex, mask;
10160 if (appData.debugMode)
10161 fprintf(debugFP, "Parsing game history: %s\n", game);
10163 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10164 gameInfo.site = StrSave(appData.icsHost);
10165 gameInfo.date = PGNDate();
10166 gameInfo.round = StrSave("-");
10168 /* Parse out names of players */
10169 while (*game == ' ') game++;
10171 while (*game != ' ') *p++ = *game++;
10173 gameInfo.white = StrSave(buf);
10174 while (*game == ' ') game++;
10176 while (*game != ' ' && *game != '\n') *p++ = *game++;
10178 gameInfo.black = StrSave(buf);
10181 boardIndex = blackPlaysFirst ? 1 : 0;
10184 yyboardindex = boardIndex;
10185 moveType = (ChessMove) Myylex();
10186 switch (moveType) {
10187 case IllegalMove: /* maybe suicide chess, etc. */
10188 if (appData.debugMode) {
10189 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10190 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10191 setbuf(debugFP, NULL);
10193 case WhitePromotion:
10194 case BlackPromotion:
10195 case WhiteNonPromotion:
10196 case BlackNonPromotion:
10199 case WhiteCapturesEnPassant:
10200 case BlackCapturesEnPassant:
10201 case WhiteKingSideCastle:
10202 case WhiteQueenSideCastle:
10203 case BlackKingSideCastle:
10204 case BlackQueenSideCastle:
10205 case WhiteKingSideCastleWild:
10206 case WhiteQueenSideCastleWild:
10207 case BlackKingSideCastleWild:
10208 case BlackQueenSideCastleWild:
10210 case WhiteHSideCastleFR:
10211 case WhiteASideCastleFR:
10212 case BlackHSideCastleFR:
10213 case BlackASideCastleFR:
10215 fromX = currentMoveString[0] - AAA;
10216 fromY = currentMoveString[1] - ONE;
10217 toX = currentMoveString[2] - AAA;
10218 toY = currentMoveString[3] - ONE;
10219 promoChar = currentMoveString[4];
10223 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10224 fromX = moveType == WhiteDrop ?
10225 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10226 (int) CharToPiece(ToLower(currentMoveString[0]));
10228 toX = currentMoveString[2] - AAA;
10229 toY = currentMoveString[3] - ONE;
10230 promoChar = NULLCHAR;
10232 case AmbiguousMove:
10234 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10235 if (appData.debugMode) {
10236 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10237 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10238 setbuf(debugFP, NULL);
10240 DisplayError(buf, 0);
10242 case ImpossibleMove:
10244 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10245 if (appData.debugMode) {
10246 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10247 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10248 setbuf(debugFP, NULL);
10250 DisplayError(buf, 0);
10253 if (boardIndex < backwardMostMove) {
10254 /* Oops, gap. How did that happen? */
10255 DisplayError(_("Gap in move list"), 0);
10258 backwardMostMove = blackPlaysFirst ? 1 : 0;
10259 if (boardIndex > forwardMostMove) {
10260 forwardMostMove = boardIndex;
10264 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10265 strcat(parseList[boardIndex-1], " ");
10266 strcat(parseList[boardIndex-1], yy_text);
10278 case GameUnfinished:
10279 if (gameMode == IcsExamining) {
10280 if (boardIndex < backwardMostMove) {
10281 /* Oops, gap. How did that happen? */
10284 backwardMostMove = blackPlaysFirst ? 1 : 0;
10287 gameInfo.result = moveType;
10288 p = strchr(yy_text, '{');
10289 if (p == NULL) p = strchr(yy_text, '(');
10292 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10294 q = strchr(p, *p == '{' ? '}' : ')');
10295 if (q != NULL) *q = NULLCHAR;
10298 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10299 gameInfo.resultDetails = StrSave(p);
10302 if (boardIndex >= forwardMostMove &&
10303 !(gameMode == IcsObserving && ics_gamenum == -1)) {
10304 backwardMostMove = blackPlaysFirst ? 1 : 0;
10307 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10308 fromY, fromX, toY, toX, promoChar,
10309 parseList[boardIndex]);
10310 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10311 /* currentMoveString is set as a side-effect of yylex */
10312 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10313 strcat(moveList[boardIndex], "\n");
10315 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10316 mask = (WhiteOnMove(boardIndex) ? 0xFF : 0xFF00);
10317 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10323 if(boards[boardIndex][CHECK_COUNT]) boards[boardIndex][CHECK_COUNT] -= mask & 0x101;
10324 if(!boards[boardIndex][CHECK_COUNT] || boards[boardIndex][CHECK_COUNT] & mask) {
10325 if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[boardIndex - 1], "+");
10330 strcat(parseList[boardIndex - 1], "#");
10337 /* Apply a move to the given board */
10339 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10341 ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, epFile, lastFile, lastRank, berolina = 0;
10342 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10344 /* [HGM] compute & store e.p. status and castling rights for new position */
10345 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10347 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10348 oldEP = (signed char)board[EP_STATUS]; epRank = board[EP_RANK]; epFile = board[EP_FILE]; lastFile = board[LAST_TO] & 255,lastRank = board[LAST_TO] >> 8;
10349 board[EP_STATUS] = EP_NONE;
10350 board[EP_FILE] = board[EP_RANK] = 100, board[LAST_TO] = 0x4040;
10352 if (fromY == DROP_RANK) {
10353 /* must be first */
10354 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10355 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10358 piece = board[toY][toX] = (ChessSquare) fromX;
10360 // ChessSquare victim;
10363 if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10364 // victim = board[killY][killX],
10365 killed = board[killY][killX],
10366 board[killY][killX] = EmptySquare,
10367 board[EP_STATUS] = EP_CAPTURE;
10368 if( kill2X >= 0 && kill2Y >= 0)
10369 killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10372 if( board[toY][toX] != EmptySquare ) {
10373 board[EP_STATUS] = EP_CAPTURE;
10374 if( (fromX != toX || fromY != toY) && // not igui!
10375 (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10376 captured == BlackLion && board[fromY][fromX] != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules
10377 board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10381 pawn = board[fromY][fromX];
10382 if(pieceDesc[pawn] && strchr(pieceDesc[pawn], 'e')) { // piece with user-defined e.p. capture
10383 if(captured == EmptySquare && toX == epFile && (toY == (epRank & 127) || toY + (pawn < BlackPawn ? -1 : 1) == epRank - 128)) {
10384 captured = board[lastRank][lastFile]; // remove victim
10385 board[lastRank][lastFile] = EmptySquare;
10386 pawn = EmptySquare; // kludge to suppress old e.p. code
10389 if( pawn == WhiteLance || pawn == BlackLance ) {
10390 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10391 if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10392 else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10395 if( pawn == WhitePawn ) {
10396 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10397 board[EP_STATUS] = EP_PAWN_MOVE;
10398 if( toY-fromY>=2) {
10399 board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10400 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
10401 gameInfo.variant != VariantBerolina || toX < fromX)
10402 board[EP_STATUS] = toX | berolina;
10403 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10404 gameInfo.variant != VariantBerolina || toX > fromX)
10405 board[EP_STATUS] = toX;
10406 board[LAST_TO] = toX + 256*toY;
10409 if( pawn == BlackPawn ) {
10410 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10411 board[EP_STATUS] = EP_PAWN_MOVE;
10412 if( toY-fromY<= -2) {
10413 board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10414 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
10415 gameInfo.variant != VariantBerolina || toX < fromX)
10416 board[EP_STATUS] = toX | berolina;
10417 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10418 gameInfo.variant != VariantBerolina || toX > fromX)
10419 board[EP_STATUS] = toX;
10420 board[LAST_TO] = toX + 256*toY;
10424 if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10425 if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10426 if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10427 if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10429 for(i=0; i<nrCastlingRights; i++) {
10430 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10431 board[CASTLING][i] == toX && castlingRank[i] == toY
10432 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10435 if(gameInfo.variant == VariantSChess) { // update virginity
10436 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10437 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10438 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
10439 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
10442 if (fromX == toX && fromY == toY && killX < 0) return;
10444 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10445 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10446 if(gameInfo.variant == VariantKnightmate)
10447 king += (int) WhiteUnicorn - (int) WhiteKing;
10449 if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10450 && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) { // and tramples own
10451 board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10452 board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10453 board[EP_STATUS] = EP_NONE; // capture was fake!
10455 if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10456 board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10457 board[toY][toX] = piece;
10458 board[EP_STATUS] = EP_NONE; // capture was fake!
10460 /* Code added by Tord: */
10461 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10462 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10463 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10464 board[EP_STATUS] = EP_NONE; // capture was fake!
10465 board[fromY][fromX] = EmptySquare;
10466 board[toY][toX] = EmptySquare;
10467 if((toX > fromX) != (piece == WhiteRook)) {
10468 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10470 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10472 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10473 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10474 board[EP_STATUS] = EP_NONE;
10475 board[fromY][fromX] = EmptySquare;
10476 board[toY][toX] = EmptySquare;
10477 if((toX > fromX) != (piece == BlackRook)) {
10478 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10480 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10482 /* End of code added by Tord */
10484 } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10485 board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10486 board[toY][toX] = piece;
10487 } else if (board[fromY][fromX] == king
10488 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10489 && toY == fromY && toX > fromX+1) {
10490 for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10491 ; // castle with nearest piece
10492 board[fromY][toX-1] = board[fromY][rookX];
10493 board[fromY][rookX] = EmptySquare;
10494 board[fromY][fromX] = EmptySquare;
10495 board[toY][toX] = king;
10496 } else if (board[fromY][fromX] == king
10497 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10498 && toY == fromY && toX < fromX-1) {
10499 for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10500 ; // castle with nearest piece
10501 board[fromY][toX+1] = board[fromY][rookX];
10502 board[fromY][rookX] = EmptySquare;
10503 board[fromY][fromX] = EmptySquare;
10504 board[toY][toX] = king;
10505 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10506 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10507 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10509 /* white pawn promotion */
10510 board[toY][toX] = CharToPiece(ToUpper(promoChar));
10511 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10512 board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10513 board[fromY][fromX] = EmptySquare;
10514 } else if ((fromY >= BOARD_HEIGHT>>1)
10515 && (epFile == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10517 && gameInfo.variant != VariantXiangqi
10518 && gameInfo.variant != VariantBerolina
10519 && (pawn == WhitePawn)
10520 && (board[toY][toX] == EmptySquare)) {
10521 if(lastFile == 100) lastFile = (board[fromY][toX] == BlackPawn ? toX : fromX), lastRank = fromY; // assume FIDE e.p. if victim present
10522 board[fromY][fromX] = EmptySquare;
10523 board[toY][toX] = piece;
10524 captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10525 } else if ((fromY == BOARD_HEIGHT-4)
10527 && gameInfo.variant == VariantBerolina
10528 && (board[fromY][fromX] == WhitePawn)
10529 && (board[toY][toX] == EmptySquare)) {
10530 board[fromY][fromX] = EmptySquare;
10531 board[toY][toX] = WhitePawn;
10532 if(oldEP & EP_BEROLIN_A) {
10533 captured = board[fromY][fromX-1];
10534 board[fromY][fromX-1] = EmptySquare;
10535 }else{ captured = board[fromY][fromX+1];
10536 board[fromY][fromX+1] = EmptySquare;
10538 } else if (board[fromY][fromX] == king
10539 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10540 && toY == fromY && toX > fromX+1) {
10541 for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10543 board[fromY][toX-1] = board[fromY][rookX];
10544 board[fromY][rookX] = EmptySquare;
10545 board[fromY][fromX] = EmptySquare;
10546 board[toY][toX] = king;
10547 } else if (board[fromY][fromX] == king
10548 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10549 && toY == fromY && toX < fromX-1) {
10550 for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10552 board[fromY][toX+1] = board[fromY][rookX];
10553 board[fromY][rookX] = EmptySquare;
10554 board[fromY][fromX] = EmptySquare;
10555 board[toY][toX] = king;
10556 } else if (fromY == 7 && fromX == 3
10557 && board[fromY][fromX] == BlackKing
10558 && toY == 7 && toX == 5) {
10559 board[fromY][fromX] = EmptySquare;
10560 board[toY][toX] = BlackKing;
10561 board[fromY][7] = EmptySquare;
10562 board[toY][4] = BlackRook;
10563 } else if (fromY == 7 && fromX == 3
10564 && board[fromY][fromX] == BlackKing
10565 && toY == 7 && toX == 1) {
10566 board[fromY][fromX] = EmptySquare;
10567 board[toY][toX] = BlackKing;
10568 board[fromY][0] = EmptySquare;
10569 board[toY][2] = BlackRook;
10570 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10571 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10572 && toY < promoRank && promoChar
10574 /* black pawn promotion */
10575 board[toY][toX] = CharToPiece(ToLower(promoChar));
10576 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10577 board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10578 board[fromY][fromX] = EmptySquare;
10579 } else if ((fromY < BOARD_HEIGHT>>1)
10580 && (epFile == toX && epRank == toY || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10582 && gameInfo.variant != VariantXiangqi
10583 && gameInfo.variant != VariantBerolina
10584 && (pawn == BlackPawn)
10585 && (board[toY][toX] == EmptySquare)) {
10586 if(lastFile == 100) lastFile = (board[fromY][toX] == WhitePawn ? toX : fromX), lastRank = fromY;
10587 board[fromY][fromX] = EmptySquare;
10588 board[toY][toX] = piece;
10589 captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10590 } else if ((fromY == 3)
10592 && gameInfo.variant == VariantBerolina
10593 && (board[fromY][fromX] == BlackPawn)
10594 && (board[toY][toX] == EmptySquare)) {
10595 board[fromY][fromX] = EmptySquare;
10596 board[toY][toX] = BlackPawn;
10597 if(oldEP & EP_BEROLIN_A) {
10598 captured = board[fromY][fromX-1];
10599 board[fromY][fromX-1] = EmptySquare;
10600 }else{ captured = board[fromY][fromX+1];
10601 board[fromY][fromX+1] = EmptySquare;
10604 ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10605 board[fromY][fromX] = EmptySquare;
10606 board[toY][toX] = piece;
10610 if (gameInfo.holdingsWidth != 0) {
10612 /* !!A lot more code needs to be written to support holdings */
10613 /* [HGM] OK, so I have written it. Holdings are stored in the */
10614 /* penultimate board files, so they are automaticlly stored */
10615 /* in the game history. */
10616 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10617 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10618 /* Delete from holdings, by decreasing count */
10619 /* and erasing image if necessary */
10620 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10621 if(p < (int) BlackPawn) { /* white drop */
10622 p -= (int)WhitePawn;
10623 p = PieceToNumber((ChessSquare)p);
10624 if(p >= gameInfo.holdingsSize) p = 0;
10625 if(--board[p][BOARD_WIDTH-2] <= 0)
10626 board[p][BOARD_WIDTH-1] = EmptySquare;
10627 if((int)board[p][BOARD_WIDTH-2] < 0)
10628 board[p][BOARD_WIDTH-2] = 0;
10629 } else { /* black drop */
10630 p -= (int)BlackPawn;
10631 p = PieceToNumber((ChessSquare)p);
10632 if(p >= gameInfo.holdingsSize) p = 0;
10633 if(--board[handSize-1-p][1] <= 0)
10634 board[handSize-1-p][0] = EmptySquare;
10635 if((int)board[handSize-1-p][1] < 0)
10636 board[handSize-1-p][1] = 0;
10639 if (captured != EmptySquare && gameInfo.holdingsSize > 0
10640 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
10641 /* [HGM] holdings: Add to holdings, if holdings exist */
10642 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10643 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10644 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10646 p = (int) captured;
10647 if (p >= (int) BlackPawn) {
10648 p -= (int)BlackPawn;
10649 if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10650 /* Restore shogi-promoted piece to its original first */
10651 captured = (ChessSquare) (DEMOTED(captured));
10654 p = PieceToNumber((ChessSquare)p);
10655 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10656 board[p][BOARD_WIDTH-2]++;
10657 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10659 p -= (int)WhitePawn;
10660 if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10661 captured = (ChessSquare) (DEMOTED(captured));
10664 p = PieceToNumber((ChessSquare)p);
10665 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10666 board[handSize-1-p][1]++;
10667 board[handSize-1-p][0] = WHITE_TO_BLACK captured;
10670 } else if (gameInfo.variant == VariantAtomic) {
10671 if (captured != EmptySquare) {
10673 for (y = toY-1; y <= toY+1; y++) {
10674 for (x = toX-1; x <= toX+1; x++) {
10675 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10676 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10677 board[y][x] = EmptySquare;
10681 board[toY][toX] = EmptySquare;
10685 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10686 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10688 if(promoChar == '+') {
10689 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10690 board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10691 if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10692 board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10693 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10694 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10695 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10696 && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10697 board[toY][toX] = newPiece;
10699 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10700 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10701 // [HGM] superchess: take promotion piece out of holdings
10702 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10703 if((int)piece < (int)BlackPawn) { // determine stm from piece color
10704 if(!--board[k][BOARD_WIDTH-2])
10705 board[k][BOARD_WIDTH-1] = EmptySquare;
10707 if(!--board[handSize-1-k][1])
10708 board[handSize-1-k][0] = EmptySquare;
10713 /* Updates forwardMostMove */
10715 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10717 int x = toX, y = toY, mask;
10718 char *s = parseList[forwardMostMove];
10719 ChessSquare p = boards[forwardMostMove][toY][toX];
10720 // forwardMostMove++; // [HGM] bare: moved downstream
10722 if(kill2X >= 0) x = kill2X, y = kill2Y; else
10723 if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10724 (void) CoordsToAlgebraic(boards[forwardMostMove],
10725 PosFlags(forwardMostMove),
10726 fromY, fromX, y, x, (killX < 0)*promoChar,
10728 if(kill2X >= 0 && kill2Y >= 0)
10729 sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10730 if(killX >= 0 && killY >= 0)
10731 sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10732 toX + AAA, toY + ONE - '0', promoChar);
10734 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10735 int timeLeft; static int lastLoadFlag=0; int king, piece;
10736 piece = boards[forwardMostMove][fromY][fromX];
10737 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10738 if(gameInfo.variant == VariantKnightmate)
10739 king += (int) WhiteUnicorn - (int) WhiteKing;
10740 if(forwardMostMove == 0) {
10741 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10742 fprintf(serverMoves, "%s;", UserName());
10743 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10744 fprintf(serverMoves, "%s;", second.tidy);
10745 fprintf(serverMoves, "%s;", first.tidy);
10746 if(gameMode == MachinePlaysWhite)
10747 fprintf(serverMoves, "%s;", UserName());
10748 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10749 fprintf(serverMoves, "%s;", second.tidy);
10750 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10751 lastLoadFlag = loadFlag;
10753 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10754 // print castling suffix
10755 if( toY == fromY && piece == king ) {
10757 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10759 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10762 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10763 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
10764 boards[forwardMostMove][toY][toX] == EmptySquare
10765 && fromX != toX && fromY != toY)
10766 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10767 // promotion suffix
10768 if(promoChar != NULLCHAR) {
10769 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10770 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10771 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10772 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10775 char buf[MOVE_LEN*2], *p; int len;
10776 fprintf(serverMoves, "/%d/%d",
10777 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10778 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10779 else timeLeft = blackTimeRemaining/1000;
10780 fprintf(serverMoves, "/%d", timeLeft);
10781 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10782 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10783 if(p = strchr(buf, '=')) *p = NULLCHAR;
10784 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10785 fprintf(serverMoves, "/%s", buf);
10787 fflush(serverMoves);
10790 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10791 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10794 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10795 if (commentList[forwardMostMove+1] != NULL) {
10796 free(commentList[forwardMostMove+1]);
10797 commentList[forwardMostMove+1] = NULL;
10799 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10800 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10801 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10802 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10803 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10804 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10805 adjustedClock = FALSE;
10806 gameInfo.result = GameUnfinished;
10807 if (gameInfo.resultDetails != NULL) {
10808 free(gameInfo.resultDetails);
10809 gameInfo.resultDetails = NULL;
10811 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10812 moveList[forwardMostMove - 1]);
10813 mask = (WhiteOnMove(forwardMostMove) ? 0xFF : 0xFF00);
10814 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10820 if(boards[forwardMostMove][CHECK_COUNT]) boards[forwardMostMove][CHECK_COUNT] -= mask & 0x101;
10821 if(!boards[forwardMostMove][CHECK_COUNT] || boards[forwardMostMove][CHECK_COUNT] & mask) {
10822 if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[forwardMostMove - 1], "+");
10827 strcat(parseList[forwardMostMove - 1], "#");
10832 /* Updates currentMove if not pausing */
10834 ShowMove (int fromX, int fromY, int toX, int toY)
10836 int instant = (gameMode == PlayFromGameFile) ?
10837 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10838 if(appData.noGUI) return;
10839 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10841 if (forwardMostMove == currentMove + 1) {
10842 AnimateMove(boards[forwardMostMove - 1],
10843 fromX, fromY, toX, toY);
10846 currentMove = forwardMostMove;
10849 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10851 if (instant) return;
10853 DisplayMove(currentMove - 1);
10854 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10855 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10856 SetHighlights(fromX, fromY, toX, toY);
10859 DrawPosition(FALSE, boards[currentMove]);
10860 DisplayBothClocks();
10861 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10865 SendEgtPath (ChessProgramState *cps)
10866 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10867 char buf[MSG_SIZ], name[MSG_SIZ], *p;
10869 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10872 char c, *q = name+1, *r, *s;
10874 name[0] = ','; // extract next format name from feature and copy with prefixed ','
10875 while(*p && *p != ',') *q++ = *p++;
10876 *q++ = ':'; *q = 0;
10877 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10878 strcmp(name, ",nalimov:") == 0 ) {
10879 // take nalimov path from the menu-changeable option first, if it is defined
10880 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10881 SendToProgram(buf,cps); // send egtbpath command for nalimov
10883 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10884 (s = StrStr(appData.egtFormats, name)) != NULL) {
10885 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10886 s = r = StrStr(s, ":") + 1; // beginning of path info
10887 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10888 c = *r; *r = 0; // temporarily null-terminate path info
10889 *--q = 0; // strip of trailig ':' from name
10890 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10892 SendToProgram(buf,cps); // send egtbpath command for this format
10894 if(*p == ',') p++; // read away comma to position for next format name
10899 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10901 int width = 8, height = 8, holdings = 0; // most common sizes
10902 if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10903 // correct the deviations default for each variant
10904 if( v == VariantXiangqi ) width = 9, height = 10;
10905 if( v == VariantShogi ) width = 9, height = 9, holdings = 7;
10906 if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10907 if( v == VariantCapablanca || v == VariantCapaRandom ||
10908 v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10910 if( v == VariantCourier ) width = 12;
10911 if( v == VariantSuper ) holdings = 8;
10912 if( v == VariantGreat ) width = 10, holdings = 8;
10913 if( v == VariantSChess ) holdings = 7;
10914 if( v == VariantGrand ) width = 10, height = 10, holdings = 7;
10915 if( v == VariantChuChess) width = 10, height = 10;
10916 if( v == VariantChu ) width = 12, height = 12;
10917 return boardWidth >= 0 && boardWidth != width || // -1 is default,
10918 boardHeight >= 0 && boardHeight != height || // and thus by definition OK
10919 holdingsSize >= 0 && holdingsSize != holdings;
10922 char variantError[MSG_SIZ];
10925 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10926 { // returns error message (recognizable by upper-case) if engine does not support the variant
10927 char *p, *variant = VariantName(v);
10928 static char b[MSG_SIZ];
10929 if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10930 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10931 holdingsSize, variant); // cook up sized variant name
10932 /* [HGM] varsize: try first if this deviant size variant is specifically known */
10933 if(StrStr(list, b) == NULL) {
10934 // specific sized variant not known, check if general sizing allowed
10935 if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10936 snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10937 boardWidth, boardHeight, holdingsSize, engine);
10940 /* [HGM] here we really should compare with the maximum supported board size */
10942 } else snprintf(b, MSG_SIZ,"%s", variant);
10943 if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10944 p = StrStr(list, b);
10945 while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10947 // occurs not at all in list, or only as sub-string
10948 snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10949 if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10950 int l = strlen(variantError);
10952 while(p != list && p[-1] != ',') p--;
10953 q = strchr(p, ',');
10954 if(q) *q = NULLCHAR;
10955 snprintf(variantError + l, MSG_SIZ - l, _(", but %s is"), p);
10964 InitChessProgram (ChessProgramState *cps, int setup)
10965 /* setup needed to setup FRC opening position */
10967 char buf[MSG_SIZ], *b;
10968 if (appData.noChessProgram) return;
10969 hintRequested = FALSE;
10970 bookRequested = FALSE;
10972 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10973 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10974 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10975 if(cps->memSize) { /* [HGM] memory */
10976 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10977 SendToProgram(buf, cps);
10979 SendEgtPath(cps); /* [HGM] EGT */
10980 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10981 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10982 SendToProgram(buf, cps);
10985 setboardSpoiledMachineBlack = FALSE;
10986 SendToProgram(cps->initString, cps);
10987 if (gameInfo.variant != VariantNormal &&
10988 gameInfo.variant != VariantLoadable
10989 /* [HGM] also send variant if board size non-standard */
10990 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10992 b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10993 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10997 char c, *q = cps->variants, *p = strchr(q, ',');
10998 if(p) *p = NULLCHAR;
10999 v = StringToVariant(q);
11000 DisplayError(variantError, 0);
11001 if(v != VariantUnknown && cps == &first) {
11003 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
11004 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
11005 ASSIGN(appData.variant, q);
11006 Reset(TRUE, FALSE);
11012 snprintf(buf, MSG_SIZ, "variant %s\n", b);
11013 SendToProgram(buf, cps);
11015 currentlyInitializedVariant = gameInfo.variant;
11017 /* [HGM] send opening position in FRC to first engine */
11019 SendToProgram("force\n", cps);
11021 /* engine is now in force mode! Set flag to wake it up after first move. */
11022 setboardSpoiledMachineBlack = 1;
11025 if (cps->sendICS) {
11026 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
11027 SendToProgram(buf, cps);
11029 cps->maybeThinking = FALSE;
11030 cps->offeredDraw = 0;
11031 if (!appData.icsActive) {
11032 SendTimeControl(cps, movesPerSession, timeControl,
11033 timeIncrement, appData.searchDepth,
11036 if (appData.showThinking
11037 // [HGM] thinking: four options require thinking output to be sent
11038 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
11040 SendToProgram("post\n", cps);
11042 SendToProgram("hard\n", cps);
11043 if (!appData.ponderNextMove) {
11044 /* Warning: "easy" is a toggle in GNU Chess, so don't send
11045 it without being sure what state we are in first. "hard"
11046 is not a toggle, so that one is OK.
11048 SendToProgram("easy\n", cps);
11050 if (cps->usePing) {
11051 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
11052 SendToProgram(buf, cps);
11054 cps->initDone = TRUE;
11055 ClearEngineOutputPane(cps == &second);
11060 ResendOptions (ChessProgramState *cps, int toEngine)
11061 { // send the stored value of the options
11063 static char buf2[MSG_SIZ*10];
11064 char buf[MSG_SIZ], *p = buf2;
11065 Option *opt = cps->option;
11067 for(i=0; i<cps->nrOptions; i++, opt++) {
11069 switch(opt->type) {
11073 if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
11074 snprintf(buf, MSG_SIZ, "%s=%d", opt->name, opt->value);
11077 if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
11078 snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->choice[opt->value]);
11081 if(strcmp(opt->textValue, opt->name + MSG_SIZ - 100))
11082 snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->textValue);
11090 snprintf(buf2, MSG_SIZ, "option %s\n", buf);
11091 SendToProgram(buf2, cps);
11093 if(p != buf2) *p++ = ',';
11094 strncpy(p, buf, 10*MSG_SIZ-1 - (p - buf2));
11103 StartChessProgram (ChessProgramState *cps)
11108 if (appData.noChessProgram) return;
11109 cps->initDone = FALSE;
11111 if (strcmp(cps->host, "localhost") == 0) {
11112 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
11113 } else if (*appData.remoteShell == NULLCHAR) {
11114 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
11116 if (*appData.remoteUser == NULLCHAR) {
11117 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
11120 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
11121 cps->host, appData.remoteUser, cps->program);
11123 err = StartChildProcess(buf, "", &cps->pr);
11127 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
11128 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
11129 if(cps != &first) return;
11130 appData.noChessProgram = TRUE;
11133 // DisplayFatalError(buf, err, 1);
11134 // cps->pr = NoProc;
11135 // cps->isr = NULL;
11139 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
11140 if (cps->protocolVersion > 1) {
11141 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
11142 if(!cps->reload) { // do not clear options when reloading because of -xreuse
11143 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
11144 cps->comboCnt = 0; // and values of combo boxes
11146 SendToProgram(buf, cps);
11147 if(cps->reload) ResendOptions(cps, TRUE);
11149 SendToProgram("xboard\n", cps);
11154 TwoMachinesEventIfReady P((void))
11156 static int curMess = 0;
11157 if (first.lastPing != first.lastPong) {
11158 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
11159 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11162 if (second.lastPing != second.lastPong) {
11163 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
11164 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11167 DisplayMessage("", ""); curMess = 0;
11168 TwoMachinesEvent();
11172 MakeName (char *template)
11176 static char buf[MSG_SIZ];
11180 clock = time((time_t *)NULL);
11181 tm = localtime(&clock);
11183 while(*p++ = *template++) if(p[-1] == '%') {
11184 switch(*template++) {
11185 case 0: *p = 0; return buf;
11186 case 'Y': i = tm->tm_year+1900; break;
11187 case 'y': i = tm->tm_year-100; break;
11188 case 'M': i = tm->tm_mon+1; break;
11189 case 'd': i = tm->tm_mday; break;
11190 case 'h': i = tm->tm_hour; break;
11191 case 'm': i = tm->tm_min; break;
11192 case 's': i = tm->tm_sec; break;
11195 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11201 CountPlayers (char *p)
11204 while(p = strchr(p, '\n')) p++, n++; // count participants
11209 WriteTourneyFile (char *results, FILE *f)
11210 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11211 if(f == NULL) f = fopen(appData.tourneyFile, "w");
11212 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11213 // create a file with tournament description
11214 fprintf(f, "-participants {%s}\n", appData.participants);
11215 fprintf(f, "-seedBase %d\n", appData.seedBase);
11216 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11217 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11218 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11219 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11220 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11221 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11222 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11223 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11224 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11225 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11226 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11227 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11228 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11229 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11230 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11231 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11232 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11233 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11234 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11235 fprintf(f, "-smpCores %d\n", appData.smpCores);
11237 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11239 fprintf(f, "-mps %d\n", appData.movesPerSession);
11240 fprintf(f, "-tc %s\n", appData.timeControl);
11241 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11243 fprintf(f, "-results \"%s\"\n", results);
11248 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11251 Substitute (char *participants, int expunge)
11253 int i, changed, changes=0, nPlayers=0;
11254 char *p, *q, *r, buf[MSG_SIZ];
11255 if(participants == NULL) return;
11256 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11257 r = p = participants; q = appData.participants;
11258 while(*p && *p == *q) {
11259 if(*p == '\n') r = p+1, nPlayers++;
11262 if(*p) { // difference
11263 while(*p && *p++ != '\n')
11265 while(*q && *q++ != '\n')
11267 changed = nPlayers;
11268 changes = 1 + (strcmp(p, q) != 0);
11270 if(changes == 1) { // a single engine mnemonic was changed
11271 q = r; while(*q) nPlayers += (*q++ == '\n');
11272 p = buf; while(*r && (*p = *r++) != '\n') p++;
11274 NamesToList(firstChessProgramNames, command, mnemonic, "all");
11275 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11276 if(mnemonic[i]) { // The substitute is valid
11278 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11279 flock(fileno(f), LOCK_EX);
11280 ParseArgsFromFile(f);
11281 fseek(f, 0, SEEK_SET);
11282 FREE(appData.participants); appData.participants = participants;
11283 if(expunge) { // erase results of replaced engine
11284 int len = strlen(appData.results), w, b, dummy;
11285 for(i=0; i<len; i++) {
11286 Pairing(i, nPlayers, &w, &b, &dummy);
11287 if((w == changed || b == changed) && appData.results[i] == '*') {
11288 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11293 for(i=0; i<len; i++) {
11294 Pairing(i, nPlayers, &w, &b, &dummy);
11295 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11298 WriteTourneyFile(appData.results, f);
11299 fclose(f); // release lock
11302 } else DisplayError(_("No engine with the name you gave is installed"), 0);
11304 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11305 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
11306 free(participants);
11311 CheckPlayers (char *participants)
11314 char buf[MSG_SIZ], *p;
11315 NamesToList(firstChessProgramNames, command, mnemonic, "all");
11316 while(p = strchr(participants, '\n')) {
11318 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11320 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11322 DisplayError(buf, 0);
11326 participants = p + 1;
11332 CreateTourney (char *name)
11335 if(matchMode && strcmp(name, appData.tourneyFile)) {
11336 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11338 if(name[0] == NULLCHAR) {
11339 if(appData.participants[0])
11340 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11343 f = fopen(name, "r");
11344 if(f) { // file exists
11345 ASSIGN(appData.tourneyFile, name);
11346 ParseArgsFromFile(f); // parse it
11348 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11349 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11350 DisplayError(_("Not enough participants"), 0);
11353 if(CheckPlayers(appData.participants)) return 0;
11354 ASSIGN(appData.tourneyFile, name);
11355 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11356 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11359 appData.noChessProgram = FALSE;
11360 appData.clockMode = TRUE;
11366 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11368 char buf[2*MSG_SIZ], *p, *q;
11369 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11370 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11371 skip = !all && group[0]; // if group requested, we start in skip mode
11372 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11373 p = names; q = buf; header = 0;
11374 while(*p && *p != '\n') *q++ = *p++;
11376 if(*p == '\n') p++;
11377 if(buf[0] == '#') {
11378 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11379 depth++; // we must be entering a new group
11380 if(all) continue; // suppress printing group headers when complete list requested
11382 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11384 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11385 if(engineList[i]) free(engineList[i]);
11386 engineList[i] = strdup(buf);
11387 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11388 if(engineMnemonic[i]) free(engineMnemonic[i]);
11389 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11391 sscanf(q + 8, "%s", buf + strlen(buf));
11394 engineMnemonic[i] = strdup(buf);
11397 engineList[i] = engineMnemonic[i] = NULL;
11402 SaveEngineSettings (int n)
11404 int len; char *p, *q, *s, buf[MSG_SIZ], *optionSettings;
11405 if(!currentEngine[n] || !currentEngine[n][0]) { DisplayMessage("saving failed: engine not from list", ""); return; } // no engine from list is loaded
11406 if(*engineListFile) ParseSettingsFile(engineListFile, &engineListFile); // update engine list
11407 p = strstr(firstChessProgramNames, currentEngine[n]);
11408 if(!p) { DisplayMessage("saving failed: engine not found in list", ""); return; } // sanity check; engine could be deleted from list after loading
11409 optionSettings = ResendOptions(n ? &second : &first, FALSE);
11410 len = strlen(currentEngine[n]);
11411 q = p + len; *p = 0; // cut list into head and tail piece
11412 s = strstr(currentEngine[n], "firstOptions");
11413 if(s && (s[-1] == '-' || s[-1] == '/') && (s[12] == ' ' || s[12] == '=') && (s[13] == '"' || s[13] == '\'')) {
11415 while(*r && *r != s[13]) r++;
11416 s[14] = 0; // cut currentEngine into head and tail part, removing old settings
11417 snprintf(buf, MSG_SIZ, "%s%s%s", currentEngine[n], optionSettings, *r ? r : "\""); // synthesize new engine line
11418 } else if(*optionSettings) {
11419 snprintf(buf, MSG_SIZ, "%s -firstOptions \"%s\"", currentEngine[n], optionSettings);
11421 ASSIGN(currentEngine[n], buf); // updated engine line
11422 len = p - firstChessProgramNames + strlen(q) + strlen(currentEngine[n]) + 1;
11424 snprintf(s, len, "%s%s%s", firstChessProgramNames, currentEngine[n], q);
11425 FREE(firstChessProgramNames); firstChessProgramNames = s; // new list
11426 if(*engineListFile) SaveEngineList();
11429 // following implemented as macro to avoid type limitations
11430 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11433 SwapEngines (int n)
11434 { // swap settings for first engine and other engine (so far only some selected options)
11439 SWAP(chessProgram, p)
11441 SWAP(hasOwnBookUCI, h)
11442 SWAP(protocolVersion, h)
11444 SWAP(scoreIsAbsolute, h)
11449 SWAP(engOptions, p)
11450 SWAP(engInitString, p)
11451 SWAP(computerString, p)
11453 SWAP(fenOverride, p)
11455 SWAP(accumulateTC, h)
11462 GetEngineLine (char *s, int n)
11466 extern char *icsNames;
11467 if(!s || !*s) return 0;
11468 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11469 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11470 if(!mnemonic[i]) return 0;
11471 if(n == 11) return 1; // just testing if there was a match
11472 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11473 if(n == 1) SwapEngines(n);
11474 ParseArgsFromString(buf);
11475 if(n == 1) SwapEngines(n);
11476 if(n < 2) { ASSIGN(currentEngine[n], command[i]); }
11477 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11478 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11479 ParseArgsFromString(buf);
11485 SetPlayer (int player, char *p)
11486 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
11488 char buf[MSG_SIZ], *engineName;
11489 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11490 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11491 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11493 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11494 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11495 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11496 ParseArgsFromString(buf);
11497 } else { // no engine with this nickname is installed!
11498 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11499 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11500 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11502 DisplayError(buf, 0);
11509 char *recentEngines;
11512 RecentEngineEvent (int nr)
11515 // SwapEngines(1); // bump first to second
11516 // ReplaceEngine(&second, 1); // and load it there
11517 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11518 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11519 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11520 ReplaceEngine(&first, 0);
11521 FloatToFront(&appData.recentEngineList, command[n]);
11522 ASSIGN(currentEngine[0], command[n]);
11527 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11528 { // determine players from game number
11529 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11531 if(appData.tourneyType == 0) {
11532 roundsPerCycle = (nPlayers - 1) | 1;
11533 pairingsPerRound = nPlayers / 2;
11534 } else if(appData.tourneyType > 0) {
11535 roundsPerCycle = nPlayers - appData.tourneyType;
11536 pairingsPerRound = appData.tourneyType;
11538 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11539 gamesPerCycle = gamesPerRound * roundsPerCycle;
11540 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11541 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11542 curRound = nr / gamesPerRound; nr %= gamesPerRound;
11543 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11544 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11545 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11547 if(appData.cycleSync) *syncInterval = gamesPerCycle;
11548 if(appData.roundSync) *syncInterval = gamesPerRound;
11550 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11552 if(appData.tourneyType == 0) {
11553 if(curPairing == (nPlayers-1)/2 ) {
11554 *whitePlayer = curRound;
11555 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11557 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11558 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11559 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11560 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11562 } else if(appData.tourneyType > 1) {
11563 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11564 *whitePlayer = curRound + appData.tourneyType;
11565 } else if(appData.tourneyType > 0) {
11566 *whitePlayer = curPairing;
11567 *blackPlayer = curRound + appData.tourneyType;
11570 // take care of white/black alternation per round.
11571 // For cycles and games this is already taken care of by default, derived from matchGame!
11572 return curRound & 1;
11576 NextTourneyGame (int nr, int *swapColors)
11577 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11579 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11581 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11582 tf = fopen(appData.tourneyFile, "r");
11583 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11584 ParseArgsFromFile(tf); fclose(tf);
11585 InitTimeControls(); // TC might be altered from tourney file
11587 nPlayers = CountPlayers(appData.participants); // count participants
11588 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11589 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11592 p = q = appData.results;
11593 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11594 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11595 DisplayMessage(_("Waiting for other game(s)"),"");
11596 waitingForGame = TRUE;
11597 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11600 waitingForGame = FALSE;
11603 if(appData.tourneyType < 0) {
11604 if(nr>=0 && !pairingReceived) {
11606 if(pairing.pr == NoProc) {
11607 if(!appData.pairingEngine[0]) {
11608 DisplayFatalError(_("No pairing engine specified"), 0, 1);
11611 StartChessProgram(&pairing); // starts the pairing engine
11613 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11614 SendToProgram(buf, &pairing);
11615 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11616 SendToProgram(buf, &pairing);
11617 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11619 pairingReceived = 0; // ... so we continue here
11621 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11622 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11623 matchGame = 1; roundNr = nr / syncInterval + 1;
11626 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11628 // redefine engines, engine dir, etc.
11629 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11630 if(first.pr == NoProc) {
11631 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11632 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
11634 if(second.pr == NoProc) {
11636 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11637 SwapEngines(1); // and make that valid for second engine by swapping
11638 InitEngine(&second, 1);
11640 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
11641 UpdateLogos(FALSE); // leave display to ModeHiglight()
11647 { // performs game initialization that does not invoke engines, and then tries to start the game
11648 int res, firstWhite, swapColors = 0;
11649 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11650 if(matchMode && appData.debugMode) { // [HGM] debug split: game is part of a match; we might have to create a debug file just for this game
11652 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11653 if(strcmp(buf, currentDebugFile)) { // name has changed
11654 FILE *f = fopen(buf, "w");
11655 if(f) { // if opening the new file failed, just keep using the old one
11656 ASSIGN(currentDebugFile, buf);
11660 if(appData.serverFileName) {
11661 if(serverFP) fclose(serverFP);
11662 serverFP = fopen(appData.serverFileName, "w");
11663 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11664 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11668 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11669 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11670 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
11671 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11672 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11673 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11674 Reset(FALSE, first.pr != NoProc);
11675 res = LoadGameOrPosition(matchGame); // setup game
11676 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11677 if(!res) return; // abort when bad game/pos file
11678 if(appData.epd) {// in EPD mode we make sure first engine is to move
11679 firstWhite = !(forwardMostMove & 1);
11680 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
11681 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11683 TwoMachinesEvent();
11687 UserAdjudicationEvent (int result)
11689 ChessMove gameResult = GameIsDrawn;
11692 gameResult = WhiteWins;
11694 else if( result < 0 ) {
11695 gameResult = BlackWins;
11698 if( gameMode == TwoMachinesPlay ) {
11699 GameEnds( gameResult, "User adjudication", GE_XBOARD );
11704 // [HGM] save: calculate checksum of game to make games easily identifiable
11706 StringCheckSum (char *s)
11709 if(s==NULL) return 0;
11710 while(*s) i = i*259 + *s++;
11718 for(i=backwardMostMove; i<forwardMostMove; i++) {
11719 sum += pvInfoList[i].depth;
11720 sum += StringCheckSum(parseList[i]);
11721 sum += StringCheckSum(commentList[i]);
11724 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11725 return sum + StringCheckSum(commentList[i]);
11726 } // end of save patch
11729 GameEnds (ChessMove result, char *resultDetails, int whosays)
11731 GameMode nextGameMode;
11733 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11735 if(endingGame) return; /* [HGM] crash: forbid recursion */
11737 if(twoBoards) { // [HGM] dual: switch back to one board
11738 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11739 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11741 if (appData.debugMode) {
11742 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11743 result, resultDetails ? resultDetails : "(null)", whosays);
11746 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11748 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11750 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11751 /* If we are playing on ICS, the server decides when the
11752 game is over, but the engine can offer to draw, claim
11756 if (appData.zippyPlay && first.initDone) {
11757 if (result == GameIsDrawn) {
11758 /* In case draw still needs to be claimed */
11759 SendToICS(ics_prefix);
11760 SendToICS("draw\n");
11761 } else if (StrCaseStr(resultDetails, "resign")) {
11762 SendToICS(ics_prefix);
11763 SendToICS("resign\n");
11767 endingGame = 0; /* [HGM] crash */
11771 /* If we're loading the game from a file, stop */
11772 if (whosays == GE_FILE) {
11773 (void) StopLoadGameTimer();
11777 /* Cancel draw offers */
11778 first.offeredDraw = second.offeredDraw = 0;
11780 /* If this is an ICS game, only ICS can really say it's done;
11781 if not, anyone can. */
11782 isIcsGame = (gameMode == IcsPlayingWhite ||
11783 gameMode == IcsPlayingBlack ||
11784 gameMode == IcsObserving ||
11785 gameMode == IcsExamining);
11787 if (!isIcsGame || whosays == GE_ICS) {
11788 /* OK -- not an ICS game, or ICS said it was done */
11790 if (!isIcsGame && !appData.noChessProgram)
11791 SetUserThinkingEnables();
11793 /* [HGM] if a machine claims the game end we verify this claim */
11794 if(gameMode == TwoMachinesPlay && appData.testClaims) {
11795 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11797 ChessMove trueResult = (ChessMove) -1;
11799 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
11800 first.twoMachinesColor[0] :
11801 second.twoMachinesColor[0] ;
11803 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11804 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11805 /* [HGM] verify: engine mate claims accepted if they were flagged */
11806 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11808 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11809 /* [HGM] verify: engine mate claims accepted if they were flagged */
11810 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11812 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11813 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11816 // now verify win claims, but not in drop games, as we don't understand those yet
11817 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11818 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11819 (result == WhiteWins && claimer == 'w' ||
11820 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
11821 if (appData.debugMode) {
11822 fprintf(debugFP, "result=%d sp=%d move=%d\n",
11823 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11825 if(result != trueResult) {
11826 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11827 result = claimer == 'w' ? BlackWins : WhiteWins;
11828 resultDetails = buf;
11831 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11832 && (forwardMostMove <= backwardMostMove ||
11833 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11834 (claimer=='b')==(forwardMostMove&1))
11836 /* [HGM] verify: draws that were not flagged are false claims */
11837 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11838 result = claimer == 'w' ? BlackWins : WhiteWins;
11839 resultDetails = buf;
11841 /* (Claiming a loss is accepted no questions asked!) */
11842 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11843 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11844 result = GameUnfinished;
11845 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11847 /* [HGM] bare: don't allow bare King to win */
11848 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11849 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11850 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11851 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11852 && result != GameIsDrawn)
11853 { int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11854 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11855 int p = (int)boards[forwardMostMove][i][j] - color;
11856 if(p >= 0 && p <= (int)WhiteKing) k++;
11857 oppoKings += (p + color == WhiteKing + BlackPawn - color);
11859 if (appData.debugMode) {
11860 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11861 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11863 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11864 result = GameIsDrawn;
11865 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11866 resultDetails = buf;
11872 if(serverMoves != NULL && !loadFlag) { char c = '=';
11873 if(result==WhiteWins) c = '+';
11874 if(result==BlackWins) c = '-';
11875 if(resultDetails != NULL)
11876 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11878 if (resultDetails != NULL) {
11879 gameInfo.result = result;
11880 gameInfo.resultDetails = StrSave(resultDetails);
11882 /* display last move only if game was not loaded from file */
11883 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11884 DisplayMove(currentMove - 1);
11886 if (forwardMostMove != 0) {
11887 if (gameMode != PlayFromGameFile && gameMode != EditGame
11888 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11890 if (*appData.saveGameFile != NULLCHAR) {
11891 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11892 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11894 SaveGameToFile(appData.saveGameFile, TRUE);
11895 } else if (appData.autoSaveGames) {
11896 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11898 if (*appData.savePositionFile != NULLCHAR) {
11899 SavePositionToFile(appData.savePositionFile);
11901 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11905 /* Tell program how game ended in case it is learning */
11906 /* [HGM] Moved this to after saving the PGN, just in case */
11907 /* engine died and we got here through time loss. In that */
11908 /* case we will get a fatal error writing the pipe, which */
11909 /* would otherwise lose us the PGN. */
11910 /* [HGM] crash: not needed anymore, but doesn't hurt; */
11911 /* output during GameEnds should never be fatal anymore */
11912 if (gameMode == MachinePlaysWhite ||
11913 gameMode == MachinePlaysBlack ||
11914 gameMode == TwoMachinesPlay ||
11915 gameMode == IcsPlayingWhite ||
11916 gameMode == IcsPlayingBlack ||
11917 gameMode == BeginningOfGame) {
11919 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11921 if (first.pr != NoProc) {
11922 SendToProgram(buf, &first);
11924 if (second.pr != NoProc &&
11925 gameMode == TwoMachinesPlay) {
11926 SendToProgram(buf, &second);
11931 if (appData.icsActive) {
11932 if (appData.quietPlay &&
11933 (gameMode == IcsPlayingWhite ||
11934 gameMode == IcsPlayingBlack)) {
11935 SendToICS(ics_prefix);
11936 SendToICS("set shout 1\n");
11938 nextGameMode = IcsIdle;
11939 ics_user_moved = FALSE;
11940 /* clean up premove. It's ugly when the game has ended and the
11941 * premove highlights are still on the board.
11944 gotPremove = FALSE;
11945 ClearPremoveHighlights();
11946 DrawPosition(FALSE, boards[currentMove]);
11948 if (whosays == GE_ICS) {
11951 if (gameMode == IcsPlayingWhite)
11953 else if(gameMode == IcsPlayingBlack)
11954 PlayIcsLossSound();
11957 if (gameMode == IcsPlayingBlack)
11959 else if(gameMode == IcsPlayingWhite)
11960 PlayIcsLossSound();
11963 PlayIcsDrawSound();
11966 PlayIcsUnfinishedSound();
11969 if(appData.quitNext) { ExitEvent(0); return; }
11970 } else if (gameMode == EditGame ||
11971 gameMode == PlayFromGameFile ||
11972 gameMode == AnalyzeMode ||
11973 gameMode == AnalyzeFile) {
11974 nextGameMode = gameMode;
11976 nextGameMode = EndOfGame;
11981 nextGameMode = gameMode;
11984 if (appData.noChessProgram) {
11985 gameMode = nextGameMode;
11987 endingGame = 0; /* [HGM] crash */
11992 /* Put first chess program into idle state */
11993 if (first.pr != NoProc &&
11994 (gameMode == MachinePlaysWhite ||
11995 gameMode == MachinePlaysBlack ||
11996 gameMode == TwoMachinesPlay ||
11997 gameMode == IcsPlayingWhite ||
11998 gameMode == IcsPlayingBlack ||
11999 gameMode == BeginningOfGame)) {
12000 SendToProgram("force\n", &first);
12001 if (first.usePing) {
12003 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
12004 SendToProgram(buf, &first);
12007 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
12008 /* Kill off first chess program */
12009 if (first.isr != NULL)
12010 RemoveInputSource(first.isr);
12013 if (first.pr != NoProc) {
12015 DoSleep( appData.delayBeforeQuit );
12016 SendToProgram("quit\n", &first);
12017 DestroyChildProcess(first.pr, 4 + first.useSigterm);
12018 first.reload = TRUE;
12022 if (second.reuse) {
12023 /* Put second chess program into idle state */
12024 if (second.pr != NoProc &&
12025 gameMode == TwoMachinesPlay) {
12026 SendToProgram("force\n", &second);
12027 if (second.usePing) {
12029 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
12030 SendToProgram(buf, &second);
12033 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
12034 /* Kill off second chess program */
12035 if (second.isr != NULL)
12036 RemoveInputSource(second.isr);
12039 if (second.pr != NoProc) {
12040 DoSleep( appData.delayBeforeQuit );
12041 SendToProgram("quit\n", &second);
12042 DestroyChildProcess(second.pr, 4 + second.useSigterm);
12043 second.reload = TRUE;
12045 second.pr = NoProc;
12048 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
12049 char resChar = '=';
12053 if (first.twoMachinesColor[0] == 'w') {
12056 second.matchWins++;
12061 if (first.twoMachinesColor[0] == 'b') {
12064 second.matchWins++;
12067 case GameUnfinished:
12073 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
12074 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
12075 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
12076 ReserveGame(nextGame, resChar); // sets nextGame
12077 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
12078 else ranking = strdup("busy"); //suppress popup when aborted but not finished
12079 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
12081 if (nextGame <= appData.matchGames && !abortMatch) {
12082 gameMode = nextGameMode;
12083 matchGame = nextGame; // this will be overruled in tourney mode!
12084 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
12085 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
12086 endingGame = 0; /* [HGM] crash */
12089 gameMode = nextGameMode;
12091 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
12092 OutputKibitz(2, buf);
12093 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
12094 OutputKibitz(2, buf);
12095 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
12096 if(second.matchWins) OutputKibitz(2, buf);
12097 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
12098 OutputKibitz(2, buf);
12100 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
12101 first.tidy, second.tidy,
12102 first.matchWins, second.matchWins,
12103 appData.matchGames - (first.matchWins + second.matchWins));
12104 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
12105 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
12106 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
12107 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
12108 first.twoMachinesColor = "black\n";
12109 second.twoMachinesColor = "white\n";
12111 first.twoMachinesColor = "white\n";
12112 second.twoMachinesColor = "black\n";
12116 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
12117 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
12119 gameMode = nextGameMode;
12121 endingGame = 0; /* [HGM] crash */
12122 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
12123 if(matchMode == TRUE) { // match through command line: exit with or without popup
12125 ToNrEvent(forwardMostMove);
12126 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
12128 } else DisplayFatalError(buf, 0, 0);
12129 } else { // match through menu; just stop, with or without popup
12130 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
12133 if(strcmp(ranking, "busy")) DisplayNote(ranking);
12134 } else DisplayNote(buf);
12136 if(ranking) free(ranking);
12140 /* Assumes program was just initialized (initString sent).
12141 Leaves program in force mode. */
12143 FeedMovesToProgram (ChessProgramState *cps, int upto)
12147 if (appData.debugMode)
12148 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
12149 startedFromSetupPosition ? "position and " : "",
12150 backwardMostMove, upto, cps->which);
12151 if(currentlyInitializedVariant != gameInfo.variant) {
12153 // [HGM] variantswitch: make engine aware of new variant
12154 if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
12155 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
12156 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
12157 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
12158 SendToProgram(buf, cps);
12159 currentlyInitializedVariant = gameInfo.variant;
12161 SendToProgram("force\n", cps);
12162 if (startedFromSetupPosition) {
12163 SendBoard(cps, backwardMostMove);
12164 if (appData.debugMode) {
12165 fprintf(debugFP, "feedMoves\n");
12168 for (i = backwardMostMove; i < upto; i++) {
12169 SendMoveToProgram(i, cps);
12175 ResurrectChessProgram ()
12177 /* The chess program may have exited.
12178 If so, restart it and feed it all the moves made so far. */
12179 static int doInit = 0;
12181 if (appData.noChessProgram) return 1;
12183 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
12184 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
12185 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
12186 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
12188 if (first.pr != NoProc) return 1;
12189 StartChessProgram(&first);
12191 InitChessProgram(&first, FALSE);
12192 FeedMovesToProgram(&first, currentMove);
12194 if (!first.sendTime) {
12195 /* can't tell gnuchess what its clock should read,
12196 so we bow to its notion. */
12198 timeRemaining[0][currentMove] = whiteTimeRemaining;
12199 timeRemaining[1][currentMove] = blackTimeRemaining;
12202 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
12203 appData.icsEngineAnalyze) && first.analysisSupport) {
12204 SendToProgram("analyze\n", &first);
12205 first.analyzing = TRUE;
12211 * Button procedures
12214 Reset (int redraw, int init)
12218 if (appData.debugMode) {
12219 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12220 redraw, init, gameMode);
12222 pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12223 deadRanks = 0; // assume entire board is used
12225 for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12226 CleanupTail(); // [HGM] vari: delete any stored variations
12227 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12228 pausing = pauseExamInvalid = FALSE;
12229 startedFromSetupPosition = blackPlaysFirst = FALSE;
12231 whiteFlag = blackFlag = FALSE;
12232 userOfferedDraw = FALSE;
12233 hintRequested = bookRequested = FALSE;
12234 first.maybeThinking = FALSE;
12235 second.maybeThinking = FALSE;
12236 first.bookSuspend = FALSE; // [HGM] book
12237 second.bookSuspend = FALSE;
12238 thinkOutput[0] = NULLCHAR;
12239 lastHint[0] = NULLCHAR;
12240 ClearGameInfo(&gameInfo);
12241 gameInfo.variant = StringToVariant(appData.variant);
12242 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12243 gameInfo.variant = VariantUnknown;
12244 strncpy(engineVariant, appData.variant, MSG_SIZ);
12246 ics_user_moved = ics_clock_paused = FALSE;
12247 ics_getting_history = H_FALSE;
12249 white_holding[0] = black_holding[0] = NULLCHAR;
12250 ClearProgramStats();
12251 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12255 flipView = appData.flipView;
12256 ClearPremoveHighlights();
12257 gotPremove = FALSE;
12258 alarmSounded = FALSE;
12259 killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12261 GameEnds(EndOfFile, NULL, GE_PLAYER);
12262 if(appData.serverMovesName != NULL) {
12263 /* [HGM] prepare to make moves file for broadcasting */
12264 clock_t t = clock();
12265 if(serverMoves != NULL) fclose(serverMoves);
12266 serverMoves = fopen(appData.serverMovesName, "r");
12267 if(serverMoves != NULL) {
12268 fclose(serverMoves);
12269 /* delay 15 sec before overwriting, so all clients can see end */
12270 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12272 serverMoves = fopen(appData.serverMovesName, "w");
12276 gameMode = BeginningOfGame;
12278 if(appData.icsActive) gameInfo.variant = VariantNormal;
12279 currentMove = forwardMostMove = backwardMostMove = 0;
12280 MarkTargetSquares(1);
12281 InitPosition(redraw);
12282 for (i = 0; i < MAX_MOVES; i++) {
12283 if (commentList[i] != NULL) {
12284 free(commentList[i]);
12285 commentList[i] = NULL;
12289 timeRemaining[0][0] = whiteTimeRemaining;
12290 timeRemaining[1][0] = blackTimeRemaining;
12292 if (first.pr == NoProc) {
12293 StartChessProgram(&first);
12296 InitChessProgram(&first, startedFromSetupPosition);
12299 DisplayMessage("", "");
12300 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12301 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12302 ClearMap(); // [HGM] exclude: invalidate map
12306 AutoPlayGameLoop ()
12309 if (!AutoPlayOneMove())
12311 if (matchMode || appData.timeDelay == 0)
12313 if (appData.timeDelay < 0)
12315 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12323 ReloadGame(1); // next game
12329 int fromX, fromY, toX, toY;
12331 if (appData.debugMode) {
12332 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12335 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12338 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12339 pvInfoList[currentMove].depth = programStats.depth;
12340 pvInfoList[currentMove].score = programStats.score;
12341 pvInfoList[currentMove].time = 0;
12342 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12343 else { // append analysis of final position as comment
12345 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12346 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12348 programStats.depth = 0;
12351 if (currentMove >= forwardMostMove) {
12352 if(gameMode == AnalyzeFile) {
12353 if(appData.loadGameIndex == -1) {
12354 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12355 ScheduleDelayedEvent(AnalyzeNextGame, 10);
12357 ExitAnalyzeMode(); SendToProgram("force\n", &first);
12360 // gameMode = EndOfGame;
12361 // ModeHighlight();
12363 /* [AS] Clear current move marker at the end of a game */
12364 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12369 toX = moveList[currentMove][2] - AAA;
12370 toY = moveList[currentMove][3] - ONE;
12372 if (moveList[currentMove][1] == '@') {
12373 if (appData.highlightLastMove) {
12374 SetHighlights(-1, -1, toX, toY);
12377 fromX = moveList[currentMove][0] - AAA;
12378 fromY = moveList[currentMove][1] - ONE;
12380 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12382 if(moveList[currentMove][4] == ';') { // multi-leg
12383 killX = moveList[currentMove][5] - AAA;
12384 killY = moveList[currentMove][6] - ONE;
12386 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12387 killX = killY = -1;
12389 if (appData.highlightLastMove) {
12390 SetHighlights(fromX, fromY, toX, toY);
12393 DisplayMove(currentMove);
12394 SendMoveToProgram(currentMove++, &first);
12395 DisplayBothClocks();
12396 DrawPosition(FALSE, boards[currentMove]);
12397 // [HGM] PV info: always display, routine tests if empty
12398 DisplayComment(currentMove - 1, commentList[currentMove]);
12404 LoadGameOneMove (ChessMove readAhead)
12406 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12407 char promoChar = NULLCHAR;
12408 ChessMove moveType;
12409 char move[MSG_SIZ];
12412 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12413 gameMode != AnalyzeMode && gameMode != Training) {
12418 yyboardindex = forwardMostMove;
12419 if (readAhead != EndOfFile) {
12420 moveType = readAhead;
12422 if (gameFileFP == NULL)
12424 moveType = (ChessMove) Myylex();
12428 switch (moveType) {
12430 if (appData.debugMode)
12431 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12434 /* append the comment but don't display it */
12435 AppendComment(currentMove, p, FALSE);
12438 case WhiteCapturesEnPassant:
12439 case BlackCapturesEnPassant:
12440 case WhitePromotion:
12441 case BlackPromotion:
12442 case WhiteNonPromotion:
12443 case BlackNonPromotion:
12446 case WhiteKingSideCastle:
12447 case WhiteQueenSideCastle:
12448 case BlackKingSideCastle:
12449 case BlackQueenSideCastle:
12450 case WhiteKingSideCastleWild:
12451 case WhiteQueenSideCastleWild:
12452 case BlackKingSideCastleWild:
12453 case BlackQueenSideCastleWild:
12455 case WhiteHSideCastleFR:
12456 case WhiteASideCastleFR:
12457 case BlackHSideCastleFR:
12458 case BlackASideCastleFR:
12460 if (appData.debugMode)
12461 fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12462 fromX = currentMoveString[0] - AAA;
12463 fromY = currentMoveString[1] - ONE;
12464 toX = currentMoveString[2] - AAA;
12465 toY = currentMoveString[3] - ONE;
12466 promoChar = currentMoveString[4];
12467 if(promoChar == ';') promoChar = currentMoveString[7];
12472 if (appData.debugMode)
12473 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12474 fromX = moveType == WhiteDrop ?
12475 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12476 (int) CharToPiece(ToLower(currentMoveString[0]));
12478 toX = currentMoveString[2] - AAA;
12479 toY = currentMoveString[3] - ONE;
12485 case GameUnfinished:
12486 if (appData.debugMode)
12487 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12488 p = strchr(yy_text, '{');
12489 if (p == NULL) p = strchr(yy_text, '(');
12492 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12494 q = strchr(p, *p == '{' ? '}' : ')');
12495 if (q != NULL) *q = NULLCHAR;
12498 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12499 GameEnds(moveType, p, GE_FILE);
12501 if (cmailMsgLoaded) {
12503 flipView = WhiteOnMove(currentMove);
12504 if (moveType == GameUnfinished) flipView = !flipView;
12505 if (appData.debugMode)
12506 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12511 if (appData.debugMode)
12512 fprintf(debugFP, "Parser hit end of file\n");
12513 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12519 if (WhiteOnMove(currentMove)) {
12520 GameEnds(BlackWins, "Black mates", GE_FILE);
12522 GameEnds(WhiteWins, "White mates", GE_FILE);
12526 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12532 case MoveNumberOne:
12533 if (lastLoadGameStart == GNUChessGame) {
12534 /* GNUChessGames have numbers, but they aren't move numbers */
12535 if (appData.debugMode)
12536 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12537 yy_text, (int) moveType);
12538 return LoadGameOneMove(EndOfFile); /* tail recursion */
12540 /* else fall thru */
12545 /* Reached start of next game in file */
12546 if (appData.debugMode)
12547 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12548 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12554 if (WhiteOnMove(currentMove)) {
12555 GameEnds(BlackWins, "Black mates", GE_FILE);
12557 GameEnds(WhiteWins, "White mates", GE_FILE);
12561 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12567 case PositionDiagram: /* should not happen; ignore */
12568 case ElapsedTime: /* ignore */
12569 case NAG: /* ignore */
12570 if (appData.debugMode)
12571 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12572 yy_text, (int) moveType);
12573 return LoadGameOneMove(EndOfFile); /* tail recursion */
12576 if (appData.testLegality) {
12577 if (appData.debugMode)
12578 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12579 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12580 (forwardMostMove / 2) + 1,
12581 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12582 DisplayError(move, 0);
12585 if (appData.debugMode)
12586 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12587 yy_text, currentMoveString);
12588 if(currentMoveString[1] == '@') {
12589 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12592 fromX = currentMoveString[0] - AAA;
12593 fromY = currentMoveString[1] - ONE;
12595 toX = currentMoveString[2] - AAA;
12596 toY = currentMoveString[3] - ONE;
12597 promoChar = currentMoveString[4];
12601 case AmbiguousMove:
12602 if (appData.debugMode)
12603 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12604 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12605 (forwardMostMove / 2) + 1,
12606 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12607 DisplayError(move, 0);
12612 case ImpossibleMove:
12613 if (appData.debugMode)
12614 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12615 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12616 (forwardMostMove / 2) + 1,
12617 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12618 DisplayError(move, 0);
12624 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12625 DrawPosition(FALSE, boards[currentMove]);
12626 DisplayBothClocks();
12627 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12628 DisplayComment(currentMove - 1, commentList[currentMove]);
12630 (void) StopLoadGameTimer();
12632 cmailOldMove = forwardMostMove;
12635 /* currentMoveString is set as a side-effect of yylex */
12637 thinkOutput[0] = NULLCHAR;
12638 MakeMove(fromX, fromY, toX, toY, promoChar);
12639 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12640 currentMove = forwardMostMove;
12645 /* Load the nth game from the given file */
12647 LoadGameFromFile (char *filename, int n, char *title, int useList)
12652 if (strcmp(filename, "-") == 0) {
12656 f = fopen(filename, "rb");
12658 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12659 DisplayError(buf, errno);
12663 if (fseek(f, 0, 0) == -1) {
12664 /* f is not seekable; probably a pipe */
12667 if (useList && n == 0) {
12668 int error = GameListBuild(f);
12670 DisplayError(_("Cannot build game list"), error);
12671 } else if (!ListEmpty(&gameList) &&
12672 ((ListGame *) gameList.tailPred)->number > 1) {
12673 GameListPopUp(f, title);
12680 return LoadGame(f, n, title, FALSE);
12685 MakeRegisteredMove ()
12687 int fromX, fromY, toX, toY;
12689 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12690 switch (cmailMoveType[lastLoadGameNumber - 1]) {
12693 if (appData.debugMode)
12694 fprintf(debugFP, "Restoring %s for game %d\n",
12695 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12697 thinkOutput[0] = NULLCHAR;
12698 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12699 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12700 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12701 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12702 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12703 promoChar = cmailMove[lastLoadGameNumber - 1][4];
12704 MakeMove(fromX, fromY, toX, toY, promoChar);
12705 ShowMove(fromX, fromY, toX, toY);
12707 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12714 if (WhiteOnMove(currentMove)) {
12715 GameEnds(BlackWins, "Black mates", GE_PLAYER);
12717 GameEnds(WhiteWins, "White mates", GE_PLAYER);
12722 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12729 if (WhiteOnMove(currentMove)) {
12730 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12732 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12737 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12748 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12750 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12754 if (gameNumber > nCmailGames) {
12755 DisplayError(_("No more games in this message"), 0);
12758 if (f == lastLoadGameFP) {
12759 int offset = gameNumber - lastLoadGameNumber;
12761 cmailMsg[0] = NULLCHAR;
12762 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12763 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12764 nCmailMovesRegistered--;
12766 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12767 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12768 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12771 if (! RegisterMove()) return FALSE;
12775 retVal = LoadGame(f, gameNumber, title, useList);
12777 /* Make move registered during previous look at this game, if any */
12778 MakeRegisteredMove();
12780 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12781 commentList[currentMove]
12782 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12783 DisplayComment(currentMove - 1, commentList[currentMove]);
12789 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12791 ReloadGame (int offset)
12793 int gameNumber = lastLoadGameNumber + offset;
12794 if (lastLoadGameFP == NULL) {
12795 DisplayError(_("No game has been loaded yet"), 0);
12798 if (gameNumber <= 0) {
12799 DisplayError(_("Can't back up any further"), 0);
12802 if (cmailMsgLoaded) {
12803 return CmailLoadGame(lastLoadGameFP, gameNumber,
12804 lastLoadGameTitle, lastLoadGameUseList);
12806 return LoadGame(lastLoadGameFP, gameNumber,
12807 lastLoadGameTitle, lastLoadGameUseList);
12811 int keys[EmptySquare+1];
12814 PositionMatches (Board b1, Board b2)
12817 switch(appData.searchMode) {
12818 case 1: return CompareWithRights(b1, b2);
12820 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12821 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12825 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12826 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12827 sum += keys[b1[r][f]] - keys[b2[r][f]];
12831 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12832 sum += keys[b1[r][f]] - keys[b2[r][f]];
12844 int pieceList[256], quickBoard[256];
12845 ChessSquare pieceType[256] = { EmptySquare };
12846 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12847 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12848 int soughtTotal, turn;
12849 Boolean epOK, flipSearch;
12852 unsigned char piece, to;
12855 #define DSIZE (250000)
12857 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12858 Move *moveDatabase = initialSpace;
12859 unsigned int movePtr, dataSize = DSIZE;
12862 MakePieceList (Board board, int *counts)
12864 int r, f, n=Q_PROMO, total=0;
12865 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12866 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12867 int sq = f + (r<<4);
12868 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12869 quickBoard[sq] = ++n;
12871 pieceType[n] = board[r][f];
12872 counts[board[r][f]]++;
12873 if(board[r][f] == WhiteKing) pieceList[1] = n; else
12874 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12878 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12883 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12885 int sq = fromX + (fromY<<4);
12886 int piece = quickBoard[sq], rook;
12887 quickBoard[sq] = 0;
12888 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12889 if(piece == pieceList[1] && fromY == toY) {
12890 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12891 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12892 moveDatabase[movePtr++].piece = Q_WCASTL;
12893 quickBoard[sq] = piece;
12894 piece = quickBoard[from]; quickBoard[from] = 0;
12895 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12896 } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12897 quickBoard[sq] = 0; // remove Rook
12898 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12899 moveDatabase[movePtr++].piece = Q_WCASTL;
12900 quickBoard[sq] = pieceList[1]; // put King
12902 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12905 if(piece == pieceList[2] && fromY == toY) {
12906 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12907 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12908 moveDatabase[movePtr++].piece = Q_BCASTL;
12909 quickBoard[sq] = piece;
12910 piece = quickBoard[from]; quickBoard[from] = 0;
12911 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12912 } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12913 quickBoard[sq] = 0; // remove Rook
12914 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12915 moveDatabase[movePtr++].piece = Q_BCASTL;
12916 quickBoard[sq] = pieceList[2]; // put King
12918 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12921 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12922 quickBoard[(fromY<<4)+toX] = 0;
12923 moveDatabase[movePtr].piece = Q_EP;
12924 moveDatabase[movePtr++].to = (fromY<<4)+toX;
12925 moveDatabase[movePtr].to = sq;
12927 if(promoPiece != pieceType[piece]) {
12928 moveDatabase[movePtr++].piece = Q_PROMO;
12929 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12931 moveDatabase[movePtr].piece = piece;
12932 quickBoard[sq] = piece;
12937 PackGame (Board board)
12939 Move *newSpace = NULL;
12940 moveDatabase[movePtr].piece = 0; // terminate previous game
12941 if(movePtr > dataSize) {
12942 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12943 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12944 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12947 Move *p = moveDatabase, *q = newSpace;
12948 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
12949 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12950 moveDatabase = newSpace;
12951 } else { // calloc failed, we must be out of memory. Too bad...
12952 dataSize = 0; // prevent calloc events for all subsequent games
12953 return 0; // and signal this one isn't cached
12957 MakePieceList(board, counts);
12962 QuickCompare (Board board, int *minCounts, int *maxCounts)
12963 { // compare according to search mode
12965 switch(appData.searchMode)
12967 case 1: // exact position match
12968 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12969 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12970 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12973 case 2: // can have extra material on empty squares
12974 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12975 if(board[r][f] == EmptySquare) continue;
12976 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12979 case 3: // material with exact Pawn structure
12980 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12981 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12982 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12983 } // fall through to material comparison
12984 case 4: // exact material
12985 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12987 case 6: // material range with given imbalance
12988 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12989 // fall through to range comparison
12990 case 5: // material range
12991 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12997 QuickScan (Board board, Move *move)
12998 { // reconstruct game,and compare all positions in it
12999 int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
13001 int piece = move->piece;
13002 int to = move->to, from = pieceList[piece];
13003 if(found < 0) { // if already found just scan to game end for final piece count
13004 if(QuickCompare(soughtBoard, minSought, maxSought) ||
13005 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
13006 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
13007 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
13009 static int lastCounts[EmptySquare+1];
13011 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
13012 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
13013 } else stretch = 0;
13014 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
13015 if(found >= 0 && !appData.minPieces) return found;
13017 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
13018 if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
13019 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
13020 piece = (++move)->piece;
13021 from = pieceList[piece];
13022 counts[pieceType[piece]]--;
13023 pieceType[piece] = (ChessSquare) move->to;
13024 counts[move->to]++;
13025 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
13026 counts[pieceType[quickBoard[to]]]--;
13027 quickBoard[to] = 0; total--;
13030 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
13031 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
13032 from = pieceList[piece]; // so this must be King
13033 quickBoard[from] = 0;
13034 pieceList[piece] = to;
13035 from = pieceList[(++move)->piece]; // for FRC this has to be done here
13036 quickBoard[from] = 0; // rook
13037 quickBoard[to] = piece;
13038 to = move->to; piece = move->piece;
13042 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
13043 if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
13044 quickBoard[from] = 0;
13046 quickBoard[to] = piece;
13047 pieceList[piece] = to;
13057 flipSearch = FALSE;
13058 CopyBoard(soughtBoard, boards[currentMove]);
13059 soughtTotal = MakePieceList(soughtBoard, maxSought);
13060 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
13061 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
13062 CopyBoard(reverseBoard, boards[currentMove]);
13063 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
13064 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
13065 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
13066 reverseBoard[r][f] = piece;
13068 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
13069 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
13070 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
13071 || (boards[currentMove][CASTLING][2] == NoRights ||
13072 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
13073 && (boards[currentMove][CASTLING][5] == NoRights ||
13074 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
13077 CopyBoard(flipBoard, soughtBoard);
13078 CopyBoard(rotateBoard, reverseBoard);
13079 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
13080 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
13081 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
13084 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
13085 if(appData.searchMode >= 5) {
13086 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
13087 MakePieceList(soughtBoard, minSought);
13088 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
13090 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
13091 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
13094 GameInfo dummyInfo;
13095 static int creatingBook;
13098 GameContainsPosition (FILE *f, ListGame *lg)
13100 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
13101 int fromX, fromY, toX, toY;
13103 static int initDone=FALSE;
13105 // weed out games based on numerical tag comparison
13106 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
13107 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
13108 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
13109 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
13111 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
13114 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
13115 else CopyBoard(boards[scratch], initialPosition); // default start position
13118 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
13119 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
13122 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13123 fseek(f, lg->offset, 0);
13126 yyboardindex = scratch;
13127 quickFlag = plyNr+1;
13132 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
13138 if(plyNr) return -1; // after we have seen moves, this is for new game
13141 case AmbiguousMove: // we cannot reconstruct the game beyond these two
13142 case ImpossibleMove:
13143 case WhiteWins: // game ends here with these four
13146 case GameUnfinished:
13150 if(appData.testLegality) return -1;
13151 case WhiteCapturesEnPassant:
13152 case BlackCapturesEnPassant:
13153 case WhitePromotion:
13154 case BlackPromotion:
13155 case WhiteNonPromotion:
13156 case BlackNonPromotion:
13159 case WhiteKingSideCastle:
13160 case WhiteQueenSideCastle:
13161 case BlackKingSideCastle:
13162 case BlackQueenSideCastle:
13163 case WhiteKingSideCastleWild:
13164 case WhiteQueenSideCastleWild:
13165 case BlackKingSideCastleWild:
13166 case BlackQueenSideCastleWild:
13167 case WhiteHSideCastleFR:
13168 case WhiteASideCastleFR:
13169 case BlackHSideCastleFR:
13170 case BlackASideCastleFR:
13171 fromX = currentMoveString[0] - AAA;
13172 fromY = currentMoveString[1] - ONE;
13173 toX = currentMoveString[2] - AAA;
13174 toY = currentMoveString[3] - ONE;
13175 promoChar = currentMoveString[4];
13179 fromX = next == WhiteDrop ?
13180 (int) CharToPiece(ToUpper(currentMoveString[0])) :
13181 (int) CharToPiece(ToLower(currentMoveString[0]));
13183 toX = currentMoveString[2] - AAA;
13184 toY = currentMoveString[3] - ONE;
13188 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
13190 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
13191 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13192 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
13193 if(appData.findMirror) {
13194 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
13195 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
13200 /* Load the nth game from open file f */
13202 LoadGame (FILE *f, int gameNumber, char *title, int useList)
13206 int gn = gameNumber;
13207 ListGame *lg = NULL;
13208 int numPGNTags = 0, i;
13210 GameMode oldGameMode;
13211 VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13212 char oldName[MSG_SIZ];
13214 safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13216 if (appData.debugMode)
13217 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13219 if (gameMode == Training )
13220 SetTrainingModeOff();
13222 oldGameMode = gameMode;
13223 if (gameMode != BeginningOfGame) {
13224 Reset(FALSE, TRUE);
13226 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13229 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13230 fclose(lastLoadGameFP);
13234 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13237 fseek(f, lg->offset, 0);
13238 GameListHighlight(gameNumber);
13239 pos = lg->position;
13243 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13244 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13246 DisplayError(_("Game number out of range"), 0);
13251 if (fseek(f, 0, 0) == -1) {
13252 if (f == lastLoadGameFP ?
13253 gameNumber == lastLoadGameNumber + 1 :
13257 DisplayError(_("Can't seek on game file"), 0);
13262 lastLoadGameFP = f;
13263 lastLoadGameNumber = gameNumber;
13264 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13265 lastLoadGameUseList = useList;
13269 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13270 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13271 lg->gameInfo.black);
13273 } else if (*title != NULLCHAR) {
13274 if (gameNumber > 1) {
13275 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13278 DisplayTitle(title);
13282 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13283 gameMode = PlayFromGameFile;
13287 currentMove = forwardMostMove = backwardMostMove = 0;
13288 CopyBoard(boards[0], initialPosition);
13292 * Skip the first gn-1 games in the file.
13293 * Also skip over anything that precedes an identifiable
13294 * start of game marker, to avoid being confused by
13295 * garbage at the start of the file. Currently
13296 * recognized start of game markers are the move number "1",
13297 * the pattern "gnuchess .* game", the pattern
13298 * "^[#;%] [^ ]* game file", and a PGN tag block.
13299 * A game that starts with one of the latter two patterns
13300 * will also have a move number 1, possibly
13301 * following a position diagram.
13302 * 5-4-02: Let's try being more lenient and allowing a game to
13303 * start with an unnumbered move. Does that break anything?
13305 cm = lastLoadGameStart = EndOfFile;
13307 yyboardindex = forwardMostMove;
13308 cm = (ChessMove) Myylex();
13311 if (cmailMsgLoaded) {
13312 nCmailGames = CMAIL_MAX_GAMES - gn;
13315 DisplayError(_("Game not found in file"), 0);
13322 lastLoadGameStart = cm;
13325 case MoveNumberOne:
13326 switch (lastLoadGameStart) {
13331 case MoveNumberOne:
13333 gn--; /* count this game */
13334 lastLoadGameStart = cm;
13343 switch (lastLoadGameStart) {
13346 case MoveNumberOne:
13348 gn--; /* count this game */
13349 lastLoadGameStart = cm;
13352 lastLoadGameStart = cm; /* game counted already */
13360 yyboardindex = forwardMostMove;
13361 cm = (ChessMove) Myylex();
13362 } while (cm == PGNTag || cm == Comment);
13369 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13370 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
13371 != CMAIL_OLD_RESULT) {
13373 cmailResult[ CMAIL_MAX_GAMES
13374 - gn - 1] = CMAIL_OLD_RESULT;
13381 /* Only a NormalMove can be at the start of a game
13382 * without a position diagram. */
13383 if (lastLoadGameStart == EndOfFile ) {
13385 lastLoadGameStart = MoveNumberOne;
13394 if (appData.debugMode)
13395 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13397 for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13399 if (cm == XBoardGame) {
13400 /* Skip any header junk before position diagram and/or move 1 */
13402 yyboardindex = forwardMostMove;
13403 cm = (ChessMove) Myylex();
13405 if (cm == EndOfFile ||
13406 cm == GNUChessGame || cm == XBoardGame) {
13407 /* Empty game; pretend end-of-file and handle later */
13412 if (cm == MoveNumberOne || cm == PositionDiagram ||
13413 cm == PGNTag || cm == Comment)
13416 } else if (cm == GNUChessGame) {
13417 if (gameInfo.event != NULL) {
13418 free(gameInfo.event);
13420 gameInfo.event = StrSave(yy_text);
13423 startedFromSetupPosition = startedFromPositionFile; // [HGM]
13424 while (cm == PGNTag) {
13425 if (appData.debugMode)
13426 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13427 err = ParsePGNTag(yy_text, &gameInfo);
13428 if (!err) numPGNTags++;
13430 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13431 if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13432 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13433 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13434 InitPosition(TRUE);
13435 oldVariant = gameInfo.variant;
13436 if (appData.debugMode)
13437 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13441 if (gameInfo.fen != NULL) {
13442 Board initial_position;
13443 startedFromSetupPosition = TRUE;
13444 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13446 DisplayError(_("Bad FEN position in file"), 0);
13449 CopyBoard(boards[0], initial_position);
13450 if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13451 CopyBoard(initialPosition, initial_position);
13452 if (blackPlaysFirst) {
13453 currentMove = forwardMostMove = backwardMostMove = 1;
13454 CopyBoard(boards[1], initial_position);
13455 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13456 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13457 timeRemaining[0][1] = whiteTimeRemaining;
13458 timeRemaining[1][1] = blackTimeRemaining;
13459 if (commentList[0] != NULL) {
13460 commentList[1] = commentList[0];
13461 commentList[0] = NULL;
13464 currentMove = forwardMostMove = backwardMostMove = 0;
13466 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13468 initialRulePlies = FENrulePlies;
13469 for( i=0; i< nrCastlingRights; i++ )
13470 initialRights[i] = initial_position[CASTLING][i];
13472 yyboardindex = forwardMostMove;
13473 free(gameInfo.fen);
13474 gameInfo.fen = NULL;
13477 yyboardindex = forwardMostMove;
13478 cm = (ChessMove) Myylex();
13480 /* Handle comments interspersed among the tags */
13481 while (cm == Comment) {
13483 if (appData.debugMode)
13484 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13486 AppendComment(currentMove, p, FALSE);
13487 yyboardindex = forwardMostMove;
13488 cm = (ChessMove) Myylex();
13492 /* don't rely on existence of Event tag since if game was
13493 * pasted from clipboard the Event tag may not exist
13495 if (numPGNTags > 0){
13497 if (gameInfo.variant == VariantNormal) {
13498 VariantClass v = StringToVariant(gameInfo.event);
13499 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13500 if(v < VariantShogi) gameInfo.variant = v;
13503 if( appData.autoDisplayTags ) {
13504 tags = PGNTags(&gameInfo);
13505 TagsPopUp(tags, CmailMsg());
13510 /* Make something up, but don't display it now */
13515 if (cm == PositionDiagram) {
13518 Board initial_position;
13520 if (appData.debugMode)
13521 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13523 if (!startedFromSetupPosition) {
13525 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13526 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13537 initial_position[i][j++] = CharToPiece(*p);
13540 while (*p == ' ' || *p == '\t' ||
13541 *p == '\n' || *p == '\r') p++;
13543 if (strncmp(p, "black", strlen("black"))==0)
13544 blackPlaysFirst = TRUE;
13546 blackPlaysFirst = FALSE;
13547 startedFromSetupPosition = TRUE;
13549 CopyBoard(boards[0], initial_position);
13550 if (blackPlaysFirst) {
13551 currentMove = forwardMostMove = backwardMostMove = 1;
13552 CopyBoard(boards[1], initial_position);
13553 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13554 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13555 timeRemaining[0][1] = whiteTimeRemaining;
13556 timeRemaining[1][1] = blackTimeRemaining;
13557 if (commentList[0] != NULL) {
13558 commentList[1] = commentList[0];
13559 commentList[0] = NULL;
13562 currentMove = forwardMostMove = backwardMostMove = 0;
13565 yyboardindex = forwardMostMove;
13566 cm = (ChessMove) Myylex();
13569 if(!creatingBook) {
13570 if (first.pr == NoProc) {
13571 StartChessProgram(&first);
13573 InitChessProgram(&first, FALSE);
13574 if(gameInfo.variant == VariantUnknown && *oldName) {
13575 safeStrCpy(engineVariant, oldName, MSG_SIZ);
13576 gameInfo.variant = v;
13578 SendToProgram("force\n", &first);
13579 if (startedFromSetupPosition) {
13580 SendBoard(&first, forwardMostMove);
13581 if (appData.debugMode) {
13582 fprintf(debugFP, "Load Game\n");
13584 DisplayBothClocks();
13588 /* [HGM] server: flag to write setup moves in broadcast file as one */
13589 loadFlag = appData.suppressLoadMoves;
13591 while (cm == Comment) {
13593 if (appData.debugMode)
13594 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13596 AppendComment(currentMove, p, FALSE);
13597 yyboardindex = forwardMostMove;
13598 cm = (ChessMove) Myylex();
13601 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13602 cm == WhiteWins || cm == BlackWins ||
13603 cm == GameIsDrawn || cm == GameUnfinished) {
13604 DisplayMessage("", _("No moves in game"));
13605 if (cmailMsgLoaded) {
13606 if (appData.debugMode)
13607 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13611 DrawPosition(FALSE, boards[currentMove]);
13612 DisplayBothClocks();
13613 gameMode = EditGame;
13620 // [HGM] PV info: routine tests if comment empty
13621 if (!matchMode && (pausing || appData.timeDelay != 0)) {
13622 DisplayComment(currentMove - 1, commentList[currentMove]);
13624 if (!matchMode && appData.timeDelay != 0)
13625 DrawPosition(FALSE, boards[currentMove]);
13627 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13628 programStats.ok_to_send = 1;
13631 /* if the first token after the PGN tags is a move
13632 * and not move number 1, retrieve it from the parser
13634 if (cm != MoveNumberOne)
13635 LoadGameOneMove(cm);
13637 /* load the remaining moves from the file */
13638 while (LoadGameOneMove(EndOfFile)) {
13639 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13640 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13643 /* rewind to the start of the game */
13644 currentMove = backwardMostMove;
13646 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13648 if (oldGameMode == AnalyzeFile) {
13649 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13650 AnalyzeFileEvent();
13652 if (oldGameMode == AnalyzeMode) {
13653 AnalyzeFileEvent();
13656 if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13657 long int w, b; // [HGM] adjourn: restore saved clock times
13658 char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13659 if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13660 timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13661 timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13665 if(creatingBook) return TRUE;
13666 if (!matchMode && pos > 0) {
13667 ToNrEvent(pos); // [HGM] no autoplay if selected on position
13669 if (matchMode || appData.timeDelay == 0) {
13671 } else if (appData.timeDelay > 0) {
13672 AutoPlayGameLoop();
13675 if (appData.debugMode)
13676 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13678 loadFlag = 0; /* [HGM] true game starts */
13682 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13684 ReloadPosition (int offset)
13686 int positionNumber = lastLoadPositionNumber + offset;
13687 if (lastLoadPositionFP == NULL) {
13688 DisplayError(_("No position has been loaded yet"), 0);
13691 if (positionNumber <= 0) {
13692 DisplayError(_("Can't back up any further"), 0);
13695 return LoadPosition(lastLoadPositionFP, positionNumber,
13696 lastLoadPositionTitle);
13699 /* Load the nth position from the given file */
13701 LoadPositionFromFile (char *filename, int n, char *title)
13706 if (strcmp(filename, "-") == 0) {
13707 return LoadPosition(stdin, n, "stdin");
13709 f = fopen(filename, "rb");
13711 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13712 DisplayError(buf, errno);
13715 return LoadPosition(f, n, title);
13720 /* Load the nth position from the given open file, and close it */
13722 LoadPosition (FILE *f, int positionNumber, char *title)
13724 char *p, line[MSG_SIZ];
13725 Board initial_position;
13726 int i, j, fenMode, pn;
13728 if (gameMode == Training )
13729 SetTrainingModeOff();
13731 if (gameMode != BeginningOfGame) {
13732 Reset(FALSE, TRUE);
13734 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13735 fclose(lastLoadPositionFP);
13737 if (positionNumber == 0) positionNumber = 1;
13738 lastLoadPositionFP = f;
13739 lastLoadPositionNumber = positionNumber;
13740 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13741 if (first.pr == NoProc && !appData.noChessProgram) {
13742 StartChessProgram(&first);
13743 InitChessProgram(&first, FALSE);
13745 pn = positionNumber;
13746 if (positionNumber < 0) {
13747 /* Negative position number means to seek to that byte offset */
13748 if (fseek(f, -positionNumber, 0) == -1) {
13749 DisplayError(_("Can't seek on position file"), 0);
13754 if (fseek(f, 0, 0) == -1) {
13755 if (f == lastLoadPositionFP ?
13756 positionNumber == lastLoadPositionNumber + 1 :
13757 positionNumber == 1) {
13760 DisplayError(_("Can't seek on position file"), 0);
13765 /* See if this file is FEN or old-style xboard */
13766 if (fgets(line, MSG_SIZ, f) == NULL) {
13767 DisplayError(_("Position not found in file"), 0);
13770 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13771 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13774 if (fenMode || line[0] == '#') pn--;
13776 /* skip positions before number pn */
13777 if (fgets(line, MSG_SIZ, f) == NULL) {
13779 DisplayError(_("Position not found in file"), 0);
13782 if (fenMode || line[0] == '#') pn--;
13788 if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13789 DisplayError(_("Bad FEN position in file"), 0);
13792 if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13793 sscanf(p+4, "%[^;]", bestMove);
13794 } else *bestMove = NULLCHAR;
13795 if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13796 sscanf(p+4, "%[^;]", avoidMove);
13797 } else *avoidMove = NULLCHAR;
13799 (void) fgets(line, MSG_SIZ, f);
13800 (void) fgets(line, MSG_SIZ, f);
13802 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13803 (void) fgets(line, MSG_SIZ, f);
13804 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13807 initial_position[i][j++] = CharToPiece(*p);
13811 blackPlaysFirst = FALSE;
13813 (void) fgets(line, MSG_SIZ, f);
13814 if (strncmp(line, "black", strlen("black"))==0)
13815 blackPlaysFirst = TRUE;
13818 startedFromSetupPosition = TRUE;
13820 CopyBoard(boards[0], initial_position);
13821 if (blackPlaysFirst) {
13822 currentMove = forwardMostMove = backwardMostMove = 1;
13823 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13824 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13825 CopyBoard(boards[1], initial_position);
13826 DisplayMessage("", _("Black to play"));
13828 currentMove = forwardMostMove = backwardMostMove = 0;
13829 DisplayMessage("", _("White to play"));
13831 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13832 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13833 SendToProgram("force\n", &first);
13834 SendBoard(&first, forwardMostMove);
13836 if (appData.debugMode) {
13838 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13839 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13840 fprintf(debugFP, "Load Position\n");
13843 if (positionNumber > 1) {
13844 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13845 DisplayTitle(line);
13847 DisplayTitle(title);
13849 gameMode = EditGame;
13852 timeRemaining[0][1] = whiteTimeRemaining;
13853 timeRemaining[1][1] = blackTimeRemaining;
13854 DrawPosition(FALSE, boards[currentMove]);
13861 CopyPlayerNameIntoFileName (char **dest, char *src)
13863 while (*src != NULLCHAR && *src != ',') {
13868 *(*dest)++ = *src++;
13874 DefaultFileName (char *ext)
13876 static char def[MSG_SIZ];
13879 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13881 CopyPlayerNameIntoFileName(&p, gameInfo.white);
13883 CopyPlayerNameIntoFileName(&p, gameInfo.black);
13885 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13892 /* Save the current game to the given file */
13894 SaveGameToFile (char *filename, int append)
13898 int result, i, t,tot=0;
13900 if (strcmp(filename, "-") == 0) {
13901 return SaveGame(stdout, 0, NULL);
13903 for(i=0; i<10; i++) { // upto 10 tries
13904 f = fopen(filename, append ? "a" : "w");
13905 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13906 if(f || errno != 13) break;
13907 DoSleep(t = 5 + random()%11); // wait 5-15 msec
13911 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13912 DisplayError(buf, errno);
13915 safeStrCpy(buf, lastMsg, MSG_SIZ);
13916 DisplayMessage(_("Waiting for access to save file"), "");
13917 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13918 DisplayMessage(_("Saving game"), "");
13919 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
13920 result = SaveGame(f, 0, NULL);
13921 DisplayMessage(buf, "");
13928 SavePart (char *str)
13930 static char buf[MSG_SIZ];
13933 p = strchr(str, ' ');
13934 if (p == NULL) return str;
13935 strncpy(buf, str, p - str);
13936 buf[p - str] = NULLCHAR;
13940 #define PGN_MAX_LINE 75
13942 #define PGN_SIDE_WHITE 0
13943 #define PGN_SIDE_BLACK 1
13946 FindFirstMoveOutOfBook (int side)
13950 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13951 int index = backwardMostMove;
13952 int has_book_hit = 0;
13954 if( (index % 2) != side ) {
13958 while( index < forwardMostMove ) {
13959 /* Check to see if engine is in book */
13960 int depth = pvInfoList[index].depth;
13961 int score = pvInfoList[index].score;
13967 else if( score == 0 && depth == 63 ) {
13968 in_book = 1; /* Zappa */
13970 else if( score == 2 && depth == 99 ) {
13971 in_book = 1; /* Abrok */
13974 has_book_hit += in_book;
13990 GetOutOfBookInfo (char * buf)
13994 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13996 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13997 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
14001 if( oob[0] >= 0 || oob[1] >= 0 ) {
14002 for( i=0; i<2; i++ ) {
14006 if( i > 0 && oob[0] >= 0 ) {
14007 strcat( buf, " " );
14010 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
14011 sprintf( buf+strlen(buf), "%s%.2f",
14012 pvInfoList[idx].score >= 0 ? "+" : "",
14013 pvInfoList[idx].score / 100.0 );
14019 /* Save game in PGN style */
14021 SaveGamePGN2 (FILE *f)
14023 int i, offset, linelen, newblock;
14026 int movelen, numlen, blank;
14027 char move_buffer[100]; /* [AS] Buffer for move+PV info */
14029 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14031 PrintPGNTags(f, &gameInfo);
14033 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
14035 if (backwardMostMove > 0 || startedFromSetupPosition) {
14036 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
14037 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
14038 fprintf(f, "\n{--------------\n");
14039 PrintPosition(f, backwardMostMove);
14040 fprintf(f, "--------------}\n");
14044 /* [AS] Out of book annotation */
14045 if( appData.saveOutOfBookInfo ) {
14048 GetOutOfBookInfo( buf );
14050 if( buf[0] != '\0' ) {
14051 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
14058 i = backwardMostMove;
14062 while (i < forwardMostMove) {
14063 /* Print comments preceding this move */
14064 if (commentList[i] != NULL) {
14065 if (linelen > 0) fprintf(f, "\n");
14066 fprintf(f, "%s", commentList[i]);
14071 /* Format move number */
14073 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
14076 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
14078 numtext[0] = NULLCHAR;
14080 numlen = strlen(numtext);
14083 /* Print move number */
14084 blank = linelen > 0 && numlen > 0;
14085 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
14094 fprintf(f, "%s", numtext);
14098 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
14099 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
14102 blank = linelen > 0 && movelen > 0;
14103 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14112 fprintf(f, "%s", move_buffer);
14113 linelen += movelen;
14115 /* [AS] Add PV info if present */
14116 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
14117 /* [HGM] add time */
14118 char buf[MSG_SIZ]; int seconds;
14120 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
14126 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
14129 seconds = (seconds + 4)/10; // round to full seconds
14131 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
14133 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
14136 if(appData.cumulativeTimePGN) {
14137 snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
14140 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
14141 pvInfoList[i].score >= 0 ? "+" : "",
14142 pvInfoList[i].score / 100.0,
14143 pvInfoList[i].depth,
14146 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
14148 /* Print score/depth */
14149 blank = linelen > 0 && movelen > 0;
14150 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14159 fprintf(f, "%s", move_buffer);
14160 linelen += movelen;
14166 /* Start a new line */
14167 if (linelen > 0) fprintf(f, "\n");
14169 /* Print comments after last move */
14170 if (commentList[i] != NULL) {
14171 fprintf(f, "%s\n", commentList[i]);
14175 if (gameInfo.resultDetails != NULL &&
14176 gameInfo.resultDetails[0] != NULLCHAR) {
14177 char buf[MSG_SIZ], *p = gameInfo.resultDetails;
14178 if(gameInfo.result == GameUnfinished && appData.clockMode &&
14179 (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
14180 snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
14181 fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
14183 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14187 /* Save game in PGN style and close the file */
14189 SaveGamePGN (FILE *f)
14193 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14197 /* Save game in old style and close the file */
14199 SaveGameOldStyle (FILE *f)
14204 tm = time((time_t *) NULL);
14206 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
14209 if (backwardMostMove > 0 || startedFromSetupPosition) {
14210 fprintf(f, "\n[--------------\n");
14211 PrintPosition(f, backwardMostMove);
14212 fprintf(f, "--------------]\n");
14217 i = backwardMostMove;
14218 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14220 while (i < forwardMostMove) {
14221 if (commentList[i] != NULL) {
14222 fprintf(f, "[%s]\n", commentList[i]);
14225 if ((i % 2) == 1) {
14226 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
14229 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
14231 if (commentList[i] != NULL) {
14235 if (i >= forwardMostMove) {
14239 fprintf(f, "%s\n", parseList[i]);
14244 if (commentList[i] != NULL) {
14245 fprintf(f, "[%s]\n", commentList[i]);
14248 /* This isn't really the old style, but it's close enough */
14249 if (gameInfo.resultDetails != NULL &&
14250 gameInfo.resultDetails[0] != NULLCHAR) {
14251 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14252 gameInfo.resultDetails);
14254 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14261 /* Save the current game to open file f and close the file */
14263 SaveGame (FILE *f, int dummy, char *dummy2)
14265 if (gameMode == EditPosition) EditPositionDone(TRUE);
14266 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14267 if (appData.oldSaveStyle)
14268 return SaveGameOldStyle(f);
14270 return SaveGamePGN(f);
14273 /* Save the current position to the given file */
14275 SavePositionToFile (char *filename)
14280 if (strcmp(filename, "-") == 0) {
14281 return SavePosition(stdout, 0, NULL);
14283 f = fopen(filename, "a");
14285 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14286 DisplayError(buf, errno);
14289 safeStrCpy(buf, lastMsg, MSG_SIZ);
14290 DisplayMessage(_("Waiting for access to save file"), "");
14291 flock(fileno(f), LOCK_EX); // [HGM] lock
14292 DisplayMessage(_("Saving position"), "");
14293 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
14294 SavePosition(f, 0, NULL);
14295 DisplayMessage(buf, "");
14301 /* Save the current position to the given open file and close the file */
14303 SavePosition (FILE *f, int dummy, char *dummy2)
14308 if (gameMode == EditPosition) EditPositionDone(TRUE);
14309 if (appData.oldSaveStyle) {
14310 tm = time((time_t *) NULL);
14312 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14314 fprintf(f, "[--------------\n");
14315 PrintPosition(f, currentMove);
14316 fprintf(f, "--------------]\n");
14318 fen = PositionToFEN(currentMove, NULL, 1);
14319 fprintf(f, "%s\n", fen);
14327 ReloadCmailMsgEvent (int unregister)
14330 static char *inFilename = NULL;
14331 static char *outFilename;
14333 struct stat inbuf, outbuf;
14336 /* Any registered moves are unregistered if unregister is set, */
14337 /* i.e. invoked by the signal handler */
14339 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14340 cmailMoveRegistered[i] = FALSE;
14341 if (cmailCommentList[i] != NULL) {
14342 free(cmailCommentList[i]);
14343 cmailCommentList[i] = NULL;
14346 nCmailMovesRegistered = 0;
14349 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14350 cmailResult[i] = CMAIL_NOT_RESULT;
14354 if (inFilename == NULL) {
14355 /* Because the filenames are static they only get malloced once */
14356 /* and they never get freed */
14357 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14358 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14360 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14361 sprintf(outFilename, "%s.out", appData.cmailGameName);
14364 status = stat(outFilename, &outbuf);
14366 cmailMailedMove = FALSE;
14368 status = stat(inFilename, &inbuf);
14369 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14372 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14373 counts the games, notes how each one terminated, etc.
14375 It would be nice to remove this kludge and instead gather all
14376 the information while building the game list. (And to keep it
14377 in the game list nodes instead of having a bunch of fixed-size
14378 parallel arrays.) Note this will require getting each game's
14379 termination from the PGN tags, as the game list builder does
14380 not process the game moves. --mann
14382 cmailMsgLoaded = TRUE;
14383 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14385 /* Load first game in the file or popup game menu */
14386 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14388 #endif /* !WIN32 */
14396 char string[MSG_SIZ];
14398 if ( cmailMailedMove
14399 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14400 return TRUE; /* Allow free viewing */
14403 /* Unregister move to ensure that we don't leave RegisterMove */
14404 /* with the move registered when the conditions for registering no */
14406 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14407 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14408 nCmailMovesRegistered --;
14410 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14412 free(cmailCommentList[lastLoadGameNumber - 1]);
14413 cmailCommentList[lastLoadGameNumber - 1] = NULL;
14417 if (cmailOldMove == -1) {
14418 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14422 if (currentMove > cmailOldMove + 1) {
14423 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14427 if (currentMove < cmailOldMove) {
14428 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14432 if (forwardMostMove > currentMove) {
14433 /* Silently truncate extra moves */
14437 if ( (currentMove == cmailOldMove + 1)
14438 || ( (currentMove == cmailOldMove)
14439 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14440 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14441 if (gameInfo.result != GameUnfinished) {
14442 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14445 if (commentList[currentMove] != NULL) {
14446 cmailCommentList[lastLoadGameNumber - 1]
14447 = StrSave(commentList[currentMove]);
14449 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14451 if (appData.debugMode)
14452 fprintf(debugFP, "Saving %s for game %d\n",
14453 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14455 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14457 f = fopen(string, "w");
14458 if (appData.oldSaveStyle) {
14459 SaveGameOldStyle(f); /* also closes the file */
14461 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14462 f = fopen(string, "w");
14463 SavePosition(f, 0, NULL); /* also closes the file */
14465 fprintf(f, "{--------------\n");
14466 PrintPosition(f, currentMove);
14467 fprintf(f, "--------------}\n\n");
14469 SaveGame(f, 0, NULL); /* also closes the file*/
14472 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14473 nCmailMovesRegistered ++;
14474 } else if (nCmailGames == 1) {
14475 DisplayError(_("You have not made a move yet"), 0);
14486 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14487 FILE *commandOutput;
14488 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14489 int nBytes = 0; /* Suppress warnings on uninitialized variables */
14495 if (! cmailMsgLoaded) {
14496 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14500 if (nCmailGames == nCmailResults) {
14501 DisplayError(_("No unfinished games"), 0);
14505 #if CMAIL_PROHIBIT_REMAIL
14506 if (cmailMailedMove) {
14507 snprintf(msg, MSG_SIZ, _("You have already mailed a move.\nWait until a move arrives from your opponent.\nTo resend the same move, type\n\"cmail -remail -game %s\"\non the command line."), appData.cmailGameName);
14508 DisplayError(msg, 0);
14513 if (! (cmailMailedMove || RegisterMove())) return;
14515 if ( cmailMailedMove
14516 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14517 snprintf(string, MSG_SIZ, partCommandString,
14518 appData.debugMode ? " -v" : "", appData.cmailGameName);
14519 commandOutput = popen(string, "r");
14521 if (commandOutput == NULL) {
14522 DisplayError(_("Failed to invoke cmail"), 0);
14524 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14525 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14527 if (nBuffers > 1) {
14528 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14529 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14530 nBytes = MSG_SIZ - 1;
14532 (void) memcpy(msg, buffer, nBytes);
14534 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14536 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14537 cmailMailedMove = TRUE; /* Prevent >1 moves */
14540 for (i = 0; i < nCmailGames; i ++) {
14541 if (cmailResult[i] == CMAIL_NOT_RESULT) {
14546 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14548 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14550 appData.cmailGameName,
14552 LoadGameFromFile(buffer, 1, buffer, FALSE);
14553 cmailMsgLoaded = FALSE;
14557 DisplayInformation(msg);
14558 pclose(commandOutput);
14561 if ((*cmailMsg) != '\0') {
14562 DisplayInformation(cmailMsg);
14567 #endif /* !WIN32 */
14576 int prependComma = 0;
14578 char string[MSG_SIZ]; /* Space for game-list */
14581 if (!cmailMsgLoaded) return "";
14583 if (cmailMailedMove) {
14584 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14586 /* Create a list of games left */
14587 snprintf(string, MSG_SIZ, "[");
14588 for (i = 0; i < nCmailGames; i ++) {
14589 if (! ( cmailMoveRegistered[i]
14590 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14591 if (prependComma) {
14592 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14594 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14598 strcat(string, number);
14601 strcat(string, "]");
14603 if (nCmailMovesRegistered + nCmailResults == 0) {
14604 switch (nCmailGames) {
14606 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14610 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14614 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14619 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14621 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14626 if (nCmailResults == nCmailGames) {
14627 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14629 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14634 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14646 if (gameMode == Training)
14647 SetTrainingModeOff();
14650 cmailMsgLoaded = FALSE;
14651 if (appData.icsActive) {
14652 SendToICS(ics_prefix);
14653 SendToICS("refresh\n");
14658 ExitEvent (int status)
14662 /* Give up on clean exit */
14666 /* Keep trying for clean exit */
14670 if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14671 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14673 if (telnetISR != NULL) {
14674 RemoveInputSource(telnetISR);
14676 if (icsPR != NoProc) {
14677 DestroyChildProcess(icsPR, TRUE);
14680 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14681 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14683 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14684 /* make sure this other one finishes before killing it! */
14685 if(endingGame) { int count = 0;
14686 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14687 while(endingGame && count++ < 10) DoSleep(1);
14688 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14691 /* Kill off chess programs */
14692 if (first.pr != NoProc) {
14695 DoSleep( appData.delayBeforeQuit );
14696 SendToProgram("quit\n", &first);
14697 DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14699 if (second.pr != NoProc) {
14700 DoSleep( appData.delayBeforeQuit );
14701 SendToProgram("quit\n", &second);
14702 DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14704 if (first.isr != NULL) {
14705 RemoveInputSource(first.isr);
14707 if (second.isr != NULL) {
14708 RemoveInputSource(second.isr);
14711 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14712 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14714 ShutDownFrontEnd();
14719 PauseEngine (ChessProgramState *cps)
14721 SendToProgram("pause\n", cps);
14726 UnPauseEngine (ChessProgramState *cps)
14728 SendToProgram("resume\n", cps);
14735 if (appData.debugMode)
14736 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14740 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14742 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14743 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14744 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14746 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14747 HandleMachineMove(stashedInputMove, stalledEngine);
14748 stalledEngine = NULL;
14751 if (gameMode == MachinePlaysWhite ||
14752 gameMode == TwoMachinesPlay ||
14753 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14754 if(first.pause) UnPauseEngine(&first);
14755 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14756 if(second.pause) UnPauseEngine(&second);
14757 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14760 DisplayBothClocks();
14762 if (gameMode == PlayFromGameFile) {
14763 if (appData.timeDelay >= 0)
14764 AutoPlayGameLoop();
14765 } else if (gameMode == IcsExamining && pauseExamInvalid) {
14766 Reset(FALSE, TRUE);
14767 SendToICS(ics_prefix);
14768 SendToICS("refresh\n");
14769 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14770 ForwardInner(forwardMostMove);
14772 pauseExamInvalid = FALSE;
14774 switch (gameMode) {
14778 pauseExamForwardMostMove = forwardMostMove;
14779 pauseExamInvalid = FALSE;
14782 case IcsPlayingWhite:
14783 case IcsPlayingBlack:
14787 case PlayFromGameFile:
14788 (void) StopLoadGameTimer();
14792 case BeginningOfGame:
14793 if (appData.icsActive) return;
14794 /* else fall through */
14795 case MachinePlaysWhite:
14796 case MachinePlaysBlack:
14797 case TwoMachinesPlay:
14798 if (forwardMostMove == 0)
14799 return; /* don't pause if no one has moved */
14800 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14801 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14802 if(onMove->pause) { // thinking engine can be paused
14803 PauseEngine(onMove); // do it
14804 if(onMove->other->pause) // pondering opponent can always be paused immediately
14805 PauseEngine(onMove->other);
14807 SendToProgram("easy\n", onMove->other);
14809 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14810 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14812 PauseEngine(&first);
14814 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14815 } else { // human on move, pause pondering by either method
14817 PauseEngine(&first);
14818 else if(appData.ponderNextMove)
14819 SendToProgram("easy\n", &first);
14822 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14832 EditCommentEvent ()
14834 char title[MSG_SIZ];
14836 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14837 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14839 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14840 WhiteOnMove(currentMove - 1) ? " " : ".. ",
14841 parseList[currentMove - 1]);
14844 EditCommentPopUp(currentMove, title, commentList[currentMove]);
14851 char *tags = PGNTags(&gameInfo);
14853 EditTagsPopUp(tags, NULL);
14860 if(WaitForEngine(&second, StartSecond)) return;
14861 InitChessProgram(&second, FALSE);
14862 FeedMovesToProgram(&second, currentMove);
14864 SendToProgram("analyze\n", &second);
14865 second.analyzing = TRUE;
14872 if(second.analyzing) {
14873 SendToProgram("exit\n", &second);
14874 second.analyzing = FALSE;
14880 /* Toggle ShowThinking */
14882 ToggleShowThinking()
14884 appData.showThinking = !appData.showThinking;
14885 ShowThinkingEvent();
14889 AnalyzeModeEvent ()
14893 if (!first.analysisSupport) {
14894 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14895 DisplayError(buf, 0);
14898 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14899 if (appData.icsActive) {
14900 if (gameMode != IcsObserving) {
14901 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14902 DisplayError(buf, 0);
14904 if (appData.icsEngineAnalyze) {
14905 if (appData.debugMode)
14906 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14912 /* if enable, user wants to disable icsEngineAnalyze */
14913 if (appData.icsEngineAnalyze) {
14918 appData.icsEngineAnalyze = TRUE;
14919 if (appData.debugMode)
14920 fprintf(debugFP, "ICS engine analyze starting... \n");
14923 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14924 if (appData.noChessProgram || gameMode == AnalyzeMode)
14927 if (gameMode != AnalyzeFile) {
14928 if (!appData.icsEngineAnalyze) {
14930 if (gameMode != EditGame) return 0;
14932 if (!appData.showThinking) ToggleShowThinking();
14933 ResurrectChessProgram();
14934 SendToProgram("analyze\n", &first);
14935 first.analyzing = TRUE;
14936 /*first.maybeThinking = TRUE;*/
14937 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14938 EngineOutputPopUp();
14940 if (!appData.icsEngineAnalyze) {
14941 gameMode = AnalyzeMode;
14942 ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14948 StartAnalysisClock();
14949 GetTimeMark(&lastNodeCountTime);
14955 AnalyzeFileEvent ()
14957 if (appData.noChessProgram || gameMode == AnalyzeFile)
14960 if (!first.analysisSupport) {
14962 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14963 DisplayError(buf, 0);
14967 if (gameMode != AnalyzeMode) {
14968 keepInfo = 1; // mere annotating should not alter PGN tags
14971 if (gameMode != EditGame) return;
14972 if (!appData.showThinking) ToggleShowThinking();
14973 ResurrectChessProgram();
14974 SendToProgram("analyze\n", &first);
14975 first.analyzing = TRUE;
14976 /*first.maybeThinking = TRUE;*/
14977 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14978 EngineOutputPopUp();
14980 gameMode = AnalyzeFile;
14984 StartAnalysisClock();
14985 GetTimeMark(&lastNodeCountTime);
14987 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14988 AnalysisPeriodicEvent(1);
14992 MachineWhiteEvent ()
14995 char *bookHit = NULL;
14997 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
15001 if (gameMode == PlayFromGameFile ||
15002 gameMode == TwoMachinesPlay ||
15003 gameMode == Training ||
15004 gameMode == AnalyzeMode ||
15005 gameMode == EndOfGame)
15008 if (gameMode == EditPosition)
15009 EditPositionDone(TRUE);
15011 if (!WhiteOnMove(currentMove)) {
15012 DisplayError(_("It is not White's turn"), 0);
15016 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
15019 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15020 gameMode == AnalyzeFile)
15023 ResurrectChessProgram(); /* in case it isn't running */
15024 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
15025 gameMode = MachinePlaysWhite;
15028 gameMode = MachinePlaysWhite;
15032 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15034 if (first.sendName) {
15035 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
15036 SendToProgram(buf, &first);
15038 if (first.sendTime) {
15039 if (first.useColors) {
15040 SendToProgram("black\n", &first); /*gnu kludge*/
15042 SendTimeRemaining(&first, TRUE);
15044 if (first.useColors) {
15045 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
15047 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
15048 SetMachineThinkingEnables();
15049 first.maybeThinking = TRUE;
15053 if (appData.autoFlipView && !flipView) {
15054 flipView = !flipView;
15055 DrawPosition(FALSE, NULL);
15056 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
15059 if(bookHit) { // [HGM] book: simulate book reply
15060 static char bookMove[MSG_SIZ]; // a bit generous?
15062 programStats.nodes = programStats.depth = programStats.time =
15063 programStats.score = programStats.got_only_move = 0;
15064 sprintf(programStats.movelist, "%s (xbook)", bookHit);
15066 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15067 strcat(bookMove, bookHit);
15068 savedMessage = bookMove; // args for deferred call
15069 savedState = &first;
15070 ScheduleDelayedEvent(DeferredBookMove, 1);
15075 MachineBlackEvent ()
15078 char *bookHit = NULL;
15080 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
15084 if (gameMode == PlayFromGameFile ||
15085 gameMode == TwoMachinesPlay ||
15086 gameMode == Training ||
15087 gameMode == AnalyzeMode ||
15088 gameMode == EndOfGame)
15091 if (gameMode == EditPosition)
15092 EditPositionDone(TRUE);
15094 if (WhiteOnMove(currentMove)) {
15095 DisplayError(_("It is not Black's turn"), 0);
15099 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
15102 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15103 gameMode == AnalyzeFile)
15106 ResurrectChessProgram(); /* in case it isn't running */
15107 gameMode = MachinePlaysBlack;
15111 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15113 if (first.sendName) {
15114 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
15115 SendToProgram(buf, &first);
15117 if (first.sendTime) {
15118 if (first.useColors) {
15119 SendToProgram("white\n", &first); /*gnu kludge*/
15121 SendTimeRemaining(&first, FALSE);
15123 if (first.useColors) {
15124 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
15126 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
15127 SetMachineThinkingEnables();
15128 first.maybeThinking = TRUE;
15131 if (appData.autoFlipView && flipView) {
15132 flipView = !flipView;
15133 DrawPosition(FALSE, NULL);
15134 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
15136 if(bookHit) { // [HGM] book: simulate book reply
15137 static char bookMove[MSG_SIZ]; // a bit generous?
15139 programStats.nodes = programStats.depth = programStats.time =
15140 programStats.score = programStats.got_only_move = 0;
15141 sprintf(programStats.movelist, "%s (xbook)", bookHit);
15143 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15144 strcat(bookMove, bookHit);
15145 savedMessage = bookMove; // args for deferred call
15146 savedState = &first;
15147 ScheduleDelayedEvent(DeferredBookMove, 1);
15153 DisplayTwoMachinesTitle ()
15156 if (appData.matchGames > 0) {
15157 if(appData.tourneyFile[0]) {
15158 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
15159 gameInfo.white, _("vs."), gameInfo.black,
15160 nextGame+1, appData.matchGames+1,
15161 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
15163 if (first.twoMachinesColor[0] == 'w') {
15164 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15165 gameInfo.white, _("vs."), gameInfo.black,
15166 first.matchWins, second.matchWins,
15167 matchGame - 1 - (first.matchWins + second.matchWins));
15169 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15170 gameInfo.white, _("vs."), gameInfo.black,
15171 second.matchWins, first.matchWins,
15172 matchGame - 1 - (first.matchWins + second.matchWins));
15175 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15181 SettingsMenuIfReady ()
15183 if (second.lastPing != second.lastPong) {
15184 DisplayMessage("", _("Waiting for second chess program"));
15185 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
15189 DisplayMessage("", "");
15190 SettingsPopUp(&second);
15194 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
15197 if (cps->pr == NoProc) {
15198 StartChessProgram(cps);
15199 if (cps->protocolVersion == 1) {
15201 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
15203 /* kludge: allow timeout for initial "feature" command */
15204 if(retry != TwoMachinesEventIfReady) FreezeUI();
15205 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
15206 DisplayMessage("", buf);
15207 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
15215 TwoMachinesEvent P((void))
15217 int i, move = forwardMostMove;
15219 ChessProgramState *onmove;
15220 char *bookHit = NULL;
15221 static int stalling = 0;
15225 if (appData.noChessProgram) return;
15227 switch (gameMode) {
15228 case TwoMachinesPlay:
15230 case MachinePlaysWhite:
15231 case MachinePlaysBlack:
15232 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15233 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15237 case BeginningOfGame:
15238 case PlayFromGameFile:
15241 if (gameMode != EditGame) return;
15244 EditPositionDone(TRUE);
15255 // forwardMostMove = currentMove;
15256 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15257 startingEngine = TRUE;
15259 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15261 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15262 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15263 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15267 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15269 if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15270 gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15271 startingEngine = matchMode = FALSE;
15272 DisplayError("second engine does not play this", 0);
15273 gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15274 EditGameEvent(); // switch back to EditGame mode
15279 InitChessProgram(&second, FALSE); // unbalances ping of second engine
15280 SendToProgram("force\n", &second);
15282 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15286 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15287 if(appData.matchPause>10000 || appData.matchPause<10)
15288 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15289 wait = SubtractTimeMarks(&now, &pauseStart);
15290 if(wait < appData.matchPause) {
15291 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15294 // we are now committed to starting the game
15296 DisplayMessage("", "");
15298 if (startedFromSetupPosition) {
15299 SendBoard(&second, backwardMostMove);
15300 if (appData.debugMode) {
15301 fprintf(debugFP, "Two Machines\n");
15304 for (i = backwardMostMove; i < forwardMostMove; i++) {
15305 SendMoveToProgram(i, &second);
15309 gameMode = TwoMachinesPlay;
15310 pausing = startingEngine = FALSE;
15311 ModeHighlight(); // [HGM] logo: this triggers display update of logos
15313 DisplayTwoMachinesTitle();
15315 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15320 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15321 SendToProgram(first.computerString, &first);
15322 if (first.sendName) {
15323 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15324 SendToProgram(buf, &first);
15327 SendToProgram(second.computerString, &second);
15328 if (second.sendName) {
15329 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15330 SendToProgram(buf, &second);
15334 if (!first.sendTime || !second.sendTime || move == 0) { // [HGM] first engine changed sides from Reset, so recalc time odds
15336 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15337 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15339 if (onmove->sendTime) {
15340 if (onmove->useColors) {
15341 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15343 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15345 if (onmove->useColors) {
15346 SendToProgram(onmove->twoMachinesColor, onmove);
15348 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15349 // SendToProgram("go\n", onmove);
15350 onmove->maybeThinking = TRUE;
15351 SetMachineThinkingEnables();
15355 if(bookHit) { // [HGM] book: simulate book reply
15356 static char bookMove[MSG_SIZ]; // a bit generous?
15358 programStats.nodes = programStats.depth = programStats.time =
15359 programStats.score = programStats.got_only_move = 0;
15360 sprintf(programStats.movelist, "%s (xbook)", bookHit);
15362 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15363 strcat(bookMove, bookHit);
15364 savedMessage = bookMove; // args for deferred call
15365 savedState = onmove;
15366 ScheduleDelayedEvent(DeferredBookMove, 1);
15373 if (gameMode == Training) {
15374 SetTrainingModeOff();
15375 gameMode = PlayFromGameFile;
15376 DisplayMessage("", _("Training mode off"));
15378 gameMode = Training;
15379 animateTraining = appData.animate;
15381 /* make sure we are not already at the end of the game */
15382 if (currentMove < forwardMostMove) {
15383 SetTrainingModeOn();
15384 DisplayMessage("", _("Training mode on"));
15386 gameMode = PlayFromGameFile;
15387 DisplayError(_("Already at end of game"), 0);
15396 if (!appData.icsActive) return;
15397 switch (gameMode) {
15398 case IcsPlayingWhite:
15399 case IcsPlayingBlack:
15402 case BeginningOfGame:
15410 EditPositionDone(TRUE);
15423 gameMode = IcsIdle;
15433 switch (gameMode) {
15435 SetTrainingModeOff();
15437 case MachinePlaysWhite:
15438 case MachinePlaysBlack:
15439 case BeginningOfGame:
15440 SendToProgram("force\n", &first);
15441 if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15442 if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15444 abortEngineThink = TRUE;
15445 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15446 SendToProgram(buf, &first);
15447 DisplayMessage("Aborting engine think", "");
15451 SetUserThinkingEnables();
15453 case PlayFromGameFile:
15454 (void) StopLoadGameTimer();
15455 if (gameFileFP != NULL) {
15460 EditPositionDone(TRUE);
15465 SendToProgram("force\n", &first);
15467 case TwoMachinesPlay:
15468 GameEnds(EndOfFile, NULL, GE_PLAYER);
15469 ResurrectChessProgram();
15470 SetUserThinkingEnables();
15473 ResurrectChessProgram();
15475 case IcsPlayingBlack:
15476 case IcsPlayingWhite:
15477 DisplayError(_("Warning: You are still playing a game"), 0);
15480 DisplayError(_("Warning: You are still observing a game"), 0);
15483 DisplayError(_("Warning: You are still examining a game"), 0);
15494 first.offeredDraw = second.offeredDraw = 0;
15496 if (gameMode == PlayFromGameFile) {
15497 whiteTimeRemaining = timeRemaining[0][currentMove];
15498 blackTimeRemaining = timeRemaining[1][currentMove];
15502 if (gameMode == MachinePlaysWhite ||
15503 gameMode == MachinePlaysBlack ||
15504 gameMode == TwoMachinesPlay ||
15505 gameMode == EndOfGame) {
15506 i = forwardMostMove;
15507 while (i > currentMove) {
15508 SendToProgram("undo\n", &first);
15511 if(!adjustedClock) {
15512 whiteTimeRemaining = timeRemaining[0][currentMove];
15513 blackTimeRemaining = timeRemaining[1][currentMove];
15514 DisplayBothClocks();
15516 if (whiteFlag || blackFlag) {
15517 whiteFlag = blackFlag = 0;
15522 gameMode = EditGame;
15528 EditPositionEvent ()
15531 if (gameMode == EditPosition) {
15537 if (gameMode != EditGame) return;
15539 gameMode = EditPosition;
15542 CopyBoard(rightsBoard, nullBoard);
15543 if (currentMove > 0)
15544 CopyBoard(boards[0], boards[currentMove]);
15545 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15546 rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15548 blackPlaysFirst = !WhiteOnMove(currentMove);
15550 currentMove = forwardMostMove = backwardMostMove = 0;
15551 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15553 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15559 /* [DM] icsEngineAnalyze - possible call from other functions */
15560 if (appData.icsEngineAnalyze) {
15561 appData.icsEngineAnalyze = FALSE;
15563 DisplayMessage("",_("Close ICS engine analyze..."));
15565 if (first.analysisSupport && first.analyzing) {
15566 SendToBoth("exit\n");
15567 first.analyzing = second.analyzing = FALSE;
15569 thinkOutput[0] = NULLCHAR;
15573 EditPositionDone (Boolean fakeRights)
15575 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15577 startedFromSetupPosition = TRUE;
15578 InitChessProgram(&first, FALSE);
15579 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15581 boards[0][EP_STATUS] = EP_NONE;
15582 for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
15583 for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
15584 if(rightsBoard[r][f]) {
15585 ChessSquare p = boards[0][r][f];
15586 if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
15587 else if(p == king) boards[0][CASTLING][2] = f;
15588 else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
15589 else rightsBoard[r][f] = 2; // mark for second pass
15592 for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
15593 if(rightsBoard[r][f] == 2) {
15594 ChessSquare p = boards[0][r][f];
15595 if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
15596 if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
15600 SendToProgram("force\n", &first);
15601 if (blackPlaysFirst) {
15602 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15603 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15604 currentMove = forwardMostMove = backwardMostMove = 1;
15605 CopyBoard(boards[1], boards[0]);
15607 currentMove = forwardMostMove = backwardMostMove = 0;
15609 SendBoard(&first, forwardMostMove);
15610 if (appData.debugMode) {
15611 fprintf(debugFP, "EditPosDone\n");
15614 DisplayMessage("", "");
15615 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15616 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15617 gameMode = EditGame;
15619 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15620 ClearHighlights(); /* [AS] */
15623 /* Pause for `ms' milliseconds */
15624 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15626 TimeDelay (long ms)
15633 } while (SubtractTimeMarks(&m2, &m1) < ms);
15636 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15638 SendMultiLineToICS (char *buf)
15640 char temp[MSG_SIZ+1], *p;
15647 strncpy(temp, buf, len);
15652 if (*p == '\n' || *p == '\r')
15657 strcat(temp, "\n");
15659 SendToPlayer(temp, strlen(temp));
15663 SetWhiteToPlayEvent ()
15665 if (gameMode == EditPosition) {
15666 blackPlaysFirst = FALSE;
15667 DisplayBothClocks(); /* works because currentMove is 0 */
15668 } else if (gameMode == IcsExamining) {
15669 SendToICS(ics_prefix);
15670 SendToICS("tomove white\n");
15675 SetBlackToPlayEvent ()
15677 if (gameMode == EditPosition) {
15678 blackPlaysFirst = TRUE;
15679 currentMove = 1; /* kludge */
15680 DisplayBothClocks();
15682 } else if (gameMode == IcsExamining) {
15683 SendToICS(ics_prefix);
15684 SendToICS("tomove black\n");
15689 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15692 ChessSquare piece = boards[0][y][x];
15693 static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15694 static int lastVariant;
15695 int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15697 if (gameMode != EditPosition && gameMode != IcsExamining) return;
15699 switch (selection) {
15701 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15702 MarkTargetSquares(1);
15703 CopyBoard(currentBoard, boards[0]);
15704 CopyBoard(menuBoard, initialPosition);
15705 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15706 SendToICS(ics_prefix);
15707 SendToICS("bsetup clear\n");
15708 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15709 SendToICS(ics_prefix);
15710 SendToICS("clearboard\n");
15713 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15714 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15715 for (y = 0; y < BOARD_HEIGHT; y++) {
15716 if (gameMode == IcsExamining) {
15717 if (boards[currentMove][y][x] != EmptySquare) {
15718 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15722 } else if(boards[0][y][x] != DarkSquare) {
15723 if(boards[0][y][x] != p) nonEmpty++;
15724 boards[0][y][x] = p;
15728 CopyBoard(rightsBoard, nullBoard);
15729 if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15731 for(r = 0; r < BOARD_HEIGHT; r++) {
15732 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
15733 ChessSquare p = menuBoard[r][x];
15734 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15737 menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
15738 DisplayMessage("Clicking clock again restores position", "");
15739 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15740 if(!nonEmpty) { // asked to clear an empty board
15741 CopyBoard(boards[0], menuBoard);
15743 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15744 CopyBoard(boards[0], initialPosition);
15746 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15747 && !CompareBoards(nullBoard, erasedBoard)) {
15748 CopyBoard(boards[0], erasedBoard);
15750 CopyBoard(erasedBoard, currentBoard);
15752 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15753 rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15756 if (gameMode == EditPosition) {
15757 DrawPosition(FALSE, boards[0]);
15762 SetWhiteToPlayEvent();
15766 SetBlackToPlayEvent();
15770 if (gameMode == IcsExamining) {
15771 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15772 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15775 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15776 if(x == BOARD_LEFT-2) {
15777 if(y < handSize-1-gameInfo.holdingsSize) break;
15778 boards[0][y][1] = 0;
15780 if(x == BOARD_RGHT+1) {
15781 if(y >= gameInfo.holdingsSize) break;
15782 boards[0][y][BOARD_WIDTH-2] = 0;
15785 boards[0][y][x] = EmptySquare;
15786 DrawPosition(FALSE, boards[0]);
15791 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15792 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
15793 selection = (ChessSquare) (PROMOTED(piece));
15794 } else if(piece == EmptySquare) selection = WhiteSilver;
15795 else selection = (ChessSquare)((int)piece - 1);
15799 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15800 piece > (int)BlackMan && piece <= (int)BlackKing ) {
15801 selection = (ChessSquare) (DEMOTED(piece));
15802 } else if(piece == EmptySquare) selection = BlackSilver;
15803 else selection = (ChessSquare)((int)piece + 1);
15808 if(gameInfo.variant == VariantShatranj ||
15809 gameInfo.variant == VariantXiangqi ||
15810 gameInfo.variant == VariantCourier ||
15811 gameInfo.variant == VariantASEAN ||
15812 gameInfo.variant == VariantMakruk )
15813 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15819 if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15820 if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15826 if(gameInfo.variant == VariantXiangqi)
15827 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15828 if(gameInfo.variant == VariantKnightmate)
15829 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15830 if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15833 if (gameMode == IcsExamining) {
15834 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15835 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15836 PieceToChar(selection), AAA + x, ONE + y);
15839 rightsBoard[y][x] = hasRights;
15840 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15842 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15843 n = PieceToNumber(selection - BlackPawn);
15844 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15845 boards[0][handSize-1-n][0] = selection;
15846 boards[0][handSize-1-n][1]++;
15848 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15849 n = PieceToNumber(selection);
15850 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15851 boards[0][n][BOARD_WIDTH-1] = selection;
15852 boards[0][n][BOARD_WIDTH-2]++;
15855 boards[0][y][x] = selection;
15856 DrawPosition(TRUE, boards[0]);
15858 fromX = fromY = -1;
15866 DropMenuEvent (ChessSquare selection, int x, int y)
15868 ChessMove moveType;
15870 switch (gameMode) {
15871 case IcsPlayingWhite:
15872 case MachinePlaysBlack:
15873 if (!WhiteOnMove(currentMove)) {
15874 DisplayMoveError(_("It is Black's turn"));
15877 moveType = WhiteDrop;
15879 case IcsPlayingBlack:
15880 case MachinePlaysWhite:
15881 if (WhiteOnMove(currentMove)) {
15882 DisplayMoveError(_("It is White's turn"));
15885 moveType = BlackDrop;
15888 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15894 if (moveType == BlackDrop && selection < BlackPawn) {
15895 selection = (ChessSquare) ((int) selection
15896 + (int) BlackPawn - (int) WhitePawn);
15898 if (boards[currentMove][y][x] != EmptySquare) {
15899 DisplayMoveError(_("That square is occupied"));
15903 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15909 /* Accept a pending offer of any kind from opponent */
15911 if (appData.icsActive) {
15912 SendToICS(ics_prefix);
15913 SendToICS("accept\n");
15914 } else if (cmailMsgLoaded) {
15915 if (currentMove == cmailOldMove &&
15916 commentList[cmailOldMove] != NULL &&
15917 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15918 "Black offers a draw" : "White offers a draw")) {
15920 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15921 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15923 DisplayError(_("There is no pending offer on this move"), 0);
15924 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15927 /* Not used for offers from chess program */
15934 /* Decline a pending offer of any kind from opponent */
15936 if (appData.icsActive) {
15937 SendToICS(ics_prefix);
15938 SendToICS("decline\n");
15939 } else if (cmailMsgLoaded) {
15940 if (currentMove == cmailOldMove &&
15941 commentList[cmailOldMove] != NULL &&
15942 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15943 "Black offers a draw" : "White offers a draw")) {
15945 AppendComment(cmailOldMove, "Draw declined", TRUE);
15946 DisplayComment(cmailOldMove - 1, "Draw declined");
15949 DisplayError(_("There is no pending offer on this move"), 0);
15952 /* Not used for offers from chess program */
15959 /* Issue ICS rematch command */
15960 if (appData.icsActive) {
15961 SendToICS(ics_prefix);
15962 SendToICS("rematch\n");
15969 /* Call your opponent's flag (claim a win on time) */
15970 if (appData.icsActive) {
15971 SendToICS(ics_prefix);
15972 SendToICS("flag\n");
15974 switch (gameMode) {
15977 case MachinePlaysWhite:
15980 GameEnds(GameIsDrawn, "Both players ran out of time",
15983 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15985 DisplayError(_("Your opponent is not out of time"), 0);
15988 case MachinePlaysBlack:
15991 GameEnds(GameIsDrawn, "Both players ran out of time",
15994 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15996 DisplayError(_("Your opponent is not out of time"), 0);
16004 ClockClick (int which)
16005 { // [HGM] code moved to back-end from winboard.c
16006 if(which) { // black clock
16007 if (gameMode == EditPosition || gameMode == IcsExamining) {
16008 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
16009 SetBlackToPlayEvent();
16010 } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
16011 gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
16012 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
16013 } else if (shiftKey) {
16014 AdjustClock(which, -1);
16015 } else if (gameMode == IcsPlayingWhite ||
16016 gameMode == MachinePlaysBlack) {
16019 } else { // white clock
16020 if (gameMode == EditPosition || gameMode == IcsExamining) {
16021 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
16022 SetWhiteToPlayEvent();
16023 } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
16024 gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
16025 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
16026 } else if (shiftKey) {
16027 AdjustClock(which, -1);
16028 } else if (gameMode == IcsPlayingBlack ||
16029 gameMode == MachinePlaysWhite) {
16038 /* Offer draw or accept pending draw offer from opponent */
16040 if (appData.icsActive) {
16041 /* Note: tournament rules require draw offers to be
16042 made after you make your move but before you punch
16043 your clock. Currently ICS doesn't let you do that;
16044 instead, you immediately punch your clock after making
16045 a move, but you can offer a draw at any time. */
16047 SendToICS(ics_prefix);
16048 SendToICS("draw\n");
16049 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
16050 } else if (cmailMsgLoaded) {
16051 if (currentMove == cmailOldMove &&
16052 commentList[cmailOldMove] != NULL &&
16053 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
16054 "Black offers a draw" : "White offers a draw")) {
16055 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
16056 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
16057 } else if (currentMove == cmailOldMove + 1) {
16058 char *offer = WhiteOnMove(cmailOldMove) ?
16059 "White offers a draw" : "Black offers a draw";
16060 AppendComment(currentMove, offer, TRUE);
16061 DisplayComment(currentMove - 1, offer);
16062 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
16064 DisplayError(_("You must make your move before offering a draw"), 0);
16065 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
16067 } else if (first.offeredDraw) {
16068 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
16070 if (first.sendDrawOffers) {
16071 SendToProgram("draw\n", &first);
16072 userOfferedDraw = TRUE;
16080 /* Offer Adjourn or accept pending Adjourn offer from opponent */
16082 if (appData.icsActive) {
16083 SendToICS(ics_prefix);
16084 SendToICS("adjourn\n");
16086 /* Currently GNU Chess doesn't offer or accept Adjourns */
16094 /* Offer Abort or accept pending Abort offer from opponent */
16096 if (appData.icsActive) {
16097 SendToICS(ics_prefix);
16098 SendToICS("abort\n");
16100 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
16107 /* Resign. You can do this even if it's not your turn. */
16109 if (appData.icsActive) {
16110 SendToICS(ics_prefix);
16111 SendToICS("resign\n");
16113 switch (gameMode) {
16114 case MachinePlaysWhite:
16115 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
16117 case MachinePlaysBlack:
16118 GameEnds(BlackWins, "White resigns", GE_PLAYER);
16121 if (cmailMsgLoaded) {
16123 if (WhiteOnMove(cmailOldMove)) {
16124 GameEnds(BlackWins, "White resigns", GE_PLAYER);
16126 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
16128 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
16139 StopObservingEvent ()
16141 /* Stop observing current games */
16142 SendToICS(ics_prefix);
16143 SendToICS("unobserve\n");
16147 StopExaminingEvent ()
16149 /* Stop observing current game */
16150 SendToICS(ics_prefix);
16151 SendToICS("unexamine\n");
16155 ForwardInner (int target)
16157 int limit; int oldSeekGraphUp = seekGraphUp;
16159 if (appData.debugMode)
16160 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
16161 target, currentMove, forwardMostMove);
16163 if (gameMode == EditPosition)
16166 seekGraphUp = FALSE;
16167 MarkTargetSquares(1);
16168 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16170 if (gameMode == PlayFromGameFile && !pausing)
16173 if (gameMode == IcsExamining && pausing)
16174 limit = pauseExamForwardMostMove;
16176 limit = forwardMostMove;
16178 if (target > limit) target = limit;
16180 if (target > 0 && moveList[target - 1][0]) {
16181 int fromX, fromY, toX, toY;
16182 toX = moveList[target - 1][2] - AAA;
16183 toY = moveList[target - 1][3] - ONE;
16184 if (moveList[target - 1][1] == '@') {
16185 if (appData.highlightLastMove) {
16186 SetHighlights(-1, -1, toX, toY);
16189 fromX = moveList[target - 1][0] - AAA;
16190 fromY = moveList[target - 1][1] - ONE;
16191 if (target == currentMove + 1) {
16192 if(moveList[target - 1][4] == ';') { // multi-leg
16193 killX = moveList[target - 1][5] - AAA;
16194 killY = moveList[target - 1][6] - ONE;
16196 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
16197 killX = killY = -1;
16199 if (appData.highlightLastMove) {
16200 SetHighlights(fromX, fromY, toX, toY);
16204 if (gameMode == EditGame || gameMode == AnalyzeMode ||
16205 gameMode == Training || gameMode == PlayFromGameFile ||
16206 gameMode == AnalyzeFile) {
16207 while (currentMove < target) {
16208 if(second.analyzing) SendMoveToProgram(currentMove, &second);
16209 SendMoveToProgram(currentMove++, &first);
16212 currentMove = target;
16215 if (gameMode == EditGame || gameMode == EndOfGame) {
16216 whiteTimeRemaining = timeRemaining[0][currentMove];
16217 blackTimeRemaining = timeRemaining[1][currentMove];
16219 DisplayBothClocks();
16220 DisplayMove(currentMove - 1);
16221 DrawPosition(oldSeekGraphUp, boards[currentMove]);
16222 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16223 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
16224 DisplayComment(currentMove - 1, commentList[currentMove]);
16226 ClearMap(); // [HGM] exclude: invalidate map
16233 if (gameMode == IcsExamining && !pausing) {
16234 SendToICS(ics_prefix);
16235 SendToICS("forward\n");
16237 ForwardInner(currentMove + 1);
16244 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16245 /* to optimze, we temporarily turn off analysis mode while we feed
16246 * the remaining moves to the engine. Otherwise we get analysis output
16249 if (first.analysisSupport) {
16250 SendToProgram("exit\nforce\n", &first);
16251 first.analyzing = FALSE;
16255 if (gameMode == IcsExamining && !pausing) {
16256 SendToICS(ics_prefix);
16257 SendToICS("forward 999999\n");
16259 ForwardInner(forwardMostMove);
16262 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16263 /* we have fed all the moves, so reactivate analysis mode */
16264 SendToProgram("analyze\n", &first);
16265 first.analyzing = TRUE;
16266 /*first.maybeThinking = TRUE;*/
16267 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16272 BackwardInner (int target)
16274 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16276 if (appData.debugMode)
16277 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16278 target, currentMove, forwardMostMove);
16280 if (gameMode == EditPosition) return;
16281 seekGraphUp = FALSE;
16282 MarkTargetSquares(1);
16283 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16284 if (currentMove <= backwardMostMove) {
16286 DrawPosition(full_redraw, boards[currentMove]);
16289 if (gameMode == PlayFromGameFile && !pausing)
16292 if (moveList[target][0]) {
16293 int fromX, fromY, toX, toY;
16294 toX = moveList[target][2] - AAA;
16295 toY = moveList[target][3] - ONE;
16296 if (moveList[target][1] == '@') {
16297 if (appData.highlightLastMove) {
16298 SetHighlights(-1, -1, toX, toY);
16301 fromX = moveList[target][0] - AAA;
16302 fromY = moveList[target][1] - ONE;
16303 if (target == currentMove - 1) {
16304 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16306 if (appData.highlightLastMove) {
16307 SetHighlights(fromX, fromY, toX, toY);
16311 if (gameMode == EditGame || gameMode==AnalyzeMode ||
16312 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16313 while (currentMove > target) {
16314 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16315 // null move cannot be undone. Reload program with move history before it.
16317 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16318 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16320 SendBoard(&first, i);
16321 if(second.analyzing) SendBoard(&second, i);
16322 for(currentMove=i; currentMove<target; currentMove++) {
16323 SendMoveToProgram(currentMove, &first);
16324 if(second.analyzing) SendMoveToProgram(currentMove, &second);
16328 SendToBoth("undo\n");
16332 currentMove = target;
16335 if (gameMode == EditGame || gameMode == EndOfGame) {
16336 whiteTimeRemaining = timeRemaining[0][currentMove];
16337 blackTimeRemaining = timeRemaining[1][currentMove];
16339 DisplayBothClocks();
16340 DisplayMove(currentMove - 1);
16341 DrawPosition(full_redraw, boards[currentMove]);
16342 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16343 // [HGM] PV info: routine tests if comment empty
16344 DisplayComment(currentMove - 1, commentList[currentMove]);
16345 ClearMap(); // [HGM] exclude: invalidate map
16351 if (gameMode == IcsExamining && !pausing) {
16352 SendToICS(ics_prefix);
16353 SendToICS("backward\n");
16355 BackwardInner(currentMove - 1);
16362 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16363 /* to optimize, we temporarily turn off analysis mode while we undo
16364 * all the moves. Otherwise we get analysis output after each undo.
16366 if (first.analysisSupport) {
16367 SendToProgram("exit\nforce\n", &first);
16368 first.analyzing = FALSE;
16372 if (gameMode == IcsExamining && !pausing) {
16373 SendToICS(ics_prefix);
16374 SendToICS("backward 999999\n");
16376 BackwardInner(backwardMostMove);
16379 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16380 /* we have fed all the moves, so reactivate analysis mode */
16381 SendToProgram("analyze\n", &first);
16382 first.analyzing = TRUE;
16383 /*first.maybeThinking = TRUE;*/
16384 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16391 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16392 if (to >= forwardMostMove) to = forwardMostMove;
16393 if (to <= backwardMostMove) to = backwardMostMove;
16394 if (to < currentMove) {
16402 RevertEvent (Boolean annotate)
16404 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16407 if (gameMode != IcsExamining) {
16408 DisplayError(_("You are not examining a game"), 0);
16412 DisplayError(_("You can't revert while pausing"), 0);
16415 SendToICS(ics_prefix);
16416 SendToICS("revert\n");
16420 RetractMoveEvent ()
16422 switch (gameMode) {
16423 case MachinePlaysWhite:
16424 case MachinePlaysBlack:
16425 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16426 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16429 if (forwardMostMove < 2) return;
16430 currentMove = forwardMostMove = forwardMostMove - 2;
16431 whiteTimeRemaining = timeRemaining[0][currentMove];
16432 blackTimeRemaining = timeRemaining[1][currentMove];
16433 DisplayBothClocks();
16434 DisplayMove(currentMove - 1);
16435 ClearHighlights();/*!! could figure this out*/
16436 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16437 SendToProgram("remove\n", &first);
16438 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16441 case BeginningOfGame:
16445 case IcsPlayingWhite:
16446 case IcsPlayingBlack:
16447 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16448 SendToICS(ics_prefix);
16449 SendToICS("takeback 2\n");
16451 SendToICS(ics_prefix);
16452 SendToICS("takeback 1\n");
16461 ChessProgramState *cps;
16463 switch (gameMode) {
16464 case MachinePlaysWhite:
16465 if (!WhiteOnMove(forwardMostMove)) {
16466 DisplayError(_("It is your turn"), 0);
16471 case MachinePlaysBlack:
16472 if (WhiteOnMove(forwardMostMove)) {
16473 DisplayError(_("It is your turn"), 0);
16478 case TwoMachinesPlay:
16479 if (WhiteOnMove(forwardMostMove) ==
16480 (first.twoMachinesColor[0] == 'w')) {
16486 case BeginningOfGame:
16490 SendToProgram("?\n", cps);
16494 TruncateGameEvent ()
16497 if (gameMode != EditGame) return;
16504 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16505 if (forwardMostMove > currentMove) {
16506 if (gameInfo.resultDetails != NULL) {
16507 free(gameInfo.resultDetails);
16508 gameInfo.resultDetails = NULL;
16509 gameInfo.result = GameUnfinished;
16511 forwardMostMove = currentMove;
16512 HistorySet(parseList, backwardMostMove, forwardMostMove,
16520 if (appData.noChessProgram) return;
16521 switch (gameMode) {
16522 case MachinePlaysWhite:
16523 if (WhiteOnMove(forwardMostMove)) {
16524 DisplayError(_("Wait until your turn."), 0);
16528 case BeginningOfGame:
16529 case MachinePlaysBlack:
16530 if (!WhiteOnMove(forwardMostMove)) {
16531 DisplayError(_("Wait until your turn."), 0);
16536 DisplayError(_("No hint available"), 0);
16539 SendToProgram("hint\n", &first);
16540 hintRequested = TRUE;
16544 SaveSelected (FILE *g, int dummy, char *dummy2)
16546 ListGame * lg = (ListGame *) gameList.head;
16550 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16551 DisplayError(_("Game list not loaded or empty"), 0);
16555 creatingBook = TRUE; // suppresses stuff during load game
16557 /* Get list size */
16558 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16559 if(lg->position >= 0) { // selected?
16560 LoadGame(f, nItem, "", TRUE);
16561 SaveGamePGN2(g); // leaves g open
16564 lg = (ListGame *) lg->node.succ;
16568 creatingBook = FALSE;
16576 ListGame * lg = (ListGame *) gameList.head;
16579 static int secondTime = FALSE;
16581 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16582 DisplayError(_("Game list not loaded or empty"), 0);
16586 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16589 DisplayNote(_("Book file exists! Try again for overwrite."));
16593 creatingBook = TRUE;
16594 secondTime = FALSE;
16596 /* Get list size */
16597 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16598 if(lg->position >= 0) {
16599 LoadGame(f, nItem, "", TRUE);
16600 AddGameToBook(TRUE);
16603 lg = (ListGame *) lg->node.succ;
16606 creatingBook = FALSE;
16613 if (appData.noChessProgram) return;
16614 switch (gameMode) {
16615 case MachinePlaysWhite:
16616 if (WhiteOnMove(forwardMostMove)) {
16617 DisplayError(_("Wait until your turn."), 0);
16621 case BeginningOfGame:
16622 case MachinePlaysBlack:
16623 if (!WhiteOnMove(forwardMostMove)) {
16624 DisplayError(_("Wait until your turn."), 0);
16629 EditPositionDone(TRUE);
16631 case TwoMachinesPlay:
16636 SendToProgram("bk\n", &first);
16637 bookOutput[0] = NULLCHAR;
16638 bookRequested = TRUE;
16644 char *tags = PGNTags(&gameInfo);
16645 TagsPopUp(tags, CmailMsg());
16649 /* end button procedures */
16652 PrintPosition (FILE *fp, int move)
16656 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16657 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16658 char c = PieceToChar(boards[move][i][j]);
16659 fputc(c == '?' ? '.' : c, fp);
16660 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16663 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16664 fprintf(fp, "white to play\n");
16666 fprintf(fp, "black to play\n");
16670 PrintOpponents (FILE *fp)
16672 if (gameInfo.white != NULL) {
16673 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16679 /* Find last component of program's own name, using some heuristics */
16681 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16684 int local = (strcmp(host, "localhost") == 0);
16685 while (!local && (p = strchr(prog, ';')) != NULL) {
16687 while (*p == ' ') p++;
16690 if (*prog == '"' || *prog == '\'') {
16691 q = strchr(prog + 1, *prog);
16693 q = strchr(prog, ' ');
16695 if (q == NULL) q = prog + strlen(prog);
16697 while (p >= prog && *p != '/' && *p != '\\') p--;
16699 if(p == prog && *p == '"') p++;
16701 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16702 memcpy(buf, p, q - p);
16703 buf[q - p] = NULLCHAR;
16711 TimeControlTagValue ()
16714 if (!appData.clockMode) {
16715 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16716 } else if (movesPerSession > 0) {
16717 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16718 } else if (timeIncrement == 0) {
16719 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16721 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16723 return StrSave(buf);
16729 /* This routine is used only for certain modes */
16730 VariantClass v = gameInfo.variant;
16731 ChessMove r = GameUnfinished;
16734 if(keepInfo) return;
16736 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16737 r = gameInfo.result;
16738 p = gameInfo.resultDetails;
16739 gameInfo.resultDetails = NULL;
16741 ClearGameInfo(&gameInfo);
16742 gameInfo.variant = v;
16744 switch (gameMode) {
16745 case MachinePlaysWhite:
16746 gameInfo.event = StrSave( appData.pgnEventHeader );
16747 gameInfo.site = StrSave(HostName());
16748 gameInfo.date = PGNDate();
16749 gameInfo.round = StrSave("-");
16750 gameInfo.white = StrSave(first.tidy);
16751 gameInfo.black = StrSave(UserName());
16752 gameInfo.timeControl = TimeControlTagValue();
16755 case MachinePlaysBlack:
16756 gameInfo.event = StrSave( appData.pgnEventHeader );
16757 gameInfo.site = StrSave(HostName());
16758 gameInfo.date = PGNDate();
16759 gameInfo.round = StrSave("-");
16760 gameInfo.white = StrSave(UserName());
16761 gameInfo.black = StrSave(first.tidy);
16762 gameInfo.timeControl = TimeControlTagValue();
16765 case TwoMachinesPlay:
16766 gameInfo.event = StrSave( appData.pgnEventHeader );
16767 gameInfo.site = StrSave(HostName());
16768 gameInfo.date = PGNDate();
16771 snprintf(buf, MSG_SIZ, "%d", roundNr);
16772 gameInfo.round = StrSave(buf);
16774 gameInfo.round = StrSave("-");
16776 if (first.twoMachinesColor[0] == 'w') {
16777 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16778 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16780 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16781 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16783 gameInfo.timeControl = TimeControlTagValue();
16787 gameInfo.event = StrSave("Edited game");
16788 gameInfo.site = StrSave(HostName());
16789 gameInfo.date = PGNDate();
16790 gameInfo.round = StrSave("-");
16791 gameInfo.white = StrSave("-");
16792 gameInfo.black = StrSave("-");
16793 gameInfo.result = r;
16794 gameInfo.resultDetails = p;
16798 gameInfo.event = StrSave("Edited position");
16799 gameInfo.site = StrSave(HostName());
16800 gameInfo.date = PGNDate();
16801 gameInfo.round = StrSave("-");
16802 gameInfo.white = StrSave("-");
16803 gameInfo.black = StrSave("-");
16806 case IcsPlayingWhite:
16807 case IcsPlayingBlack:
16812 case PlayFromGameFile:
16813 gameInfo.event = StrSave("Game from non-PGN file");
16814 gameInfo.site = StrSave(HostName());
16815 gameInfo.date = PGNDate();
16816 gameInfo.round = StrSave("-");
16817 gameInfo.white = StrSave("?");
16818 gameInfo.black = StrSave("?");
16827 ReplaceComment (int index, char *text)
16833 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16834 pvInfoList[index-1].depth == len &&
16835 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16836 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16837 while (*text == '\n') text++;
16838 len = strlen(text);
16839 while (len > 0 && text[len - 1] == '\n') len--;
16841 if (commentList[index] != NULL)
16842 free(commentList[index]);
16845 commentList[index] = NULL;
16848 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16849 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16850 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16851 commentList[index] = (char *) malloc(len + 2);
16852 strncpy(commentList[index], text, len);
16853 commentList[index][len] = '\n';
16854 commentList[index][len + 1] = NULLCHAR;
16856 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16858 commentList[index] = (char *) malloc(len + 7);
16859 safeStrCpy(commentList[index], "{\n", 3);
16860 safeStrCpy(commentList[index]+2, text, len+1);
16861 commentList[index][len+2] = NULLCHAR;
16862 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16863 strcat(commentList[index], "\n}\n");
16868 CrushCRs (char *text)
16876 if (ch == '\r') continue;
16878 } while (ch != '\0');
16882 AppendComment (int index, char *text, Boolean addBraces)
16883 /* addBraces tells if we should add {} */
16888 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16889 if(addBraces == 3) addBraces = 0; else // force appending literally
16890 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16893 while (*text == '\n') text++;
16894 len = strlen(text);
16895 while (len > 0 && text[len - 1] == '\n') len--;
16896 text[len] = NULLCHAR;
16898 if (len == 0) return;
16900 if (commentList[index] != NULL) {
16901 Boolean addClosingBrace = addBraces;
16902 old = commentList[index];
16903 oldlen = strlen(old);
16904 while(commentList[index][oldlen-1] == '\n')
16905 commentList[index][--oldlen] = NULLCHAR;
16906 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16907 safeStrCpy(commentList[index], old, oldlen + len + 6);
16909 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16910 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16911 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16912 while (*text == '\n') { text++; len--; }
16913 commentList[index][--oldlen] = NULLCHAR;
16915 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16916 else strcat(commentList[index], "\n");
16917 strcat(commentList[index], text);
16918 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16919 else strcat(commentList[index], "\n");
16921 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16923 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16924 else commentList[index][0] = NULLCHAR;
16925 strcat(commentList[index], text);
16926 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16927 if(addBraces == TRUE) strcat(commentList[index], "}\n");
16932 FindStr (char * text, char * sub_text)
16934 char * result = strstr( text, sub_text );
16936 if( result != NULL ) {
16937 result += strlen( sub_text );
16943 /* [AS] Try to extract PV info from PGN comment */
16944 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16946 GetInfoFromComment (int index, char * text)
16948 char * sep = text, *p;
16950 if( text != NULL && index > 0 ) {
16953 int time = -1, sec = 0, deci;
16954 char * s_eval = FindStr( text, "[%eval " );
16955 char * s_emt = FindStr( text, "[%emt " );
16957 if( s_eval != NULL || s_emt != NULL ) {
16959 if(0) { // [HGM] this code is not finished, and could actually be detrimental
16964 if( s_eval != NULL ) {
16965 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16969 if( delim != ']' ) {
16974 if( s_emt != NULL ) {
16979 /* We expect something like: [+|-]nnn.nn/dd */
16982 if(*text != '{') return text; // [HGM] braces: must be normal comment
16984 sep = strchr( text, '/' );
16985 if( sep == NULL || sep < (text+4) ) {
16990 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16991 if(p[1] == '(') { // comment starts with PV
16992 p = strchr(p, ')'); // locate end of PV
16993 if(p == NULL || sep < p+5) return text;
16994 // at this point we have something like "{(.*) +0.23/6 ..."
16995 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16996 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16997 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16999 time = -1; sec = -1; deci = -1;
17000 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
17001 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
17002 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
17003 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
17007 if( score_lo < 0 || score_lo >= 100 ) {
17011 if(sec >= 0) time = 600*time + 10*sec; else
17012 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
17014 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
17016 /* [HGM] PV time: now locate end of PV info */
17017 while( *++sep >= '0' && *sep <= '9'); // strip depth
17019 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
17021 while( *++sep >= '0' && *sep <= '9'); // strip seconds
17023 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
17024 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
17035 pvInfoList[index-1].depth = depth;
17036 pvInfoList[index-1].score = score;
17037 pvInfoList[index-1].time = 10*time; // centi-sec
17038 if(*sep == '}') *sep = 0; else *--sep = '{';
17040 while(*p++ = *sep++)
17043 } // squeeze out space between PV and comment, and return both
17049 SendToProgram (char *message, ChessProgramState *cps)
17051 int count, outCount, error;
17054 if (cps->pr == NoProc) return;
17057 if (appData.debugMode) {
17060 fprintf(debugFP, "%ld >%-6s: %s",
17061 SubtractTimeMarks(&now, &programStartTime),
17062 cps->which, message);
17064 fprintf(serverFP, "%ld >%-6s: %s",
17065 SubtractTimeMarks(&now, &programStartTime),
17066 cps->which, message), fflush(serverFP);
17069 count = strlen(message);
17070 outCount = OutputToProcess(cps->pr, message, count, &error);
17071 if (outCount < count && !exiting
17072 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
17073 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
17074 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
17075 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
17076 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
17077 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
17078 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
17079 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
17081 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
17082 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
17083 gameInfo.result = res;
17085 gameInfo.resultDetails = StrSave(buf);
17087 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
17088 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
17093 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
17097 ChessProgramState *cps = (ChessProgramState *)closure;
17099 if (isr != cps->isr) return; /* Killed intentionally */
17102 RemoveInputSource(cps->isr);
17103 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
17104 _(cps->which), cps->program);
17105 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
17106 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
17107 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
17108 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
17109 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
17110 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
17112 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
17113 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
17114 gameInfo.result = res;
17116 gameInfo.resultDetails = StrSave(buf);
17118 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
17119 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
17121 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
17122 _(cps->which), cps->program);
17123 RemoveInputSource(cps->isr);
17125 /* [AS] Program is misbehaving badly... kill it */
17126 if( count == -2 ) {
17127 DestroyChildProcess( cps->pr, 9 );
17131 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
17136 if ((end_str = strchr(message, '\r')) != NULL)
17137 *end_str = NULLCHAR;
17138 if ((end_str = strchr(message, '\n')) != NULL)
17139 *end_str = NULLCHAR;
17141 if (appData.debugMode) {
17142 TimeMark now; int print = 1;
17143 char *quote = ""; char c; int i;
17145 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
17146 char start = message[0];
17147 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
17148 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
17149 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
17150 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
17151 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
17152 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
17153 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
17154 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
17155 sscanf(message, "hint: %c", &c)!=1 &&
17156 sscanf(message, "pong %c", &c)!=1 && start != '#') {
17157 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
17158 print = (appData.engineComments >= 2);
17160 message[0] = start; // restore original message
17164 fprintf(debugFP, "%ld <%-6s: %s%s\n",
17165 SubtractTimeMarks(&now, &programStartTime), cps->which,
17169 fprintf(serverFP, "%ld <%-6s: %s%s\n",
17170 SubtractTimeMarks(&now, &programStartTime), cps->which,
17172 message), fflush(serverFP);
17176 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
17177 if (appData.icsEngineAnalyze) {
17178 if (strstr(message, "whisper") != NULL ||
17179 strstr(message, "kibitz") != NULL ||
17180 strstr(message, "tellics") != NULL) return;
17183 HandleMachineMove(message, cps);
17188 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
17193 if( timeControl_2 > 0 ) {
17194 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
17195 tc = timeControl_2;
17198 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
17199 inc /= cps->timeOdds;
17200 st /= cps->timeOdds;
17202 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
17205 /* Set exact time per move, normally using st command */
17206 if (cps->stKludge) {
17207 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
17209 if (seconds == 0) {
17210 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
17212 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
17215 snprintf(buf, MSG_SIZ, "st %d\n", st);
17218 /* Set conventional or incremental time control, using level command */
17219 if (seconds == 0) {
17220 /* Note old gnuchess bug -- minutes:seconds used to not work.
17221 Fixed in later versions, but still avoid :seconds
17222 when seconds is 0. */
17223 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
17225 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
17226 seconds, inc/1000.);
17229 SendToProgram(buf, cps);
17231 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17232 /* Orthogonally, limit search to given depth */
17234 if (cps->sdKludge) {
17235 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17237 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17239 SendToProgram(buf, cps);
17242 if(cps->nps >= 0) { /* [HGM] nps */
17243 if(cps->supportsNPS == FALSE)
17244 cps->nps = -1; // don't use if engine explicitly says not supported!
17246 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17247 SendToProgram(buf, cps);
17252 ChessProgramState *
17254 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17256 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17257 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17263 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17265 char message[MSG_SIZ];
17268 /* Note: this routine must be called when the clocks are stopped
17269 or when they have *just* been set or switched; otherwise
17270 it will be off by the time since the current tick started.
17272 if (machineWhite) {
17273 time = whiteTimeRemaining / 10;
17274 otime = blackTimeRemaining / 10;
17276 time = blackTimeRemaining / 10;
17277 otime = whiteTimeRemaining / 10;
17279 /* [HGM] translate opponent's time by time-odds factor */
17280 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17282 if (time <= 0) time = 1;
17283 if (otime <= 0) otime = 1;
17285 snprintf(message, MSG_SIZ, "time %ld\n", time);
17286 SendToProgram(message, cps);
17288 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17289 SendToProgram(message, cps);
17293 EngineDefinedVariant (ChessProgramState *cps, int n)
17294 { // return name of n-th unknown variant that engine supports
17295 static char buf[MSG_SIZ];
17296 char *p, *s = cps->variants;
17297 if(!s) return NULL;
17298 do { // parse string from variants feature
17300 p = strchr(s, ',');
17301 if(p) *p = NULLCHAR;
17302 v = StringToVariant(s);
17303 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17304 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17305 if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17306 !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17307 !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17308 !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17309 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17312 if(n < 0) return buf;
17318 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17321 int len = strlen(name);
17324 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17326 sscanf(*p, "%d", &val);
17328 while (**p && **p != ' ')
17330 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17331 SendToProgram(buf, cps);
17338 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17341 int len = strlen(name);
17342 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17344 sscanf(*p, "%d", loc);
17345 while (**p && **p != ' ') (*p)++;
17346 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17347 SendToProgram(buf, cps);
17354 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17357 int len = strlen(name);
17358 if (strncmp((*p), name, len) == 0
17359 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17361 len = strlen(*p) + 1; if(len < MSG_SIZ && !strcmp(name, "option")) len = MSG_SIZ; // make sure string options have enough space to change their value
17362 FREE(*loc); *loc = malloc(len);
17363 strncpy(*loc, *p, len);
17364 sscanf(*p, "%[^\"]", *loc); // should always fit, because we allocated at least strlen(*p)
17365 while (**p && **p != '\"') (*p)++;
17366 if (**p == '\"') (*p)++;
17367 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17368 SendToProgram(buf, cps);
17375 ParseOption (Option *opt, ChessProgramState *cps)
17376 // [HGM] options: process the string that defines an engine option, and determine
17377 // name, type, default value, and allowed value range
17379 char *p, *q, buf[MSG_SIZ];
17380 int n, min = (-1)<<31, max = 1<<31, def;
17382 opt->target = &opt->value; // OK for spin/slider and checkbox
17383 if(p = strstr(opt->name, " -spin ")) {
17384 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17385 if(max < min) max = min; // enforce consistency
17386 if(def < min) def = min;
17387 if(def > max) def = max;
17392 } else if((p = strstr(opt->name, " -slider "))) {
17393 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17394 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17395 if(max < min) max = min; // enforce consistency
17396 if(def < min) def = min;
17397 if(def > max) def = max;
17401 opt->type = Spin; // Slider;
17402 } else if((p = strstr(opt->name, " -string "))) {
17403 opt->textValue = p+9;
17404 opt->type = TextBox;
17405 opt->target = &opt->textValue;
17406 } else if((p = strstr(opt->name, " -file "))) {
17407 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17408 opt->target = opt->textValue = p+7;
17409 opt->type = FileName; // FileName;
17410 opt->target = &opt->textValue;
17411 } else if((p = strstr(opt->name, " -path "))) {
17412 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17413 opt->target = opt->textValue = p+7;
17414 opt->type = PathName; // PathName;
17415 opt->target = &opt->textValue;
17416 } else if(p = strstr(opt->name, " -check ")) {
17417 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17418 opt->value = (def != 0);
17419 opt->type = CheckBox;
17420 } else if(p = strstr(opt->name, " -combo ")) {
17421 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17422 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17423 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17424 opt->value = n = 0;
17425 while(q = StrStr(q, " /// ")) {
17426 n++; *q = 0; // count choices, and null-terminate each of them
17428 if(*q == '*') { // remember default, which is marked with * prefix
17432 cps->comboList[cps->comboCnt++] = q;
17434 cps->comboList[cps->comboCnt++] = NULL;
17436 opt->type = ComboBox;
17437 } else if(p = strstr(opt->name, " -button")) {
17438 opt->type = Button;
17439 } else if(p = strstr(opt->name, " -save")) {
17440 opt->type = SaveButton;
17441 } else return FALSE;
17442 *p = 0; // terminate option name
17443 *(int*) (opt->name + MSG_SIZ - 104) = opt->value; // hide default values somewhere
17444 if(opt->target == &opt->textValue) strncpy(opt->name + MSG_SIZ - 100, opt->textValue, 99);
17445 // now look if the command-line options define a setting for this engine option.
17446 if(cps->optionSettings && cps->optionSettings[0])
17447 p = strstr(cps->optionSettings, opt->name); else p = NULL;
17448 if(p && (p == cps->optionSettings || p[-1] == ',')) {
17449 snprintf(buf, MSG_SIZ, "option %s", p);
17450 if(p = strstr(buf, ",")) *p = 0;
17451 if(q = strchr(buf, '=')) switch(opt->type) {
17453 for(n=0; n<opt->max; n++)
17454 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17459 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17463 opt->value = atoi(q+1);
17468 SendToProgram(buf, cps);
17474 FeatureDone (ChessProgramState *cps, int val)
17476 DelayedEventCallback cb = GetDelayedEvent();
17477 if ((cb == InitBackEnd3 && cps == &first) ||
17478 (cb == SettingsMenuIfReady && cps == &second) ||
17479 (cb == LoadEngine) || (cb == StartSecond) ||
17480 (cb == TwoMachinesEventIfReady)) {
17481 CancelDelayedEvent();
17482 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17483 } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17484 cps->initDone = val;
17485 if(val) cps->reload = FALSE, RefreshSettingsDialog(cps, val);
17488 /* Parse feature command from engine */
17490 ParseFeatures (char *args, ChessProgramState *cps)
17498 while (*p == ' ') p++;
17499 if (*p == NULLCHAR) return;
17501 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17502 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17503 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17504 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17505 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17506 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17507 if (BoolFeature(&p, "reuse", &val, cps)) {
17508 /* Engine can disable reuse, but can't enable it if user said no */
17509 if (!val) cps->reuse = FALSE;
17512 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17513 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17514 if (gameMode == TwoMachinesPlay) {
17515 DisplayTwoMachinesTitle();
17521 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17522 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17523 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17524 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17525 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17526 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17527 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17528 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17529 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17530 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17531 if (IntFeature(&p, "done", &val, cps)) {
17532 FeatureDone(cps, val);
17535 /* Added by Tord: */
17536 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17537 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17538 /* End of additions by Tord */
17540 /* [HGM] added features: */
17541 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17542 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17543 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17544 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17545 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17546 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17547 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17548 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17549 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17550 if(cps->nrOptions == 0) { ASSIGN(cps->option[0].name, _("Make Persistent -save")); ParseOption(&(cps->option[cps->nrOptions++]), cps); }
17551 FREE(cps->option[cps->nrOptions].name);
17552 cps->option[cps->nrOptions].name = q; q = NULL;
17553 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17554 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17555 SendToProgram(buf, cps);
17558 if(cps->nrOptions >= MAX_OPTIONS) {
17560 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17561 DisplayError(buf, 0);
17565 /* End of additions by HGM */
17567 /* unknown feature: complain and skip */
17569 while (*q && *q != '=') q++;
17570 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17571 SendToProgram(buf, cps);
17577 while (*p && *p != '\"') p++;
17578 if (*p == '\"') p++;
17580 while (*p && *p != ' ') p++;
17588 PeriodicUpdatesEvent (int newState)
17590 if (newState == appData.periodicUpdates)
17593 appData.periodicUpdates=newState;
17595 /* Display type changes, so update it now */
17596 // DisplayAnalysis();
17598 /* Get the ball rolling again... */
17600 AnalysisPeriodicEvent(1);
17601 StartAnalysisClock();
17606 PonderNextMoveEvent (int newState)
17608 if (newState == appData.ponderNextMove) return;
17609 if (gameMode == EditPosition) EditPositionDone(TRUE);
17611 SendToProgram("hard\n", &first);
17612 if (gameMode == TwoMachinesPlay) {
17613 SendToProgram("hard\n", &second);
17616 SendToProgram("easy\n", &first);
17617 thinkOutput[0] = NULLCHAR;
17618 if (gameMode == TwoMachinesPlay) {
17619 SendToProgram("easy\n", &second);
17622 appData.ponderNextMove = newState;
17626 NewSettingEvent (int option, int *feature, char *command, int value)
17630 if (gameMode == EditPosition) EditPositionDone(TRUE);
17631 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17632 if(feature == NULL || *feature) SendToProgram(buf, &first);
17633 if (gameMode == TwoMachinesPlay) {
17634 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17639 ShowThinkingEvent ()
17640 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17642 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17643 int newState = appData.showThinking
17644 // [HGM] thinking: other features now need thinking output as well
17645 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17647 if (oldState == newState) return;
17648 oldState = newState;
17649 if (gameMode == EditPosition) EditPositionDone(TRUE);
17651 SendToProgram("post\n", &first);
17652 if (gameMode == TwoMachinesPlay) {
17653 SendToProgram("post\n", &second);
17656 SendToProgram("nopost\n", &first);
17657 thinkOutput[0] = NULLCHAR;
17658 if (gameMode == TwoMachinesPlay) {
17659 SendToProgram("nopost\n", &second);
17662 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17666 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17668 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17669 if (pr == NoProc) return;
17670 AskQuestion(title, question, replyPrefix, pr);
17674 TypeInEvent (char firstChar)
17676 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17677 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17678 gameMode == AnalyzeMode || gameMode == EditGame ||
17679 gameMode == EditPosition || gameMode == IcsExamining ||
17680 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17681 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17682 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17683 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
17684 gameMode == Training) PopUpMoveDialog(firstChar);
17688 TypeInDoneEvent (char *move)
17691 int n, fromX, fromY, toX, toY;
17693 ChessMove moveType;
17696 if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17697 EditPositionPasteFEN(move);
17700 // [HGM] movenum: allow move number to be typed in any mode
17701 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17705 // undocumented kludge: allow command-line option to be typed in!
17706 // (potentially fatal, and does not implement the effect of the option.)
17707 // should only be used for options that are values on which future decisions will be made,
17708 // and definitely not on options that would be used during initialization.
17709 if(strstr(move, "!!! -") == move) {
17710 ParseArgsFromString(move+4);
17714 if (gameMode != EditGame && currentMove != forwardMostMove &&
17715 gameMode != Training) {
17716 DisplayMoveError(_("Displayed move is not current"));
17718 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17719 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17720 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17721 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17722 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17723 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17725 DisplayMoveError(_("Could not parse move"));
17731 DisplayMove (int moveNumber)
17733 char message[MSG_SIZ];
17735 char cpThinkOutput[MSG_SIZ];
17737 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17739 if (moveNumber == forwardMostMove - 1 ||
17740 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17742 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17744 if (strchr(cpThinkOutput, '\n')) {
17745 *strchr(cpThinkOutput, '\n') = NULLCHAR;
17748 *cpThinkOutput = NULLCHAR;
17751 /* [AS] Hide thinking from human user */
17752 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17753 *cpThinkOutput = NULLCHAR;
17754 if( thinkOutput[0] != NULLCHAR ) {
17757 for( i=0; i<=hiddenThinkOutputState; i++ ) {
17758 cpThinkOutput[i] = '.';
17760 cpThinkOutput[i] = NULLCHAR;
17761 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17765 if (moveNumber == forwardMostMove - 1 &&
17766 gameInfo.resultDetails != NULL) {
17767 if (gameInfo.resultDetails[0] == NULLCHAR) {
17768 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17770 snprintf(res, MSG_SIZ, " {%s} %s",
17771 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17777 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17778 DisplayMessage(res, cpThinkOutput);
17780 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17781 WhiteOnMove(moveNumber) ? " " : ".. ",
17782 parseList[moveNumber], res);
17783 DisplayMessage(message, cpThinkOutput);
17788 DisplayComment (int moveNumber, char *text)
17790 char title[MSG_SIZ];
17792 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17793 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17795 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17796 WhiteOnMove(moveNumber) ? " " : ".. ",
17797 parseList[moveNumber]);
17799 if (text != NULL && (appData.autoDisplayComment || commentUp))
17800 CommentPopUp(title, text);
17803 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17804 * might be busy thinking or pondering. It can be omitted if your
17805 * gnuchess is configured to stop thinking immediately on any user
17806 * input. However, that gnuchess feature depends on the FIONREAD
17807 * ioctl, which does not work properly on some flavors of Unix.
17810 Attention (ChessProgramState *cps)
17813 if (!cps->useSigint) return;
17814 if (appData.noChessProgram || (cps->pr == NoProc)) return;
17815 switch (gameMode) {
17816 case MachinePlaysWhite:
17817 case MachinePlaysBlack:
17818 case TwoMachinesPlay:
17819 case IcsPlayingWhite:
17820 case IcsPlayingBlack:
17823 /* Skip if we know it isn't thinking */
17824 if (!cps->maybeThinking) return;
17825 if (appData.debugMode)
17826 fprintf(debugFP, "Interrupting %s\n", cps->which);
17827 InterruptChildProcess(cps->pr);
17828 cps->maybeThinking = FALSE;
17833 #endif /*ATTENTION*/
17839 if (whiteTimeRemaining <= 0) {
17842 if (appData.icsActive) {
17843 if (appData.autoCallFlag &&
17844 gameMode == IcsPlayingBlack && !blackFlag) {
17845 SendToICS(ics_prefix);
17846 SendToICS("flag\n");
17850 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17852 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17853 if (appData.autoCallFlag) {
17854 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17861 if (blackTimeRemaining <= 0) {
17864 if (appData.icsActive) {
17865 if (appData.autoCallFlag &&
17866 gameMode == IcsPlayingWhite && !whiteFlag) {
17867 SendToICS(ics_prefix);
17868 SendToICS("flag\n");
17872 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17874 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17875 if (appData.autoCallFlag) {
17876 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17887 CheckTimeControl ()
17889 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17890 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17893 * add time to clocks when time control is achieved ([HGM] now also used for increment)
17895 if ( !WhiteOnMove(forwardMostMove) ) {
17896 /* White made time control */
17897 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17898 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17899 /* [HGM] time odds: correct new time quota for time odds! */
17900 / WhitePlayer()->timeOdds;
17901 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17903 lastBlack -= blackTimeRemaining;
17904 /* Black made time control */
17905 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17906 / WhitePlayer()->other->timeOdds;
17907 lastWhite = whiteTimeRemaining;
17912 DisplayBothClocks ()
17914 int wom = gameMode == EditPosition ?
17915 !blackPlaysFirst : WhiteOnMove(currentMove);
17916 DisplayWhiteClock(whiteTimeRemaining, wom);
17917 DisplayBlackClock(blackTimeRemaining, !wom);
17921 /* Timekeeping seems to be a portability nightmare. I think everyone
17922 has ftime(), but I'm really not sure, so I'm including some ifdefs
17923 to use other calls if you don't. Clocks will be less accurate if
17924 you have neither ftime nor gettimeofday.
17927 /* VS 2008 requires the #include outside of the function */
17928 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17929 #include <sys/timeb.h>
17932 /* Get the current time as a TimeMark */
17934 GetTimeMark (TimeMark *tm)
17936 #if HAVE_GETTIMEOFDAY
17938 struct timeval timeVal;
17939 struct timezone timeZone;
17941 gettimeofday(&timeVal, &timeZone);
17942 tm->sec = (long) timeVal.tv_sec;
17943 tm->ms = (int) (timeVal.tv_usec / 1000L);
17945 #else /*!HAVE_GETTIMEOFDAY*/
17948 // include <sys/timeb.h> / moved to just above start of function
17949 struct timeb timeB;
17952 tm->sec = (long) timeB.time;
17953 tm->ms = (int) timeB.millitm;
17955 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17956 tm->sec = (long) time(NULL);
17962 /* Return the difference in milliseconds between two
17963 time marks. We assume the difference will fit in a long!
17966 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17968 return 1000L*(tm2->sec - tm1->sec) +
17969 (long) (tm2->ms - tm1->ms);
17974 * Code to manage the game clocks.
17976 * In tournament play, black starts the clock and then white makes a move.
17977 * We give the human user a slight advantage if he is playing white---the
17978 * clocks don't run until he makes his first move, so it takes zero time.
17979 * Also, we don't account for network lag, so we could get out of sync
17980 * with GNU Chess's clock -- but then, referees are always right.
17983 static TimeMark tickStartTM;
17984 static long intendedTickLength;
17987 NextTickLength (long timeRemaining)
17989 long nominalTickLength, nextTickLength;
17991 if (timeRemaining > 0L && timeRemaining <= 10000L)
17992 nominalTickLength = 100L;
17994 nominalTickLength = 1000L;
17995 nextTickLength = timeRemaining % nominalTickLength;
17996 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17998 return nextTickLength;
18001 /* Adjust clock one minute up or down */
18003 AdjustClock (Boolean which, int dir)
18005 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
18006 if(which) blackTimeRemaining += 60000*dir;
18007 else whiteTimeRemaining += 60000*dir;
18008 DisplayBothClocks();
18009 adjustedClock = TRUE;
18012 /* Stop clocks and reset to a fresh time control */
18016 (void) StopClockTimer();
18017 if (appData.icsActive) {
18018 whiteTimeRemaining = blackTimeRemaining = 0;
18019 } else if (searchTime) {
18020 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
18021 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
18022 } else { /* [HGM] correct new time quote for time odds */
18023 whiteTC = blackTC = fullTimeControlString;
18024 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
18025 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
18027 if (whiteFlag || blackFlag) {
18029 whiteFlag = blackFlag = FALSE;
18031 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
18032 DisplayBothClocks();
18033 adjustedClock = FALSE;
18036 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
18038 static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness...
18040 /* Decrement running clock by amount of time that has passed */
18045 long lastTickLength, fudge;
18048 if (!appData.clockMode) return;
18049 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
18053 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18055 /* Fudge if we woke up a little too soon */
18056 fudge = intendedTickLength - lastTickLength;
18057 if (fudge < 0 || fudge > FUDGE) fudge = 0;
18059 if (WhiteOnMove(forwardMostMove)) {
18060 if(whiteNPS >= 0) lastTickLength = 0;
18061 tRemaining = whiteTimeRemaining -= lastTickLength;
18062 if( tRemaining < 0 && !appData.icsActive) {
18063 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
18064 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
18065 whiteStartMove = forwardMostMove; whiteTC = nextSession;
18066 lastWhite= tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
18069 if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining;
18070 DisplayWhiteClock(whiteTimeRemaining - fudge,
18071 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
18074 if(blackNPS >= 0) lastTickLength = 0;
18075 tRemaining = blackTimeRemaining -= lastTickLength;
18076 if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
18077 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
18079 blackStartMove = forwardMostMove;
18080 lastBlack = tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
18083 if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining;
18084 DisplayBlackClock(blackTimeRemaining - fudge,
18085 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
18088 if (CheckFlags()) return;
18090 if(twoBoards) { // count down secondary board's clocks as well
18091 activePartnerTime -= lastTickLength;
18093 if(activePartner == 'W')
18094 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
18096 DisplayBlackClock(activePartnerTime, TRUE);
18101 intendedTickLength = NextTickLength( tRemaining - fudge) + fudge;
18102 StartClockTimer(intendedTickLength);
18104 /* if the time remaining has fallen below the alarm threshold, sound the
18105 * alarm. if the alarm has sounded and (due to a takeback or time control
18106 * with increment) the time remaining has increased to a level above the
18107 * threshold, reset the alarm so it can sound again.
18110 if (appData.icsActive && appData.icsAlarm) {
18112 /* make sure we are dealing with the user's clock */
18113 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
18114 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
18117 if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) {
18118 alarmSounded = FALSE;
18119 } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) {
18121 alarmSounded = TRUE;
18127 /* A player has just moved, so stop the previously running
18128 clock and (if in clock mode) start the other one.
18129 We redisplay both clocks in case we're in ICS mode, because
18130 ICS gives us an update to both clocks after every move.
18131 Note that this routine is called *after* forwardMostMove
18132 is updated, so the last fractional tick must be subtracted
18133 from the color that is *not* on move now.
18136 SwitchClocks (int newMoveNr)
18138 long lastTickLength;
18140 int flagged = FALSE;
18144 if (StopClockTimer() && appData.clockMode) {
18145 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18146 if (!WhiteOnMove(forwardMostMove)) {
18147 if(blackNPS >= 0) lastTickLength = 0;
18148 blackTimeRemaining -= lastTickLength;
18149 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18150 // if(pvInfoList[forwardMostMove].time == -1)
18151 pvInfoList[forwardMostMove].time = // use GUI time
18152 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
18154 if(whiteNPS >= 0) lastTickLength = 0;
18155 whiteTimeRemaining -= lastTickLength;
18156 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18157 // if(pvInfoList[forwardMostMove].time == -1)
18158 pvInfoList[forwardMostMove].time =
18159 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
18161 flagged = CheckFlags();
18163 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
18164 CheckTimeControl();
18166 if (flagged || !appData.clockMode) return;
18168 switch (gameMode) {
18169 case MachinePlaysBlack:
18170 case MachinePlaysWhite:
18171 case BeginningOfGame:
18172 if (pausing) return;
18176 case PlayFromGameFile:
18184 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
18185 if(WhiteOnMove(forwardMostMove))
18186 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
18187 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
18191 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18192 whiteTimeRemaining : blackTimeRemaining);
18193 StartClockTimer(intendedTickLength);
18197 /* Stop both clocks */
18201 long lastTickLength;
18204 if (!StopClockTimer()) return;
18205 if (!appData.clockMode) return;
18209 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18210 if (WhiteOnMove(forwardMostMove)) {
18211 if(whiteNPS >= 0) lastTickLength = 0;
18212 whiteTimeRemaining -= lastTickLength;
18213 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
18215 if(blackNPS >= 0) lastTickLength = 0;
18216 blackTimeRemaining -= lastTickLength;
18217 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
18222 /* Start clock of player on move. Time may have been reset, so
18223 if clock is already running, stop and restart it. */
18227 (void) StopClockTimer(); /* in case it was running already */
18228 DisplayBothClocks();
18229 if (CheckFlags()) return;
18231 if (!appData.clockMode) return;
18232 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
18234 GetTimeMark(&tickStartTM);
18235 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18236 whiteTimeRemaining : blackTimeRemaining);
18238 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
18239 whiteNPS = blackNPS = -1;
18240 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
18241 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
18242 whiteNPS = first.nps;
18243 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18244 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18245 blackNPS = first.nps;
18246 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18247 whiteNPS = second.nps;
18248 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18249 blackNPS = second.nps;
18250 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18252 StartClockTimer(intendedTickLength);
18256 TimeString (long ms)
18258 long second, minute, hour, day;
18260 static char buf[40], moveTime[8];
18262 if (ms > 0 && ms <= 9900) {
18263 /* convert milliseconds to tenths, rounding up */
18264 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18266 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18270 /* convert milliseconds to seconds, rounding up */
18271 /* use floating point to avoid strangeness of integer division
18272 with negative dividends on many machines */
18273 second = (long) floor(((double) (ms + 999L)) / 1000.0);
18280 day = second / (60 * 60 * 24);
18281 second = second % (60 * 60 * 24);
18282 hour = second / (60 * 60);
18283 second = second % (60 * 60);
18284 minute = second / 60;
18285 second = second % 60;
18287 if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time
18288 else *moveTime = NULLCHAR;
18291 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ",
18292 sign, day, hour, minute, second, moveTime);
18294 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime);
18296 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime);
18303 * This is necessary because some C libraries aren't ANSI C compliant yet.
18306 StrStr (char *string, char *match)
18310 length = strlen(match);
18312 for (i = strlen(string) - length; i >= 0; i--, string++)
18313 if (!strncmp(match, string, length))
18320 StrCaseStr (char *string, char *match)
18324 length = strlen(match);
18326 for (i = strlen(string) - length; i >= 0; i--, string++) {
18327 for (j = 0; j < length; j++) {
18328 if (ToLower(match[j]) != ToLower(string[j]))
18331 if (j == length) return string;
18339 StrCaseCmp (char *s1, char *s2)
18344 c1 = ToLower(*s1++);
18345 c2 = ToLower(*s2++);
18346 if (c1 > c2) return 1;
18347 if (c1 < c2) return -1;
18348 if (c1 == NULLCHAR) return 0;
18356 return isupper(c) ? tolower(c) : c;
18363 return islower(c) ? toupper(c) : c;
18365 #endif /* !_amigados */
18372 if ((ret = (char *) malloc(strlen(s) + 1)))
18374 safeStrCpy(ret, s, strlen(s)+1);
18380 StrSavePtr (char *s, char **savePtr)
18385 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18386 safeStrCpy(*savePtr, s, strlen(s)+1);
18398 clock = time((time_t *)NULL);
18399 tm = localtime(&clock);
18400 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18401 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18402 return StrSave(buf);
18407 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18409 int i, j, fromX, fromY, toX, toY;
18410 int whiteToPlay, haveRights = nrCastlingRights;
18416 whiteToPlay = (gameMode == EditPosition) ?
18417 !blackPlaysFirst : (move % 2 == 0);
18420 /* Piece placement data */
18421 for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18422 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18424 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18425 if (boards[move][i][j] == EmptySquare) {
18427 } else { ChessSquare piece = boards[move][i][j];
18428 if (emptycount > 0) {
18429 if(emptycount<10) /* [HGM] can be >= 10 */
18430 *p++ = '0' + emptycount;
18431 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18434 if(PieceToChar(piece) == '+') {
18435 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18437 piece = (ChessSquare)(CHUDEMOTED(piece));
18439 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18440 if(*p = PieceSuffix(piece)) p++;
18442 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18443 p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18448 if (emptycount > 0) {
18449 if(emptycount<10) /* [HGM] can be >= 10 */
18450 *p++ = '0' + emptycount;
18451 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18458 /* [HGM] print Crazyhouse or Shogi holdings */
18459 if( gameInfo.holdingsWidth ) {
18460 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18462 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18463 piece = boards[move][i][BOARD_WIDTH-1];
18464 if( piece != EmptySquare )
18465 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18466 *p++ = PieceToChar(piece);
18468 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18469 piece = boards[move][handSize-i-1][0];
18470 if( piece != EmptySquare )
18471 for(j=0; j<(int) boards[move][handSize-i-1][1]; j++)
18472 *p++ = PieceToChar(piece);
18475 if( q == p ) *p++ = '-';
18481 *p++ = whiteToPlay ? 'w' : 'b';
18484 if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18485 haveRights = 0; q = p;
18486 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18487 piece = boards[move][0][i];
18488 if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18489 if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18492 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18493 piece = boards[move][BOARD_HEIGHT-1][i];
18494 if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18495 if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18498 if(p == q) *p++ = '-';
18502 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18505 if(q != overrideCastling+1) p[-1] = ' '; else --p;
18508 int handW=0, handB=0;
18509 if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18510 for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18511 for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18514 if(appData.fischerCastling) {
18515 if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18516 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18517 if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18519 /* [HGM] write directly from rights */
18520 if(boards[move][CASTLING][2] != NoRights &&
18521 boards[move][CASTLING][0] != NoRights )
18522 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18523 if(boards[move][CASTLING][2] != NoRights &&
18524 boards[move][CASTLING][1] != NoRights )
18525 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18528 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18529 if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18531 if(boards[move][CASTLING][5] != NoRights &&
18532 boards[move][CASTLING][3] != NoRights )
18533 *p++ = boards[move][CASTLING][3] + AAA;
18534 if(boards[move][CASTLING][5] != NoRights &&
18535 boards[move][CASTLING][4] != NoRights )
18536 *p++ = boards[move][CASTLING][4] + AAA;
18540 /* [HGM] write true castling rights */
18541 if( nrCastlingRights == 6 ) {
18543 if(boards[move][CASTLING][0] != NoRights &&
18544 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
18545 q = (boards[move][CASTLING][1] != NoRights &&
18546 boards[move][CASTLING][2] != NoRights );
18547 if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18548 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18549 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18550 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18554 if(boards[move][CASTLING][3] != NoRights &&
18555 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
18556 q = (boards[move][CASTLING][4] != NoRights &&
18557 boards[move][CASTLING][5] != NoRights );
18559 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18560 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18561 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18566 if (q == p) *p++ = '-'; /* No castling rights */
18570 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
18571 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18572 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18573 /* En passant target square */
18574 if (move > backwardMostMove) {
18575 fromX = moveList[move - 1][0] - AAA;
18576 fromY = moveList[move - 1][1] - ONE;
18577 toX = moveList[move - 1][2] - AAA;
18578 toY = moveList[move - 1][3] - ONE;
18579 if ((whiteToPlay ? toY < fromY - 1 : toY > fromY + 1) &&
18580 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) ) {
18581 /* 2-square pawn move just happened */
18582 *p++ = (3*toX + 5*fromX + 4)/8 + AAA;
18583 *p++ = (3*toY + 5*fromY + 4)/8 + ONE;
18584 if(gameInfo.variant == VariantBerolina) {
18591 } else if(move == backwardMostMove) {
18592 // [HGM] perhaps we should always do it like this, and forget the above?
18593 if((signed char)boards[move][EP_STATUS] >= 0) {
18594 *p++ = boards[move][EP_STATUS] + AAA;
18595 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18606 i = boards[move][CHECK_COUNT];
18608 sprintf(p, "%d+%d ", i&255, i>>8);
18613 { int i = 0, j=move;
18615 /* [HGM] find reversible plies */
18616 if (appData.debugMode) { int k;
18617 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18618 for(k=backwardMostMove; k<=forwardMostMove; k++)
18619 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18623 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18624 if( j == backwardMostMove ) i += initialRulePlies;
18625 sprintf(p, "%d ", i);
18626 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18628 /* Fullmove number */
18629 sprintf(p, "%d", (move / 2) + 1);
18630 } else *--p = NULLCHAR;
18632 return StrSave(buf);
18636 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18638 int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18640 int emptycount, virgin[BOARD_FILES];
18641 ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18645 for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) board[BOARD_HEIGHT-i][j] = DarkSquare;
18647 /* Piece placement data */
18648 for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18651 if (*p == '/' || *p == ' ' || *p == '[' ) {
18653 emptycount = gameInfo.boardWidth - j;
18654 while (emptycount--)
18655 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18656 if (*p == '/') p++;
18657 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18658 for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18659 for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18661 appData.NrRanks = gameInfo.boardHeight - i; i=0;
18664 #if(BOARD_FILES >= 10)*0
18665 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18666 p++; emptycount=10;
18667 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18668 while (emptycount--)
18669 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18671 } else if (*p == '*') {
18672 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18673 } else if (isdigit(*p)) {
18674 emptycount = *p++ - '0';
18675 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18676 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18677 while (emptycount--)
18678 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18679 } else if (*p == '<') {
18680 if(i == BOARD_HEIGHT-1) shuffle = 1;
18681 else if (i != 0 || !shuffle) return FALSE;
18683 } else if (shuffle && *p == '>') {
18684 p++; // for now ignore closing shuffle range, and assume rank-end
18685 } else if (*p == '?') {
18686 if (j >= gameInfo.boardWidth) return FALSE;
18687 if (i != 0 && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18688 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18689 } else if (*p == '+' || isalpha(*p)) {
18690 char *q, *s = SUFFIXES;
18691 if (j >= gameInfo.boardWidth) return FALSE;
18694 if(q = strchr(s, p[1])) p++;
18695 piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18696 if(piece == EmptySquare) return FALSE; /* unknown piece */
18697 piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18698 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18701 if(q = strchr(s, *p)) p++;
18702 piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18705 if(piece==EmptySquare) return FALSE; /* unknown piece */
18706 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18707 piece = (ChessSquare) (PROMOTED(piece));
18708 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18711 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18712 if(piece == king) wKingRank = i;
18713 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18719 while (*p == '/' || *p == ' ') p++;
18721 if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18723 /* [HGM] by default clear Crazyhouse holdings, if present */
18724 if(gameInfo.holdingsWidth) {
18725 for(i=0; i<handSize; i++) {
18726 board[i][0] = EmptySquare; /* black holdings */
18727 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18728 board[i][1] = (ChessSquare) 0; /* black counts */
18729 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18733 /* [HGM] look for Crazyhouse holdings here */
18734 while(*p==' ') p++;
18735 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18736 int swap=0, wcnt=0, bcnt=0;
18738 if(*p == '<') swap++, p++;
18739 if(*p == '-' ) p++; /* empty holdings */ else {
18740 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18741 /* if we would allow FEN reading to set board size, we would */
18742 /* have to add holdings and shift the board read so far here */
18743 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18745 if((int) piece >= (int) BlackPawn ) {
18746 i = (int)piece - (int)BlackPawn;
18747 i = PieceToNumber((ChessSquare)i);
18748 if( i >= gameInfo.holdingsSize ) return FALSE;
18749 board[handSize-1-i][0] = piece; /* black holdings */
18750 board[handSize-1-i][1]++; /* black counts */
18753 i = (int)piece - (int)WhitePawn;
18754 i = PieceToNumber((ChessSquare)i);
18755 if( i >= gameInfo.holdingsSize ) return FALSE;
18756 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
18757 board[i][BOARD_WIDTH-2]++; /* black holdings */
18761 if(subst) { // substitute back-rank question marks by holdings pieces
18762 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18763 int k, m, n = bcnt + 1;
18764 if(board[0][j] == ClearBoard) {
18765 if(!wcnt) return FALSE;
18767 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18768 board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18769 if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18773 if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18774 if(!bcnt) return FALSE;
18775 if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18776 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[handSize-1-k][1]) < 0) {
18777 board[BOARD_HEIGHT-1][j] = board[handSize-1-k][0]; bcnt--;
18778 if(--board[handSize-1-k][1] == 0) board[handSize-1-k][0] = EmptySquare;
18789 if(subst) return FALSE; // substitution requested, but no holdings
18791 while(*p == ' ') p++;
18795 if(appData.colorNickNames) {
18796 if( c == appData.colorNickNames[0] ) c = 'w'; else
18797 if( c == appData.colorNickNames[1] ) c = 'b';
18801 *blackPlaysFirst = FALSE;
18804 *blackPlaysFirst = TRUE;
18810 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18811 /* return the extra info in global variiables */
18813 while(*p==' ') p++;
18815 if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18816 if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18817 if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18820 /* set defaults in case FEN is incomplete */
18821 board[EP_STATUS] = EP_UNKNOWN;
18822 board[TOUCHED_W] = board[TOUCHED_B] = 0;
18823 for(i=0; i<nrCastlingRights; i++ ) {
18824 board[CASTLING][i] =
18825 appData.fischerCastling ? NoRights : initialRights[i];
18826 } /* assume possible unless obviously impossible */
18827 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18828 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18829 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18830 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18831 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18832 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18833 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18834 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18837 if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18840 while(isalpha(*p)) {
18841 if(isupper(*p)) w |= 1 << (*p++ - 'A');
18842 if(islower(*p)) b |= 1 << (*p++ - 'a');
18846 board[TOUCHED_W] = ~w;
18847 board[TOUCHED_B] = ~b;
18848 while(*p == ' ') p++;
18852 if(nrCastlingRights) {
18854 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18855 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18856 /* castling indicator present, so default becomes no castlings */
18857 for(i=0; i<nrCastlingRights; i++ ) {
18858 board[CASTLING][i] = NoRights;
18861 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18862 (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18863 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18864 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
18865 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18867 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18868 if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18869 if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18871 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18872 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18873 if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18874 && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18875 if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18876 && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18879 for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18880 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18881 board[CASTLING][2] = whiteKingFile;
18882 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18883 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18884 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18887 for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18888 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18889 board[CASTLING][2] = whiteKingFile;
18890 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18891 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18892 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18895 for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18896 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18897 board[CASTLING][5] = blackKingFile;
18898 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18899 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18900 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18903 for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18904 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18905 board[CASTLING][5] = blackKingFile;
18906 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18907 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18908 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18911 default: /* FRC castlings */
18912 if(c >= 'a') { /* black rights */
18913 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18914 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18915 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18916 if(i == BOARD_RGHT) break;
18917 board[CASTLING][5] = i;
18919 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
18920 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
18922 board[CASTLING][3] = c;
18924 board[CASTLING][4] = c;
18925 } else { /* white rights */
18926 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18927 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18928 if(board[0][i] == WhiteKing) break;
18929 if(i == BOARD_RGHT) break;
18930 board[CASTLING][2] = i;
18931 c -= AAA - 'a' + 'A';
18932 if(board[0][c] >= WhiteKing) break;
18934 board[CASTLING][0] = c;
18936 board[CASTLING][1] = c;
18940 for(i=0; i<nrCastlingRights; i++)
18941 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18942 if(gameInfo.variant == VariantSChess)
18943 for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18944 if(fischer && shuffle) appData.fischerCastling = TRUE;
18945 if (appData.debugMode) {
18946 fprintf(debugFP, "FEN castling rights:");
18947 for(i=0; i<nrCastlingRights; i++)
18948 fprintf(debugFP, " %d", board[CASTLING][i]);
18949 fprintf(debugFP, "\n");
18952 while(*p==' ') p++;
18955 if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18957 /* read e.p. field in games that know e.p. capture */
18958 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
18959 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18960 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18962 p++; board[EP_STATUS] = EP_NONE;
18964 int d, r, c = *p - AAA;
18966 if(c >= BOARD_LEFT && c < BOARD_RGHT) {
18968 board[EP_STATUS] = board[EP_FILE] = c; r = 0;
18969 if(*p >= '0' && *p <='9') r = board[EP_RANK] = *p++ - ONE;
18970 d = (r < BOARD_HEIGHT << 1 ? 1 : -1); // assume double-push (P next to e.p. square nearer center)
18971 if(board[r+d][c] == EmptySquare) d *= 2; // but if no Pawn there, triple push
18972 board[LAST_TO] = 256*(r + d) + c;
18974 if(c >= BOARD_LEFT && c < BOARD_RGHT) { // mover explicitly mentioned
18975 if(*p >= '0' && *p <='9') r = board[EP_RANK] = *p++ - ONE;
18976 board[LAST_TO] = 256*r + c;
18977 if(!(board[EP_RANK]-r & 1)) board[EP_RANK] |= 128;
18983 while(*p == ' ') p++;
18985 board[CHECK_COUNT] = 0; // [HGM] 3check: check-count field
18986 if(sscanf(p, "%d+%d", &i, &j) == 2) {
18987 board[CHECK_COUNT] = i + 256*j;
18988 while(*p && *p != ' ') p++;
18991 c = sscanf(p, "%d%*d +%d+%d", &i, &j, &k);
18993 FENrulePlies = i; /* 50-move ply counter */
18994 /* (The move number is still ignored) */
18995 if(c == 3 && !board[CHECK_COUNT]) board[CHECK_COUNT] = (3 - j) + 256*(3 - k); // SCIDB-style check count
19002 EditPositionPasteFEN (char *fen)
19005 Board initial_position;
19007 if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
19008 DisplayError(_("Bad FEN position in clipboard"), 0);
19011 int savedBlackPlaysFirst = blackPlaysFirst;
19012 EditPositionEvent();
19013 blackPlaysFirst = savedBlackPlaysFirst;
19014 CopyBoard(boards[0], initial_position);
19015 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
19016 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
19017 DisplayBothClocks();
19018 DrawPosition(FALSE, boards[currentMove]);
19023 static char cseq[12] = "\\ ";
19026 set_cont_sequence (char *new_seq)
19031 // handle bad attempts to set the sequence
19033 return 0; // acceptable error - no debug
19035 len = strlen(new_seq);
19036 ret = (len > 0) && (len < sizeof(cseq));
19038 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
19039 else if (appData.debugMode)
19040 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
19045 reformat a source message so words don't cross the width boundary. internal
19046 newlines are not removed. returns the wrapped size (no null character unless
19047 included in source message). If dest is NULL, only calculate the size required
19048 for the dest buffer. lp argument indicats line position upon entry, and it's
19049 passed back upon exit.
19052 wrap (char *dest, char *src, int count, int width, int *lp)
19054 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
19056 cseq_len = strlen(cseq);
19057 old_line = line = *lp;
19058 ansi = len = clen = 0;
19060 for (i=0; i < count; i++)
19062 if (src[i] == '\033')
19065 // if we hit the width, back up
19066 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
19068 // store i & len in case the word is too long
19069 old_i = i, old_len = len;
19071 // find the end of the last word
19072 while (i && src[i] != ' ' && src[i] != '\n')
19078 // word too long? restore i & len before splitting it
19079 if ((old_i-i+clen) >= width)
19086 if (i && src[i-1] == ' ')
19089 if (src[i] != ' ' && src[i] != '\n')
19096 // now append the newline and continuation sequence
19101 strncpy(dest+len, cseq, cseq_len);
19109 dest[len] = src[i];
19113 if (src[i] == '\n')
19118 if (dest && appData.debugMode)
19120 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
19121 count, width, line, len, *lp);
19122 show_bytes(debugFP, src, count);
19123 fprintf(debugFP, "\ndest: ");
19124 show_bytes(debugFP, dest, len);
19125 fprintf(debugFP, "\n");
19127 *lp = dest ? line : old_line;
19132 // [HGM] vari: routines for shelving variations
19133 Boolean modeRestore = FALSE;
19136 PushInner (int firstMove, int lastMove)
19138 int i, j, nrMoves = lastMove - firstMove;
19140 // push current tail of game on stack
19141 savedResult[storedGames] = gameInfo.result;
19142 savedDetails[storedGames] = gameInfo.resultDetails;
19143 gameInfo.resultDetails = NULL;
19144 savedFirst[storedGames] = firstMove;
19145 savedLast [storedGames] = lastMove;
19146 savedFramePtr[storedGames] = framePtr;
19147 framePtr -= nrMoves; // reserve space for the boards
19148 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
19149 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
19150 for(j=0; j<MOVE_LEN; j++)
19151 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
19152 for(j=0; j<2*MOVE_LEN; j++)
19153 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
19154 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
19155 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
19156 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
19157 pvInfoList[firstMove+i-1].depth = 0;
19158 commentList[framePtr+i] = commentList[firstMove+i];
19159 commentList[firstMove+i] = NULL;
19163 forwardMostMove = firstMove; // truncate game so we can start variation
19167 PushTail (int firstMove, int lastMove)
19169 if(appData.icsActive) { // only in local mode
19170 forwardMostMove = currentMove; // mimic old ICS behavior
19173 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
19175 PushInner(firstMove, lastMove);
19176 if(storedGames == 1) GreyRevert(FALSE);
19177 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
19181 PopInner (Boolean annotate)
19184 char buf[8000], moveBuf[20];
19186 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
19187 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
19188 nrMoves = savedLast[storedGames] - currentMove;
19191 if(!WhiteOnMove(currentMove))
19192 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
19193 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
19194 for(i=currentMove; i<forwardMostMove; i++) {
19196 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
19197 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
19198 strcat(buf, moveBuf);
19199 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
19200 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
19204 for(i=1; i<=nrMoves; i++) { // copy last variation back
19205 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
19206 for(j=0; j<MOVE_LEN; j++)
19207 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
19208 for(j=0; j<2*MOVE_LEN; j++)
19209 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
19210 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
19211 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
19212 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
19213 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
19214 commentList[currentMove+i] = commentList[framePtr+i];
19215 commentList[framePtr+i] = NULL;
19217 if(annotate) AppendComment(currentMove+1, buf, FALSE);
19218 framePtr = savedFramePtr[storedGames];
19219 gameInfo.result = savedResult[storedGames];
19220 if(gameInfo.resultDetails != NULL) {
19221 free(gameInfo.resultDetails);
19223 gameInfo.resultDetails = savedDetails[storedGames];
19224 forwardMostMove = currentMove + nrMoves;
19228 PopTail (Boolean annotate)
19230 if(appData.icsActive) return FALSE; // only in local mode
19231 if(!storedGames) return FALSE; // sanity
19232 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
19234 PopInner(annotate);
19235 if(currentMove < forwardMostMove) ForwardEvent(); else
19236 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
19238 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
19244 { // remove all shelved variations
19246 for(i=0; i<storedGames; i++) {
19247 if(savedDetails[i])
19248 free(savedDetails[i]);
19249 savedDetails[i] = NULL;
19251 for(i=framePtr; i<MAX_MOVES; i++) {
19252 if(commentList[i]) free(commentList[i]);
19253 commentList[i] = NULL;
19255 framePtr = MAX_MOVES-1;
19260 LoadVariation (int index, char *text)
19261 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
19262 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
19263 int level = 0, move;
19265 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
19266 // first find outermost bracketing variation
19267 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
19268 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
19269 if(*p == '{') wait = '}'; else
19270 if(*p == '[') wait = ']'; else
19271 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
19272 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
19274 if(*p == wait) wait = NULLCHAR; // closing ]} found
19277 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19278 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19279 end[1] = NULLCHAR; // clip off comment beyond variation
19280 ToNrEvent(currentMove-1);
19281 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19282 // kludge: use ParsePV() to append variation to game
19283 move = currentMove;
19284 ParsePV(start, TRUE, TRUE);
19285 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19286 ClearPremoveHighlights();
19288 ToNrEvent(currentMove+1);
19291 int transparency[2];
19296 #define BUF_SIZ (2*MSG_SIZ)
19297 char *p, *q, buf[BUF_SIZ];
19298 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19299 snprintf(buf, BUF_SIZ, "-theme %s", engineLine);
19300 ParseArgsFromString(buf);
19301 ActivateTheme(TRUE); // also redo colors
19305 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19308 q = appData.themeNames;
19309 snprintf(buf, BUF_SIZ, "\"%s\"", nickName);
19310 if(appData.useBitmaps) {
19311 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt true -lbtf \"%s\"",
19312 Shorten(appData.liteBackTextureFile));
19313 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dbtf \"%s\" -lbtm %d -dbtm %d",
19314 Shorten(appData.darkBackTextureFile),
19315 appData.liteBackTextureMode,
19316 appData.darkBackTextureMode );
19318 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt false");
19320 if(!appData.useBitmaps || transparency[0]) {
19321 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -lsc %s", Col2Text(2) ); // lightSquareColor
19323 if(!appData.useBitmaps || transparency[1]) {
19324 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dsc %s", Col2Text(3) ); // darkSquareColor
19326 if(appData.useBorder) {
19327 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub true -border \"%s\"",
19330 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub false");
19332 if(appData.useFont) {
19333 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19334 appData.renderPiecesWithFont,
19335 appData.fontToPieceTable,
19336 Col2Text(9), // appData.fontBackColorWhite
19337 Col2Text(10) ); // appData.fontForeColorBlack
19339 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf false");
19340 if(appData.pieceDirectory[0]) {
19341 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -pid \"%s\"", Shorten(appData.pieceDirectory));
19342 if(appData.trueColors != 2) // 2 is a kludge to suppress this in WinBoard
19343 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -trueColors %s", appData.trueColors ? "true" : "false");
19345 if(!appData.pieceDirectory[0] || !appData.trueColors)
19346 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -wpc %s -bpc %s",
19347 Col2Text(0), // whitePieceColor
19348 Col2Text(1) ); // blackPieceColor
19350 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19351 Col2Text(4), // highlightSquareColor
19352 Col2Text(5) ); // premoveHighlightColor
19353 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19354 if(insert != q) insert[-1] = NULLCHAR;
19355 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19358 ActivateTheme(FALSE);