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 */
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;
922 if(WaitForEngine(savCps, LoadEngine)) return;
923 CommonEngineInit(); // recalculate time odds
924 if(gameInfo.variant != StringToVariant(appData.variant)) {
925 // we changed variant when loading the engine; this forces us to reset
926 Reset(TRUE, savCps != &first);
927 oldMode = BeginningOfGame; // to prevent restoring old mode
929 InitChessProgram(savCps, FALSE);
930 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
931 DisplayMessage("", "");
932 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
933 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
936 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
940 ReplaceEngine (ChessProgramState *cps, int n)
942 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
944 if(oldMode != BeginningOfGame) EditGameEvent();
947 appData.noChessProgram = FALSE;
948 appData.clockMode = TRUE;
951 if(n) return; // only startup first engine immediately; second can wait
952 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
956 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
957 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
959 static char resetOptions[] =
960 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
961 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
962 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
963 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
966 FloatToFront(char **list, char *engineLine)
968 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
970 if(appData.recentEngines <= 0) return;
971 TidyProgramName(engineLine, "localhost", tidy+1);
972 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
973 strncpy(buf+1, *list, MSG_SIZ-50);
974 if(p = strstr(buf, tidy)) { // tidy name appears in list
975 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
976 while(*p++ = *++q); // squeeze out
978 strcat(tidy, buf+1); // put list behind tidy name
979 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
980 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
981 ASSIGN(*list, tidy+1);
984 char *insert, *wbOptions, *currentEngine[2]; // point in ChessProgramNames were we should insert new engine
987 Load (ChessProgramState *cps, int i)
989 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
990 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
991 ASSIGN(currentEngine[i], engineLine);
992 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
993 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
994 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
995 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
996 appData.firstProtocolVersion = PROTOVER;
997 ParseArgsFromString(buf);
999 ReplaceEngine(cps, i);
1000 FloatToFront(&appData.recentEngineList, engineLine);
1001 if(gameMode == BeginningOfGame) Reset(TRUE, TRUE);
1005 while(q = strchr(p, SLASH)) p = q+1;
1006 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1007 if(engineDir[0] != NULLCHAR) {
1008 ASSIGN(appData.directory[i], engineDir); p = engineName;
1009 } else if(p != engineName) { // derive directory from engine path, when not given
1011 ASSIGN(appData.directory[i], engineName);
1013 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1014 } else { ASSIGN(appData.directory[i], "."); }
1015 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1017 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1018 snprintf(command, MSG_SIZ, "%s %s", p, params);
1021 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1022 ASSIGN(appData.chessProgram[i], p);
1023 appData.isUCI[i] = isUCI;
1024 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1025 appData.hasOwnBookUCI[i] = hasBook;
1026 if(!nickName[0]) useNick = FALSE;
1027 if(useNick) ASSIGN(appData.pgnName[i], nickName);
1031 q = firstChessProgramNames;
1032 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1033 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1034 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s",
1035 quote, p, quote, appData.directory[i],
1036 useNick ? " -fn \"" : "",
1037 useNick ? nickName : "",
1038 useNick ? "\"" : "",
1039 v1 ? " -firstProtocolVersion 1" : "",
1040 hasBook ? "" : " -fNoOwnBookUCI",
1041 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1042 storeVariant ? " -variant " : "",
1043 storeVariant ? VariantName(gameInfo.variant) : "");
1044 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " %s", wbOptions);
1045 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 2);
1046 if(insert != q) insert[-1] = NULLCHAR;
1047 snprintf(firstChessProgramNames, len, "%s\n%s\n%s", q, buf, insert);
1049 FloatToFront(&appData.recentEngineList, buf);
1050 ASSIGN(currentEngine[i], buf);
1052 ReplaceEngine(cps, i);
1058 int matched, min, sec;
1060 * Parse timeControl resource
1062 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1063 appData.movesPerSession)) {
1065 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1066 DisplayFatalError(buf, 0, 2);
1070 * Parse searchTime resource
1072 if (*appData.searchTime != NULLCHAR) {
1073 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1075 searchTime = min * 60;
1076 } else if (matched == 2) {
1077 searchTime = min * 60 + sec;
1080 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1081 DisplayFatalError(buf, 0, 2);
1090 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1091 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1093 GetTimeMark(&programStartTime);
1094 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1095 appData.seedBase = random() + (random()<<15);
1096 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1098 ClearProgramStats();
1099 programStats.ok_to_send = 1;
1100 programStats.seen_stat = 0;
1103 * Initialize game list
1109 * Internet chess server status
1111 if (appData.icsActive) {
1112 appData.matchMode = FALSE;
1113 appData.matchGames = 0;
1115 appData.noChessProgram = !appData.zippyPlay;
1117 appData.zippyPlay = FALSE;
1118 appData.zippyTalk = FALSE;
1119 appData.noChessProgram = TRUE;
1121 if (*appData.icsHelper != NULLCHAR) {
1122 appData.useTelnet = TRUE;
1123 appData.telnetProgram = appData.icsHelper;
1126 appData.zippyTalk = appData.zippyPlay = FALSE;
1129 /* [AS] Initialize pv info list [HGM] and game state */
1133 for( i=0; i<=framePtr; i++ ) {
1134 pvInfoList[i].depth = -1;
1135 boards[i][EP_STATUS] = EP_NONE;
1136 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1142 /* [AS] Adjudication threshold */
1143 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1145 InitEngine(&first, 0);
1146 InitEngine(&second, 1);
1149 pairing.which = "pairing"; // pairing engine
1150 pairing.pr = NoProc;
1152 pairing.program = appData.pairingEngine;
1153 pairing.host = "localhost";
1156 if (appData.icsActive) {
1157 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1158 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1159 appData.clockMode = FALSE;
1160 first.sendTime = second.sendTime = 0;
1164 /* Override some settings from environment variables, for backward
1165 compatibility. Unfortunately it's not feasible to have the env
1166 vars just set defaults, at least in xboard. Ugh.
1168 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1173 if (!appData.icsActive) {
1177 /* Check for variants that are supported only in ICS mode,
1178 or not at all. Some that are accepted here nevertheless
1179 have bugs; see comments below.
1181 VariantClass variant = StringToVariant(appData.variant);
1183 case VariantBughouse: /* need four players and two boards */
1184 case VariantKriegspiel: /* need to hide pieces and move details */
1185 /* case VariantFischeRandom: (Fabien: moved below) */
1186 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1187 if( (len >= MSG_SIZ) && appData.debugMode )
1188 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1190 DisplayFatalError(buf, 0, 2);
1193 case VariantUnknown:
1194 case VariantLoadable:
1204 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1205 if( (len >= MSG_SIZ) && appData.debugMode )
1206 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1208 DisplayFatalError(buf, 0, 2);
1211 case VariantNormal: /* definitely works! */
1212 if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1213 safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1216 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1217 case VariantFairy: /* [HGM] TestLegality definitely off! */
1218 case VariantGothic: /* [HGM] should work */
1219 case VariantCapablanca: /* [HGM] should work */
1220 case VariantCourier: /* [HGM] initial forced moves not implemented */
1221 case VariantShogi: /* [HGM] could still mate with pawn drop */
1222 case VariantChu: /* [HGM] experimental */
1223 case VariantKnightmate: /* [HGM] should work */
1224 case VariantCylinder: /* [HGM] untested */
1225 case VariantFalcon: /* [HGM] untested */
1226 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1227 offboard interposition not understood */
1228 case VariantWildCastle: /* pieces not automatically shuffled */
1229 case VariantNoCastle: /* pieces not automatically shuffled */
1230 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1231 case VariantLosers: /* should work except for win condition,
1232 and doesn't know captures are mandatory */
1233 case VariantSuicide: /* should work except for win condition,
1234 and doesn't know captures are mandatory */
1235 case VariantGiveaway: /* should work except for win condition,
1236 and doesn't know captures are mandatory */
1237 case VariantTwoKings: /* should work */
1238 case VariantAtomic: /* should work except for win condition */
1239 case Variant3Check: /* should work except for win condition */
1240 case VariantShatranj: /* should work except for all win conditions */
1241 case VariantMakruk: /* should work except for draw countdown */
1242 case VariantASEAN : /* should work except for draw countdown */
1243 case VariantBerolina: /* might work if TestLegality is off */
1244 case VariantCapaRandom: /* should work */
1245 case VariantJanus: /* should work */
1246 case VariantSuper: /* experimental */
1247 case VariantGreat: /* experimental, requires legality testing to be off */
1248 case VariantSChess: /* S-Chess, should work */
1249 case VariantGrand: /* should work */
1250 case VariantSpartan: /* should work */
1251 case VariantLion: /* should work */
1252 case VariantChuChess: /* should work */
1260 NextIntegerFromString (char ** str, long * value)
1265 while( *s == ' ' || *s == '\t' ) {
1271 if( *s >= '0' && *s <= '9' ) {
1272 while( *s >= '0' && *s <= '9' ) {
1273 *value = *value * 10 + (*s - '0');
1286 NextTimeControlFromString (char ** str, long * value)
1289 int result = NextIntegerFromString( str, &temp );
1292 *value = temp * 60; /* Minutes */
1293 if( **str == ':' ) {
1295 result = NextIntegerFromString( str, &temp );
1296 *value += temp; /* Seconds */
1304 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1305 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1306 int result = -1, type = 0; long temp, temp2;
1308 if(**str != ':') return -1; // old params remain in force!
1310 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1311 if( NextIntegerFromString( str, &temp ) ) return -1;
1312 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1315 /* time only: incremental or sudden-death time control */
1316 if(**str == '+') { /* increment follows; read it */
1318 if(**str == '!') type = *(*str)++; // Bronstein TC
1319 if(result = NextIntegerFromString( str, &temp2)) return -1;
1320 *inc = temp2 * 1000;
1321 if(**str == '.') { // read fraction of increment
1322 char *start = ++(*str);
1323 if(result = NextIntegerFromString( str, &temp2)) return -1;
1325 while(start++ < *str) temp2 /= 10;
1329 *moves = 0; *tc = temp * 1000; *incType = type;
1333 (*str)++; /* classical time control */
1334 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1346 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1347 { /* [HGM] get time to add from the multi-session time-control string */
1348 int incType, moves=1; /* kludge to force reading of first session */
1349 long time, increment;
1352 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1354 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1355 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1356 if(movenr == -1) return time; /* last move before new session */
1357 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1358 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1359 if(!moves) return increment; /* current session is incremental */
1360 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1361 } while(movenr >= -1); /* try again for next session */
1363 return 0; // no new time quota on this move
1367 ParseTimeControl (char *tc, float ti, int mps)
1371 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1374 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1375 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1376 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1380 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1382 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1385 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1387 snprintf(buf, MSG_SIZ, ":%s", mytc);
1389 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1391 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1396 /* Parse second time control */
1399 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1407 timeControl_2 = tc2 * 1000;
1417 timeControl = tc1 * 1000;
1420 timeIncrement = ti * 1000; /* convert to ms */
1421 movesPerSession = 0;
1424 movesPerSession = mps;
1432 if (appData.debugMode) {
1433 # ifdef __GIT_VERSION
1434 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1436 fprintf(debugFP, "Version: %s\n", programVersion);
1439 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1441 set_cont_sequence(appData.wrapContSeq);
1442 if (appData.matchGames > 0) {
1443 appData.matchMode = TRUE;
1444 } else if (appData.matchMode) {
1445 appData.matchGames = 1;
1447 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1448 appData.matchGames = appData.sameColorGames;
1449 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1450 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1451 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1454 if (appData.noChessProgram || first.protocolVersion == 1) {
1457 /* kludge: allow timeout for initial "feature" commands */
1459 DisplayMessage("", _("Starting chess program"));
1460 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1465 CalculateIndex (int index, int gameNr)
1466 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1468 if(index > 0) return index; // fixed nmber
1469 if(index == 0) return 1;
1470 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1471 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1476 LoadGameOrPosition (int gameNr)
1477 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1478 if (*appData.loadGameFile != NULLCHAR) {
1479 if (!LoadGameFromFile(appData.loadGameFile,
1480 CalculateIndex(appData.loadGameIndex, gameNr),
1481 appData.loadGameFile, FALSE)) {
1482 DisplayFatalError(_("Bad game file"), 0, 1);
1485 } else if (*appData.loadPositionFile != NULLCHAR) {
1486 if (!LoadPositionFromFile(appData.loadPositionFile,
1487 CalculateIndex(appData.loadPositionIndex, gameNr),
1488 appData.loadPositionFile)) {
1489 DisplayFatalError(_("Bad position file"), 0, 1);
1497 ReserveGame (int gameNr, char resChar)
1499 FILE *tf = fopen(appData.tourneyFile, "r+");
1500 char *p, *q, c, buf[MSG_SIZ];
1501 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1502 safeStrCpy(buf, lastMsg, MSG_SIZ);
1503 DisplayMessage(_("Pick new game"), "");
1504 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1505 ParseArgsFromFile(tf);
1506 p = q = appData.results;
1507 if(appData.debugMode) {
1508 char *r = appData.participants;
1509 fprintf(debugFP, "results = '%s'\n", p);
1510 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1511 fprintf(debugFP, "\n");
1513 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1515 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1516 safeStrCpy(q, p, strlen(p) + 2);
1517 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1518 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1519 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1520 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1523 fseek(tf, -(strlen(p)+4), SEEK_END);
1525 if(c != '"') // depending on DOS or Unix line endings we can be one off
1526 fseek(tf, -(strlen(p)+2), SEEK_END);
1527 else fseek(tf, -(strlen(p)+3), SEEK_END);
1528 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1529 DisplayMessage(buf, "");
1530 free(p); appData.results = q;
1531 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1532 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1533 int round = appData.defaultMatchGames * appData.tourneyType;
1534 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1535 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1536 UnloadEngine(&first); // next game belongs to other pairing;
1537 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1539 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1543 MatchEvent (int mode)
1544 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1546 if(matchMode) { // already in match mode: switch it off
1548 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1551 // if(gameMode != BeginningOfGame) {
1552 // DisplayError(_("You can only start a match from the initial position."), 0);
1556 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1557 /* Set up machine vs. machine match */
1559 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1560 if(appData.tourneyFile[0]) {
1562 if(nextGame > appData.matchGames) {
1564 if(strchr(appData.results, '*') == NULL) {
1566 appData.tourneyCycles++;
1567 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1569 NextTourneyGame(-1, &dummy);
1571 if(nextGame <= appData.matchGames) {
1572 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1574 ScheduleDelayedEvent(NextMatchGame, 10000);
1579 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1580 DisplayError(buf, 0);
1581 appData.tourneyFile[0] = 0;
1585 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1586 DisplayFatalError(_("Can't have a match with no chess programs"),
1591 matchGame = roundNr = 1;
1592 first.matchWins = second.matchWins = totalTime = 0; // [HGM] match: needed in later matches
1597 InitBackEnd3 P((void))
1599 GameMode initialMode;
1603 ParseFeatures(appData.features[0], &first);
1604 if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode && // mode involves only first engine
1605 !strcmp(appData.variant, "normal") && // no explicit variant request
1606 appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 && // no size overrides requested
1607 !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") && // but 'normal' won't work with engine
1608 !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1609 char c, *q = first.variants, *p = strchr(q, ',');
1610 if(p) *p = NULLCHAR;
1611 if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1613 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1614 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1615 ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1616 Reset(TRUE, FALSE); // and re-initialize
1621 InitChessProgram(&first, startedFromSetupPosition);
1623 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1624 free(programVersion);
1625 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1626 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1627 FloatToFront(&appData.recentEngineList, currentEngine[0] ? currentEngine[0] : appData.firstChessProgram);
1630 if (appData.icsActive) {
1632 /* [DM] Make a console window if needed [HGM] merged ifs */
1638 if (*appData.icsCommPort != NULLCHAR)
1639 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1640 appData.icsCommPort);
1642 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1643 appData.icsHost, appData.icsPort);
1645 if( (len >= MSG_SIZ) && appData.debugMode )
1646 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1648 DisplayFatalError(buf, err, 1);
1653 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1655 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1656 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1657 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1658 } else if (appData.noChessProgram) {
1664 if (*appData.cmailGameName != NULLCHAR) {
1666 OpenLoopback(&cmailPR);
1668 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1672 DisplayMessage("", "");
1673 if (StrCaseCmp(appData.initialMode, "") == 0) {
1674 initialMode = BeginningOfGame;
1675 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1676 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1677 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1678 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1681 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1682 initialMode = TwoMachinesPlay;
1683 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1684 initialMode = AnalyzeFile;
1685 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1686 initialMode = AnalyzeMode;
1687 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1688 initialMode = MachinePlaysWhite;
1689 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1690 initialMode = MachinePlaysBlack;
1691 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1692 initialMode = EditGame;
1693 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1694 initialMode = EditPosition;
1695 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1696 initialMode = Training;
1698 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1699 if( (len >= MSG_SIZ) && appData.debugMode )
1700 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1702 DisplayFatalError(buf, 0, 2);
1706 if (appData.matchMode) {
1707 if(appData.tourneyFile[0]) { // start tourney from command line
1709 if(f = fopen(appData.tourneyFile, "r")) {
1710 ParseArgsFromFile(f); // make sure tourney parmeters re known
1712 appData.clockMode = TRUE;
1714 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1717 } else if (*appData.cmailGameName != NULLCHAR) {
1718 /* Set up cmail mode */
1719 ReloadCmailMsgEvent(TRUE);
1721 /* Set up other modes */
1722 if (initialMode == AnalyzeFile) {
1723 if (*appData.loadGameFile == NULLCHAR) {
1724 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1728 if (*appData.loadGameFile != NULLCHAR) {
1729 (void) LoadGameFromFile(appData.loadGameFile,
1730 appData.loadGameIndex,
1731 appData.loadGameFile, TRUE);
1732 } else if (*appData.loadPositionFile != NULLCHAR) {
1733 (void) LoadPositionFromFile(appData.loadPositionFile,
1734 appData.loadPositionIndex,
1735 appData.loadPositionFile);
1736 /* [HGM] try to make self-starting even after FEN load */
1737 /* to allow automatic setup of fairy variants with wtm */
1738 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1739 gameMode = BeginningOfGame;
1740 setboardSpoiledMachineBlack = 1;
1742 /* [HGM] loadPos: make that every new game uses the setup */
1743 /* from file as long as we do not switch variant */
1744 if(!blackPlaysFirst) {
1745 startedFromPositionFile = TRUE;
1746 CopyBoard(filePosition, boards[0]);
1747 CopyBoard(initialPosition, boards[0]);
1749 } else if(*appData.fen != NULLCHAR) {
1750 if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
1751 startedFromPositionFile = TRUE;
1755 if (initialMode == AnalyzeMode) {
1756 if (appData.noChessProgram) {
1757 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1760 if (appData.icsActive) {
1761 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1765 } else if (initialMode == AnalyzeFile) {
1766 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1767 ShowThinkingEvent();
1769 AnalysisPeriodicEvent(1);
1770 } else if (initialMode == MachinePlaysWhite) {
1771 if (appData.noChessProgram) {
1772 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1776 if (appData.icsActive) {
1777 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1781 MachineWhiteEvent();
1782 } else if (initialMode == MachinePlaysBlack) {
1783 if (appData.noChessProgram) {
1784 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1788 if (appData.icsActive) {
1789 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1793 MachineBlackEvent();
1794 } else if (initialMode == TwoMachinesPlay) {
1795 if (appData.noChessProgram) {
1796 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1800 if (appData.icsActive) {
1801 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1806 } else if (initialMode == EditGame) {
1808 } else if (initialMode == EditPosition) {
1809 EditPositionEvent();
1810 } else if (initialMode == Training) {
1811 if (*appData.loadGameFile == NULLCHAR) {
1812 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1821 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1823 DisplayBook(current+1);
1825 MoveHistorySet( movelist, first, last, current, pvInfoList );
1827 EvalGraphSet( first, last, current, pvInfoList );
1829 MakeEngineOutputTitle();
1833 * Establish will establish a contact to a remote host.port.
1834 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1835 * used to talk to the host.
1836 * Returns 0 if okay, error code if not.
1843 if (*appData.icsCommPort != NULLCHAR) {
1844 /* Talk to the host through a serial comm port */
1845 return OpenCommPort(appData.icsCommPort, &icsPR);
1847 } else if (*appData.gateway != NULLCHAR) {
1848 if (*appData.remoteShell == NULLCHAR) {
1849 /* Use the rcmd protocol to run telnet program on a gateway host */
1850 snprintf(buf, sizeof(buf), "%s %s %s",
1851 appData.telnetProgram, appData.icsHost, appData.icsPort);
1852 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1855 /* Use the rsh program to run telnet program on a gateway host */
1856 if (*appData.remoteUser == NULLCHAR) {
1857 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1858 appData.gateway, appData.telnetProgram,
1859 appData.icsHost, appData.icsPort);
1861 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1862 appData.remoteShell, appData.gateway,
1863 appData.remoteUser, appData.telnetProgram,
1864 appData.icsHost, appData.icsPort);
1866 return StartChildProcess(buf, "", &icsPR);
1869 } else if (appData.useTelnet) {
1870 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1873 /* TCP socket interface differs somewhat between
1874 Unix and NT; handle details in the front end.
1876 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1881 EscapeExpand (char *p, char *q)
1882 { // [HGM] initstring: routine to shape up string arguments
1883 while(*p++ = *q++) if(p[-1] == '\\')
1885 case 'n': p[-1] = '\n'; break;
1886 case 'r': p[-1] = '\r'; break;
1887 case 't': p[-1] = '\t'; break;
1888 case '\\': p[-1] = '\\'; break;
1889 case 0: *p = 0; return;
1890 default: p[-1] = q[-1]; break;
1895 show_bytes (FILE *fp, char *buf, int count)
1898 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1899 fprintf(fp, "\\%03o", *buf & 0xff);
1908 /* Returns an errno value */
1910 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1912 char buf[8192], *p, *q, *buflim;
1913 int left, newcount, outcount;
1915 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1916 *appData.gateway != NULLCHAR) {
1917 if (appData.debugMode) {
1918 fprintf(debugFP, ">ICS: ");
1919 show_bytes(debugFP, message, count);
1920 fprintf(debugFP, "\n");
1922 return OutputToProcess(pr, message, count, outError);
1925 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1932 if (appData.debugMode) {
1933 fprintf(debugFP, ">ICS: ");
1934 show_bytes(debugFP, buf, newcount);
1935 fprintf(debugFP, "\n");
1937 outcount = OutputToProcess(pr, buf, newcount, outError);
1938 if (outcount < newcount) return -1; /* to be sure */
1945 } else if (((unsigned char) *p) == TN_IAC) {
1946 *q++ = (char) TN_IAC;
1953 if (appData.debugMode) {
1954 fprintf(debugFP, ">ICS: ");
1955 show_bytes(debugFP, buf, newcount);
1956 fprintf(debugFP, "\n");
1958 outcount = OutputToProcess(pr, buf, newcount, outError);
1959 if (outcount < newcount) return -1; /* to be sure */
1964 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1966 int outError, outCount;
1967 static int gotEof = 0;
1970 /* Pass data read from player on to ICS */
1973 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1974 if (outCount < count) {
1975 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1977 if(have_sent_ICS_logon == 2) {
1978 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1979 fprintf(ini, "%s", message);
1980 have_sent_ICS_logon = 3;
1982 have_sent_ICS_logon = 1;
1983 } else if(have_sent_ICS_logon == 3) {
1984 fprintf(ini, "%s", message);
1986 have_sent_ICS_logon = 1;
1988 } else if (count < 0) {
1989 RemoveInputSource(isr);
1990 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1991 } else if (gotEof++ > 0) {
1992 RemoveInputSource(isr);
1993 DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
1999 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
2000 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
2001 connectionAlive = FALSE; // only sticks if no response to 'date' command.
2002 SendToICS("date\n");
2003 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
2006 /* added routine for printf style output to ics */
2008 ics_printf (char *format, ...)
2010 char buffer[MSG_SIZ];
2013 va_start(args, format);
2014 vsnprintf(buffer, sizeof(buffer), format, args);
2015 buffer[sizeof(buffer)-1] = '\0';
2023 int count, outCount, outError;
2025 if (icsPR == NoProc) return;
2028 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2029 if (outCount < count) {
2030 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2034 /* This is used for sending logon scripts to the ICS. Sending
2035 without a delay causes problems when using timestamp on ICC
2036 (at least on my machine). */
2038 SendToICSDelayed (char *s, long msdelay)
2040 int count, outCount, outError;
2042 if (icsPR == NoProc) return;
2045 if (appData.debugMode) {
2046 fprintf(debugFP, ">ICS: ");
2047 show_bytes(debugFP, s, count);
2048 fprintf(debugFP, "\n");
2050 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2052 if (outCount < count) {
2053 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2058 /* Remove all highlighting escape sequences in s
2059 Also deletes any suffix starting with '('
2062 StripHighlightAndTitle (char *s)
2064 static char retbuf[MSG_SIZ];
2067 while (*s != NULLCHAR) {
2068 while (*s == '\033') {
2069 while (*s != NULLCHAR && !isalpha(*s)) s++;
2070 if (*s != NULLCHAR) s++;
2072 while (*s != NULLCHAR && *s != '\033') {
2073 if (*s == '(' || *s == '[') {
2084 /* Remove all highlighting escape sequences in s */
2086 StripHighlight (char *s)
2088 static char retbuf[MSG_SIZ];
2091 while (*s != NULLCHAR) {
2092 while (*s == '\033') {
2093 while (*s != NULLCHAR && !isalpha(*s)) s++;
2094 if (*s != NULLCHAR) s++;
2096 while (*s != NULLCHAR && *s != '\033') {
2104 char engineVariant[MSG_SIZ];
2105 char *variantNames[] = VARIANT_NAMES;
2107 VariantName (VariantClass v)
2109 if(v == VariantUnknown || *engineVariant) return engineVariant;
2110 return variantNames[v];
2114 /* Identify a variant from the strings the chess servers use or the
2115 PGN Variant tag names we use. */
2117 StringToVariant (char *e)
2121 VariantClass v = VariantNormal;
2122 int i, found = FALSE;
2123 char buf[MSG_SIZ], c;
2128 /* [HGM] skip over optional board-size prefixes */
2129 if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2130 sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2131 while( *e++ != '_');
2134 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2138 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2139 if (p = StrCaseStr(e, variantNames[i])) {
2140 if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2141 v = (VariantClass) i;
2148 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2149 || StrCaseStr(e, "wild/fr")
2150 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2151 v = VariantFischeRandom;
2152 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2153 (i = 1, p = StrCaseStr(e, "w"))) {
2155 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2162 case 0: /* FICS only, actually */
2164 /* Castling legal even if K starts on d-file */
2165 v = VariantWildCastle;
2170 /* Castling illegal even if K & R happen to start in
2171 normal positions. */
2172 v = VariantNoCastle;
2185 /* Castling legal iff K & R start in normal positions */
2191 /* Special wilds for position setup; unclear what to do here */
2192 v = VariantLoadable;
2195 /* Bizarre ICC game */
2196 v = VariantTwoKings;
2199 v = VariantKriegspiel;
2205 v = VariantFischeRandom;
2208 v = VariantCrazyhouse;
2211 v = VariantBughouse;
2217 /* Not quite the same as FICS suicide! */
2218 v = VariantGiveaway;
2224 v = VariantShatranj;
2227 /* Temporary names for future ICC types. The name *will* change in
2228 the next xboard/WinBoard release after ICC defines it. */
2266 v = VariantCapablanca;
2269 v = VariantKnightmate;
2275 v = VariantCylinder;
2281 v = VariantCapaRandom;
2284 v = VariantBerolina;
2296 /* Found "wild" or "w" in the string but no number;
2297 must assume it's normal chess. */
2301 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2302 if( (len >= MSG_SIZ) && appData.debugMode )
2303 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2305 DisplayError(buf, 0);
2311 if (appData.debugMode) {
2312 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2313 e, wnum, VariantName(v));
2318 static int leftover_start = 0, leftover_len = 0;
2319 char star_match[STAR_MATCH_N][MSG_SIZ];
2321 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2322 advance *index beyond it, and set leftover_start to the new value of
2323 *index; else return FALSE. If pattern contains the character '*', it
2324 matches any sequence of characters not containing '\r', '\n', or the
2325 character following the '*' (if any), and the matched sequence(s) are
2326 copied into star_match.
2329 looking_at ( char *buf, int *index, char *pattern)
2331 char *bufp = &buf[*index], *patternp = pattern;
2333 char *matchp = star_match[0];
2336 if (*patternp == NULLCHAR) {
2337 *index = leftover_start = bufp - buf;
2341 if (*bufp == NULLCHAR) return FALSE;
2342 if (*patternp == '*') {
2343 if (*bufp == *(patternp + 1)) {
2345 matchp = star_match[++star_count];
2349 } else if (*bufp == '\n' || *bufp == '\r') {
2351 if (*patternp == NULLCHAR)
2356 *matchp++ = *bufp++;
2360 if (*patternp != *bufp) return FALSE;
2367 SendToPlayer (char *data, int length)
2369 int error, outCount;
2370 outCount = OutputToProcess(NoProc, data, length, &error);
2371 if (outCount < length) {
2372 DisplayFatalError(_("Error writing to display"), error, 1);
2377 PackHolding (char packed[], char *holding)
2387 switch (runlength) {
2398 sprintf(q, "%d", runlength);
2410 /* Telnet protocol requests from the front end */
2412 TelnetRequest (unsigned char ddww, unsigned char option)
2414 unsigned char msg[3];
2415 int outCount, outError;
2417 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2419 if (appData.debugMode) {
2420 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2436 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2445 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2448 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2453 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2455 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2462 if (!appData.icsActive) return;
2463 TelnetRequest(TN_DO, TN_ECHO);
2469 if (!appData.icsActive) return;
2470 TelnetRequest(TN_DONT, TN_ECHO);
2474 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2476 /* put the holdings sent to us by the server on the board holdings area */
2477 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2481 if(gameInfo.holdingsWidth < 2) return;
2482 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2483 return; // prevent overwriting by pre-board holdings
2485 if( (int)lowestPiece >= BlackPawn ) {
2488 holdingsStartRow = BOARD_HEIGHT-1;
2491 holdingsColumn = BOARD_WIDTH-1;
2492 countsColumn = BOARD_WIDTH-2;
2493 holdingsStartRow = 0;
2497 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2498 board[i][holdingsColumn] = EmptySquare;
2499 board[i][countsColumn] = (ChessSquare) 0;
2501 while( (p=*holdings++) != NULLCHAR ) {
2502 piece = CharToPiece( ToUpper(p) );
2503 if(piece == EmptySquare) continue;
2504 /*j = (int) piece - (int) WhitePawn;*/
2505 j = PieceToNumber(piece);
2506 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2507 if(j < 0) continue; /* should not happen */
2508 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2509 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2510 board[holdingsStartRow+j*direction][countsColumn]++;
2516 VariantSwitch (Board board, VariantClass newVariant)
2518 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2519 static Board oldBoard;
2521 startedFromPositionFile = FALSE;
2522 if(gameInfo.variant == newVariant) return;
2524 /* [HGM] This routine is called each time an assignment is made to
2525 * gameInfo.variant during a game, to make sure the board sizes
2526 * are set to match the new variant. If that means adding or deleting
2527 * holdings, we shift the playing board accordingly
2528 * This kludge is needed because in ICS observe mode, we get boards
2529 * of an ongoing game without knowing the variant, and learn about the
2530 * latter only later. This can be because of the move list we requested,
2531 * in which case the game history is refilled from the beginning anyway,
2532 * but also when receiving holdings of a crazyhouse game. In the latter
2533 * case we want to add those holdings to the already received position.
2537 if (appData.debugMode) {
2538 fprintf(debugFP, "Switch board from %s to %s\n",
2539 VariantName(gameInfo.variant), VariantName(newVariant));
2540 setbuf(debugFP, NULL);
2542 shuffleOpenings = 0; /* [HGM] shuffle */
2543 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2547 newWidth = 9; newHeight = 9;
2548 gameInfo.holdingsSize = 7;
2549 case VariantBughouse:
2550 case VariantCrazyhouse:
2551 newHoldingsWidth = 2; break;
2555 newHoldingsWidth = 2;
2556 gameInfo.holdingsSize = 8;
2559 case VariantCapablanca:
2560 case VariantCapaRandom:
2563 newHoldingsWidth = gameInfo.holdingsSize = 0;
2566 if(newWidth != gameInfo.boardWidth ||
2567 newHeight != gameInfo.boardHeight ||
2568 newHoldingsWidth != gameInfo.holdingsWidth ) {
2570 /* shift position to new playing area, if needed */
2571 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2572 for(i=0; i<BOARD_HEIGHT; i++)
2573 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2574 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2576 for(i=0; i<newHeight; i++) {
2577 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2578 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2580 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2581 for(i=0; i<BOARD_HEIGHT; i++)
2582 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2583 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2586 board[HOLDINGS_SET] = 0;
2587 gameInfo.boardWidth = newWidth;
2588 gameInfo.boardHeight = newHeight;
2589 gameInfo.holdingsWidth = newHoldingsWidth;
2590 gameInfo.variant = newVariant;
2591 InitDrawingSizes(-2, 0);
2592 } else gameInfo.variant = newVariant;
2593 CopyBoard(oldBoard, board); // remember correctly formatted board
2594 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2595 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2598 static int loggedOn = FALSE;
2600 /*-- Game start info cache: --*/
2602 char gs_kind[MSG_SIZ];
2603 static char player1Name[128] = "";
2604 static char player2Name[128] = "";
2605 static char cont_seq[] = "\n\\ ";
2606 static int player1Rating = -1;
2607 static int player2Rating = -1;
2608 /*----------------------------*/
2610 ColorClass curColor = ColorNormal;
2611 int suppressKibitz = 0;
2614 Boolean soughtPending = FALSE;
2615 Boolean seekGraphUp;
2616 #define MAX_SEEK_ADS 200
2618 char *seekAdList[MAX_SEEK_ADS];
2619 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2620 float tcList[MAX_SEEK_ADS];
2621 char colorList[MAX_SEEK_ADS];
2622 int nrOfSeekAds = 0;
2623 int minRating = 1010, maxRating = 2800;
2624 int hMargin = 10, vMargin = 20, h, w;
2625 extern int squareSize, lineGap;
2630 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2631 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2632 if(r < minRating+100 && r >=0 ) r = minRating+100;
2633 if(r > maxRating) r = maxRating;
2634 if(tc < 1.f) tc = 1.f;
2635 if(tc > 95.f) tc = 95.f;
2636 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2637 y = ((double)r - minRating)/(maxRating - minRating)
2638 * (h-vMargin-squareSize/8-1) + vMargin;
2639 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2640 if(strstr(seekAdList[i], " u ")) color = 1;
2641 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2642 !strstr(seekAdList[i], "bullet") &&
2643 !strstr(seekAdList[i], "blitz") &&
2644 !strstr(seekAdList[i], "standard") ) color = 2;
2645 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2646 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2650 PlotSingleSeekAd (int i)
2656 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2658 char buf[MSG_SIZ], *ext = "";
2659 VariantClass v = StringToVariant(type);
2660 if(strstr(type, "wild")) {
2661 ext = type + 4; // append wild number
2662 if(v == VariantFischeRandom) type = "chess960"; else
2663 if(v == VariantLoadable) type = "setup"; else
2664 type = VariantName(v);
2666 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2667 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2668 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2669 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2670 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2671 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2672 seekNrList[nrOfSeekAds] = nr;
2673 zList[nrOfSeekAds] = 0;
2674 seekAdList[nrOfSeekAds++] = StrSave(buf);
2675 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2680 EraseSeekDot (int i)
2682 int x = xList[i], y = yList[i], d=squareSize/4, k;
2683 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2684 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2685 // now replot every dot that overlapped
2686 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2687 int xx = xList[k], yy = yList[k];
2688 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2689 DrawSeekDot(xx, yy, colorList[k]);
2694 RemoveSeekAd (int nr)
2697 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2699 if(seekAdList[i]) free(seekAdList[i]);
2700 seekAdList[i] = seekAdList[--nrOfSeekAds];
2701 seekNrList[i] = seekNrList[nrOfSeekAds];
2702 ratingList[i] = ratingList[nrOfSeekAds];
2703 colorList[i] = colorList[nrOfSeekAds];
2704 tcList[i] = tcList[nrOfSeekAds];
2705 xList[i] = xList[nrOfSeekAds];
2706 yList[i] = yList[nrOfSeekAds];
2707 zList[i] = zList[nrOfSeekAds];
2708 seekAdList[nrOfSeekAds] = NULL;
2714 MatchSoughtLine (char *line)
2716 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2717 int nr, base, inc, u=0; char dummy;
2719 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2720 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2722 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2723 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2724 // match: compact and save the line
2725 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2735 if(!seekGraphUp) return FALSE;
2736 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2737 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap + 2*border;
2739 DrawSeekBackground(0, 0, w, h);
2740 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2741 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2742 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2743 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2745 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2748 snprintf(buf, MSG_SIZ, "%d", i);
2749 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2752 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2753 for(i=1; i<100; i+=(i<10?1:5)) {
2754 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2755 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2756 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2758 snprintf(buf, MSG_SIZ, "%d", i);
2759 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2762 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2767 SeekGraphClick (ClickType click, int x, int y, int moving)
2769 static int lastDown = 0, displayed = 0, lastSecond;
2770 if(y < 0) return FALSE;
2771 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2772 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2773 if(!seekGraphUp) return FALSE;
2774 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2775 DrawPosition(TRUE, NULL);
2778 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2779 if(click == Release || moving) return FALSE;
2781 soughtPending = TRUE;
2782 SendToICS(ics_prefix);
2783 SendToICS("sought\n"); // should this be "sought all"?
2784 } else { // issue challenge based on clicked ad
2785 int dist = 10000; int i, closest = 0, second = 0;
2786 for(i=0; i<nrOfSeekAds; i++) {
2787 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2788 if(d < dist) { dist = d; closest = i; }
2789 second += (d - zList[i] < 120); // count in-range ads
2790 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2794 second = (second > 1);
2795 if(displayed != closest || second != lastSecond) {
2796 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2797 lastSecond = second; displayed = closest;
2799 if(click == Press) {
2800 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2803 } // on press 'hit', only show info
2804 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2805 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2806 SendToICS(ics_prefix);
2808 return TRUE; // let incoming board of started game pop down the graph
2809 } else if(click == Release) { // release 'miss' is ignored
2810 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2811 if(moving == 2) { // right up-click
2812 nrOfSeekAds = 0; // refresh graph
2813 soughtPending = TRUE;
2814 SendToICS(ics_prefix);
2815 SendToICS("sought\n"); // should this be "sought all"?
2818 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2819 // press miss or release hit 'pop down' seek graph
2820 seekGraphUp = FALSE;
2821 DrawPosition(TRUE, NULL);
2827 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2829 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2830 #define STARTED_NONE 0
2831 #define STARTED_MOVES 1
2832 #define STARTED_BOARD 2
2833 #define STARTED_OBSERVE 3
2834 #define STARTED_HOLDINGS 4
2835 #define STARTED_CHATTER 5
2836 #define STARTED_COMMENT 6
2837 #define STARTED_MOVES_NOHIDE 7
2839 static int started = STARTED_NONE;
2840 static char parse[20000];
2841 static int parse_pos = 0;
2842 static char buf[BUF_SIZE + 1];
2843 static int firstTime = TRUE, intfSet = FALSE;
2844 static ColorClass prevColor = ColorNormal;
2845 static int savingComment = FALSE;
2846 static int cmatch = 0; // continuation sequence match
2853 int backup; /* [DM] For zippy color lines */
2855 char talker[MSG_SIZ]; // [HGM] chat
2856 int channel, collective=0;
2858 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2860 if (appData.debugMode) {
2862 fprintf(debugFP, "<ICS: ");
2863 show_bytes(debugFP, data, count);
2864 fprintf(debugFP, "\n");
2868 if (appData.debugMode) { int f = forwardMostMove;
2869 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2870 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2871 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2874 /* If last read ended with a partial line that we couldn't parse,
2875 prepend it to the new read and try again. */
2876 if (leftover_len > 0) {
2877 for (i=0; i<leftover_len; i++)
2878 buf[i] = buf[leftover_start + i];
2881 /* copy new characters into the buffer */
2882 bp = buf + leftover_len;
2883 buf_len=leftover_len;
2884 for (i=0; i<count; i++)
2887 if (data[i] == '\r')
2890 // join lines split by ICS?
2891 if (!appData.noJoin)
2894 Joining just consists of finding matches against the
2895 continuation sequence, and discarding that sequence
2896 if found instead of copying it. So, until a match
2897 fails, there's nothing to do since it might be the
2898 complete sequence, and thus, something we don't want
2901 if (data[i] == cont_seq[cmatch])
2904 if (cmatch == strlen(cont_seq))
2906 cmatch = 0; // complete match. just reset the counter
2909 it's possible for the ICS to not include the space
2910 at the end of the last word, making our [correct]
2911 join operation fuse two separate words. the server
2912 does this when the space occurs at the width setting.
2914 if (!buf_len || buf[buf_len-1] != ' ')
2925 match failed, so we have to copy what matched before
2926 falling through and copying this character. In reality,
2927 this will only ever be just the newline character, but
2928 it doesn't hurt to be precise.
2930 strncpy(bp, cont_seq, cmatch);
2942 buf[buf_len] = NULLCHAR;
2943 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2948 while (i < buf_len) {
2949 /* Deal with part of the TELNET option negotiation
2950 protocol. We refuse to do anything beyond the
2951 defaults, except that we allow the WILL ECHO option,
2952 which ICS uses to turn off password echoing when we are
2953 directly connected to it. We reject this option
2954 if localLineEditing mode is on (always on in xboard)
2955 and we are talking to port 23, which might be a real
2956 telnet server that will try to keep WILL ECHO on permanently.
2958 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2959 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2960 unsigned char option;
2962 switch ((unsigned char) buf[++i]) {
2964 if (appData.debugMode)
2965 fprintf(debugFP, "\n<WILL ");
2966 switch (option = (unsigned char) buf[++i]) {
2968 if (appData.debugMode)
2969 fprintf(debugFP, "ECHO ");
2970 /* Reply only if this is a change, according
2971 to the protocol rules. */
2972 if (remoteEchoOption) break;
2973 if (appData.localLineEditing &&
2974 atoi(appData.icsPort) == TN_PORT) {
2975 TelnetRequest(TN_DONT, TN_ECHO);
2978 TelnetRequest(TN_DO, TN_ECHO);
2979 remoteEchoOption = TRUE;
2983 if (appData.debugMode)
2984 fprintf(debugFP, "%d ", option);
2985 /* Whatever this is, we don't want it. */
2986 TelnetRequest(TN_DONT, option);
2991 if (appData.debugMode)
2992 fprintf(debugFP, "\n<WONT ");
2993 switch (option = (unsigned char) buf[++i]) {
2995 if (appData.debugMode)
2996 fprintf(debugFP, "ECHO ");
2997 /* Reply only if this is a change, according
2998 to the protocol rules. */
2999 if (!remoteEchoOption) break;
3001 TelnetRequest(TN_DONT, TN_ECHO);
3002 remoteEchoOption = FALSE;
3005 if (appData.debugMode)
3006 fprintf(debugFP, "%d ", (unsigned char) option);
3007 /* Whatever this is, it must already be turned
3008 off, because we never agree to turn on
3009 anything non-default, so according to the
3010 protocol rules, we don't reply. */
3015 if (appData.debugMode)
3016 fprintf(debugFP, "\n<DO ");
3017 switch (option = (unsigned char) buf[++i]) {
3019 /* Whatever this is, we refuse to do it. */
3020 if (appData.debugMode)
3021 fprintf(debugFP, "%d ", option);
3022 TelnetRequest(TN_WONT, option);
3027 if (appData.debugMode)
3028 fprintf(debugFP, "\n<DONT ");
3029 switch (option = (unsigned char) buf[++i]) {
3031 if (appData.debugMode)
3032 fprintf(debugFP, "%d ", option);
3033 /* Whatever this is, we are already not doing
3034 it, because we never agree to do anything
3035 non-default, so according to the protocol
3036 rules, we don't reply. */
3041 if (appData.debugMode)
3042 fprintf(debugFP, "\n<IAC ");
3043 /* Doubled IAC; pass it through */
3047 if (appData.debugMode)
3048 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3049 /* Drop all other telnet commands on the floor */
3052 if (oldi > next_out)
3053 SendToPlayer(&buf[next_out], oldi - next_out);
3059 /* OK, this at least will *usually* work */
3060 if (!loggedOn && looking_at(buf, &i, "ics%")) {
3064 if (loggedOn && !intfSet) {
3065 if (ics_type == ICS_ICC) {
3066 snprintf(str, MSG_SIZ,
3067 "/set-quietly interface %s\n/set-quietly style 12\n",
3069 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3070 strcat(str, "/set-2 51 1\n/set seek 1\n");
3071 } else if (ics_type == ICS_CHESSNET) {
3072 snprintf(str, MSG_SIZ, "/style 12\n");
3074 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3075 strcat(str, programVersion);
3076 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3077 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3078 strcat(str, "$iset seekremove 1\n$set seek 1\n");
3080 strcat(str, "$iset nohighlight 1\n");
3082 strcat(str, "$iset lock 1\n$style 12\n");
3085 NotifyFrontendLogin();
3089 if (started == STARTED_COMMENT) {
3090 /* Accumulate characters in comment */
3091 parse[parse_pos++] = buf[i];
3092 if (buf[i] == '\n') {
3093 parse[parse_pos] = NULLCHAR;
3094 if(chattingPartner>=0) {
3096 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3097 OutputChatMessage(chattingPartner, mess);
3098 if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3100 talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3101 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3102 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3103 OutputChatMessage(p, mess);
3107 chattingPartner = -1;
3108 if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3111 if(!suppressKibitz) // [HGM] kibitz
3112 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3113 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3114 int nrDigit = 0, nrAlph = 0, j;
3115 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3116 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3117 parse[parse_pos] = NULLCHAR;
3118 // try to be smart: if it does not look like search info, it should go to
3119 // ICS interaction window after all, not to engine-output window.
3120 for(j=0; j<parse_pos; j++) { // count letters and digits
3121 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3122 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3123 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3125 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3126 int depth=0; float score;
3127 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3128 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3129 pvInfoList[forwardMostMove-1].depth = depth;
3130 pvInfoList[forwardMostMove-1].score = 100*score;
3132 OutputKibitz(suppressKibitz, parse);
3135 if(gameMode == IcsObserving) // restore original ICS messages
3136 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3137 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3139 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3140 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3141 SendToPlayer(tmp, strlen(tmp));
3143 next_out = i+1; // [HGM] suppress printing in ICS window
3145 started = STARTED_NONE;
3147 /* Don't match patterns against characters in comment */
3152 if (started == STARTED_CHATTER) {
3153 if (buf[i] != '\n') {
3154 /* Don't match patterns against characters in chatter */
3158 started = STARTED_NONE;
3159 if(suppressKibitz) next_out = i+1;
3162 /* Kludge to deal with rcmd protocol */
3163 if (firstTime && looking_at(buf, &i, "\001*")) {
3164 DisplayFatalError(&buf[1], 0, 1);
3170 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3173 if (appData.debugMode)
3174 fprintf(debugFP, "ics_type %d\n", ics_type);
3177 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3178 ics_type = ICS_FICS;
3180 if (appData.debugMode)
3181 fprintf(debugFP, "ics_type %d\n", ics_type);
3184 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3185 ics_type = ICS_CHESSNET;
3187 if (appData.debugMode)
3188 fprintf(debugFP, "ics_type %d\n", ics_type);
3193 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3194 looking_at(buf, &i, "Logging you in as \"*\"") ||
3195 looking_at(buf, &i, "will be \"*\""))) {
3196 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3200 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3202 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3203 DisplayIcsInteractionTitle(buf);
3204 have_set_title = TRUE;
3207 /* skip finger notes */
3208 if (started == STARTED_NONE &&
3209 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3210 (buf[i] == '1' && buf[i+1] == '0')) &&
3211 buf[i+2] == ':' && buf[i+3] == ' ') {
3212 started = STARTED_CHATTER;
3218 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3219 if(appData.seekGraph) {
3220 if(soughtPending && MatchSoughtLine(buf+i)) {
3221 i = strstr(buf+i, "rated") - buf;
3222 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3223 next_out = leftover_start = i;
3224 started = STARTED_CHATTER;
3225 suppressKibitz = TRUE;
3228 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3229 && looking_at(buf, &i, "* ads displayed")) {
3230 soughtPending = FALSE;
3235 if(appData.autoRefresh) {
3236 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3237 int s = (ics_type == ICS_ICC); // ICC format differs
3239 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3240 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3241 looking_at(buf, &i, "*% "); // eat prompt
3242 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3243 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3244 next_out = i; // suppress
3247 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3248 char *p = star_match[0];
3250 if(seekGraphUp) RemoveSeekAd(atoi(p));
3251 while(*p && *p++ != ' '); // next
3253 looking_at(buf, &i, "*% "); // eat prompt
3254 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3261 /* skip formula vars */
3262 if (started == STARTED_NONE &&
3263 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3264 started = STARTED_CHATTER;
3269 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3270 if (appData.autoKibitz && started == STARTED_NONE &&
3271 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3272 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3273 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3274 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3275 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3276 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3277 suppressKibitz = TRUE;
3278 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3280 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3281 && (gameMode == IcsPlayingWhite)) ||
3282 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3283 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3284 started = STARTED_CHATTER; // own kibitz we simply discard
3286 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3287 parse_pos = 0; parse[0] = NULLCHAR;
3288 savingComment = TRUE;
3289 suppressKibitz = gameMode != IcsObserving ? 2 :
3290 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3294 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3295 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3296 && atoi(star_match[0])) {
3297 // suppress the acknowledgements of our own autoKibitz
3299 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3300 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3301 SendToPlayer(star_match[0], strlen(star_match[0]));
3302 if(looking_at(buf, &i, "*% ")) // eat prompt
3303 suppressKibitz = FALSE;
3307 } // [HGM] kibitz: end of patch
3309 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3311 // [HGM] chat: intercept tells by users for which we have an open chat window
3313 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3314 looking_at(buf, &i, "* whispers:") ||
3315 looking_at(buf, &i, "* kibitzes:") ||
3316 looking_at(buf, &i, "* shouts:") ||
3317 looking_at(buf, &i, "* c-shouts:") ||
3318 looking_at(buf, &i, "--> * ") ||
3319 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3320 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3321 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3322 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3324 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3325 chattingPartner = -1; collective = 0;
3327 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3328 for(p=0; p<MAX_CHAT; p++) {
3330 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3331 talker[0] = '['; strcat(talker, "] ");
3332 Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3333 chattingPartner = p; break;
3336 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3337 for(p=0; p<MAX_CHAT; p++) {
3339 if(!strcmp("kibitzes", chatPartner[p])) {
3340 talker[0] = '['; strcat(talker, "] ");
3341 chattingPartner = p; break;
3344 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3345 for(p=0; p<MAX_CHAT; p++) {
3347 if(!strcmp("whispers", chatPartner[p])) {
3348 talker[0] = '['; strcat(talker, "] ");
3349 chattingPartner = p; break;
3352 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3353 if(buf[i-8] == '-' && buf[i-3] == 't')
3354 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3356 if(!strcmp("c-shouts", chatPartner[p])) {
3357 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3358 chattingPartner = p; break;
3361 if(chattingPartner < 0)
3362 for(p=0; p<MAX_CHAT; p++) {
3364 if(!strcmp("shouts", chatPartner[p])) {
3365 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3366 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3367 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3368 chattingPartner = p; break;
3372 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3373 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3375 Colorize(ColorTell, FALSE);
3376 if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3378 chattingPartner = p; break;
3380 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3381 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3382 started = STARTED_COMMENT;
3383 parse_pos = 0; parse[0] = NULLCHAR;
3384 savingComment = 3 + chattingPartner; // counts as TRUE
3385 if(collective == 3) i = oldi; else {
3386 suppressKibitz = TRUE;
3387 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3388 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3392 } // [HGM] chat: end of patch
3395 if (appData.zippyTalk || appData.zippyPlay) {
3396 /* [DM] Backup address for color zippy lines */
3398 if (loggedOn == TRUE)
3399 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3400 (appData.zippyPlay && ZippyMatch(buf, &backup)))
3403 } // [DM] 'else { ' deleted
3405 /* Regular tells and says */
3406 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3407 looking_at(buf, &i, "* (your partner) tells you: ") ||
3408 looking_at(buf, &i, "* says: ") ||
3409 /* Don't color "message" or "messages" output */
3410 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3411 looking_at(buf, &i, "*. * at *:*: ") ||
3412 looking_at(buf, &i, "--* (*:*): ") ||
3413 /* Message notifications (same color as tells) */
3414 looking_at(buf, &i, "* has left a message ") ||
3415 looking_at(buf, &i, "* just sent you a message:\n") ||
3416 /* Whispers and kibitzes */
3417 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3418 looking_at(buf, &i, "* kibitzes: ") ||
3420 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3422 if (tkind == 1 && strchr(star_match[0], ':')) {
3423 /* Avoid "tells you:" spoofs in channels */
3426 if (star_match[0][0] == NULLCHAR ||
3427 strchr(star_match[0], ' ') ||
3428 (tkind == 3 && strchr(star_match[1], ' '))) {
3429 /* Reject bogus matches */
3432 if (appData.colorize) {
3433 if (oldi > next_out) {
3434 SendToPlayer(&buf[next_out], oldi - next_out);
3439 Colorize(ColorTell, FALSE);
3440 curColor = ColorTell;
3443 Colorize(ColorKibitz, FALSE);
3444 curColor = ColorKibitz;
3447 p = strrchr(star_match[1], '(');
3454 Colorize(ColorChannel1, FALSE);
3455 curColor = ColorChannel1;
3457 Colorize(ColorChannel, FALSE);
3458 curColor = ColorChannel;
3462 curColor = ColorNormal;
3466 if (started == STARTED_NONE && appData.autoComment &&
3467 (gameMode == IcsObserving ||
3468 gameMode == IcsPlayingWhite ||
3469 gameMode == IcsPlayingBlack)) {
3470 parse_pos = i - oldi;
3471 memcpy(parse, &buf[oldi], parse_pos);
3472 parse[parse_pos] = NULLCHAR;
3473 started = STARTED_COMMENT;
3474 savingComment = TRUE;
3475 } else if(collective != 3) {
3476 started = STARTED_CHATTER;
3477 savingComment = FALSE;
3484 if (looking_at(buf, &i, "* s-shouts: ") ||
3485 looking_at(buf, &i, "* c-shouts: ")) {
3486 if (appData.colorize) {
3487 if (oldi > next_out) {
3488 SendToPlayer(&buf[next_out], oldi - next_out);
3491 Colorize(ColorSShout, FALSE);
3492 curColor = ColorSShout;
3495 started = STARTED_CHATTER;
3499 if (looking_at(buf, &i, "--->")) {
3504 if (looking_at(buf, &i, "* shouts: ") ||
3505 looking_at(buf, &i, "--> ")) {
3506 if (appData.colorize) {
3507 if (oldi > next_out) {
3508 SendToPlayer(&buf[next_out], oldi - next_out);
3511 Colorize(ColorShout, FALSE);
3512 curColor = ColorShout;
3515 started = STARTED_CHATTER;
3519 if (looking_at( buf, &i, "Challenge:")) {
3520 if (appData.colorize) {
3521 if (oldi > next_out) {
3522 SendToPlayer(&buf[next_out], oldi - next_out);
3525 Colorize(ColorChallenge, FALSE);
3526 curColor = ColorChallenge;
3532 if (looking_at(buf, &i, "* offers you") ||
3533 looking_at(buf, &i, "* offers to be") ||
3534 looking_at(buf, &i, "* would like to") ||
3535 looking_at(buf, &i, "* requests to") ||
3536 looking_at(buf, &i, "Your opponent offers") ||
3537 looking_at(buf, &i, "Your opponent requests")) {
3539 if (appData.colorize) {
3540 if (oldi > next_out) {
3541 SendToPlayer(&buf[next_out], oldi - next_out);
3544 Colorize(ColorRequest, FALSE);
3545 curColor = ColorRequest;
3550 if (looking_at(buf, &i, "* (*) seeking")) {
3551 if (appData.colorize) {
3552 if (oldi > next_out) {
3553 SendToPlayer(&buf[next_out], oldi - next_out);
3556 Colorize(ColorSeek, FALSE);
3557 curColor = ColorSeek;
3562 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3564 if (looking_at(buf, &i, "\\ ")) {
3565 if (prevColor != ColorNormal) {
3566 if (oldi > next_out) {
3567 SendToPlayer(&buf[next_out], oldi - next_out);
3570 Colorize(prevColor, TRUE);
3571 curColor = prevColor;
3573 if (savingComment) {
3574 parse_pos = i - oldi;
3575 memcpy(parse, &buf[oldi], parse_pos);
3576 parse[parse_pos] = NULLCHAR;
3577 started = STARTED_COMMENT;
3578 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3579 chattingPartner = savingComment - 3; // kludge to remember the box
3581 started = STARTED_CHATTER;
3586 if (looking_at(buf, &i, "Black Strength :") ||
3587 looking_at(buf, &i, "<<< style 10 board >>>") ||
3588 looking_at(buf, &i, "<10>") ||
3589 looking_at(buf, &i, "#@#")) {
3590 /* Wrong board style */
3592 SendToICS(ics_prefix);
3593 SendToICS("set style 12\n");
3594 SendToICS(ics_prefix);
3595 SendToICS("refresh\n");
3599 if (looking_at(buf, &i, "login:")) {
3600 if (!have_sent_ICS_logon) {
3602 have_sent_ICS_logon = 1;
3603 else // no init script was found
3604 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3605 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3606 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3611 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3612 (looking_at(buf, &i, "\n<12> ") ||
3613 looking_at(buf, &i, "<12> "))) {
3615 if (oldi > next_out) {
3616 SendToPlayer(&buf[next_out], oldi - next_out);
3619 started = STARTED_BOARD;
3624 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3625 looking_at(buf, &i, "<b1> ")) {
3626 if (oldi > next_out) {
3627 SendToPlayer(&buf[next_out], oldi - next_out);
3630 started = STARTED_HOLDINGS;
3635 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3637 /* Header for a move list -- first line */
3639 switch (ics_getting_history) {
3643 case BeginningOfGame:
3644 /* User typed "moves" or "oldmoves" while we
3645 were idle. Pretend we asked for these
3646 moves and soak them up so user can step
3647 through them and/or save them.
3650 gameMode = IcsObserving;
3653 ics_getting_history = H_GOT_UNREQ_HEADER;
3655 case EditGame: /*?*/
3656 case EditPosition: /*?*/
3657 /* Should above feature work in these modes too? */
3658 /* For now it doesn't */
3659 ics_getting_history = H_GOT_UNWANTED_HEADER;
3662 ics_getting_history = H_GOT_UNWANTED_HEADER;
3667 /* Is this the right one? */
3668 if (gameInfo.white && gameInfo.black &&
3669 strcmp(gameInfo.white, star_match[0]) == 0 &&
3670 strcmp(gameInfo.black, star_match[2]) == 0) {
3672 ics_getting_history = H_GOT_REQ_HEADER;
3675 case H_GOT_REQ_HEADER:
3676 case H_GOT_UNREQ_HEADER:
3677 case H_GOT_UNWANTED_HEADER:
3678 case H_GETTING_MOVES:
3679 /* Should not happen */
3680 DisplayError(_("Error gathering move list: two headers"), 0);
3681 ics_getting_history = H_FALSE;
3685 /* Save player ratings into gameInfo if needed */
3686 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3687 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3688 (gameInfo.whiteRating == -1 ||
3689 gameInfo.blackRating == -1)) {
3691 gameInfo.whiteRating = string_to_rating(star_match[1]);
3692 gameInfo.blackRating = string_to_rating(star_match[3]);
3693 if (appData.debugMode)
3694 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3695 gameInfo.whiteRating, gameInfo.blackRating);
3700 if (looking_at(buf, &i,
3701 "* * match, initial time: * minute*, increment: * second")) {
3702 /* Header for a move list -- second line */
3703 /* Initial board will follow if this is a wild game */
3704 if (gameInfo.event != NULL) free(gameInfo.event);
3705 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3706 gameInfo.event = StrSave(str);
3707 /* [HGM] we switched variant. Translate boards if needed. */
3708 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3712 if (looking_at(buf, &i, "Move ")) {
3713 /* Beginning of a move list */
3714 switch (ics_getting_history) {
3716 /* Normally should not happen */
3717 /* Maybe user hit reset while we were parsing */
3720 /* Happens if we are ignoring a move list that is not
3721 * the one we just requested. Common if the user
3722 * tries to observe two games without turning off
3725 case H_GETTING_MOVES:
3726 /* Should not happen */
3727 DisplayError(_("Error gathering move list: nested"), 0);
3728 ics_getting_history = H_FALSE;
3730 case H_GOT_REQ_HEADER:
3731 ics_getting_history = H_GETTING_MOVES;
3732 started = STARTED_MOVES;
3734 if (oldi > next_out) {
3735 SendToPlayer(&buf[next_out], oldi - next_out);
3738 case H_GOT_UNREQ_HEADER:
3739 ics_getting_history = H_GETTING_MOVES;
3740 started = STARTED_MOVES_NOHIDE;
3743 case H_GOT_UNWANTED_HEADER:
3744 ics_getting_history = H_FALSE;
3750 if (looking_at(buf, &i, "% ") ||
3751 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3752 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3753 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3754 soughtPending = FALSE;
3758 if(suppressKibitz) next_out = i;
3759 savingComment = FALSE;
3763 case STARTED_MOVES_NOHIDE:
3764 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3765 parse[parse_pos + i - oldi] = NULLCHAR;
3766 ParseGameHistory(parse);
3768 if (appData.zippyPlay && first.initDone) {
3769 FeedMovesToProgram(&first, forwardMostMove);
3770 if (gameMode == IcsPlayingWhite) {
3771 if (WhiteOnMove(forwardMostMove)) {
3772 if (first.sendTime) {
3773 if (first.useColors) {
3774 SendToProgram("black\n", &first);
3776 SendTimeRemaining(&first, TRUE);
3778 if (first.useColors) {
3779 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3781 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3782 first.maybeThinking = TRUE;
3784 if (first.usePlayother) {
3785 if (first.sendTime) {
3786 SendTimeRemaining(&first, TRUE);
3788 SendToProgram("playother\n", &first);
3794 } else if (gameMode == IcsPlayingBlack) {
3795 if (!WhiteOnMove(forwardMostMove)) {
3796 if (first.sendTime) {
3797 if (first.useColors) {
3798 SendToProgram("white\n", &first);
3800 SendTimeRemaining(&first, FALSE);
3802 if (first.useColors) {
3803 SendToProgram("black\n", &first);
3805 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3806 first.maybeThinking = TRUE;
3808 if (first.usePlayother) {
3809 if (first.sendTime) {
3810 SendTimeRemaining(&first, FALSE);
3812 SendToProgram("playother\n", &first);
3821 if (gameMode == IcsObserving && ics_gamenum == -1) {
3822 /* Moves came from oldmoves or moves command
3823 while we weren't doing anything else.
3825 currentMove = forwardMostMove;
3826 ClearHighlights();/*!!could figure this out*/
3827 flipView = appData.flipView;
3828 DrawPosition(TRUE, boards[currentMove]);
3829 DisplayBothClocks();
3830 snprintf(str, MSG_SIZ, "%s %s %s",
3831 gameInfo.white, _("vs."), gameInfo.black);
3835 /* Moves were history of an active game */
3836 if (gameInfo.resultDetails != NULL) {
3837 free(gameInfo.resultDetails);
3838 gameInfo.resultDetails = NULL;
3841 HistorySet(parseList, backwardMostMove,
3842 forwardMostMove, currentMove-1);
3843 DisplayMove(currentMove - 1);
3844 if (started == STARTED_MOVES) next_out = i;
3845 started = STARTED_NONE;
3846 ics_getting_history = H_FALSE;
3849 case STARTED_OBSERVE:
3850 started = STARTED_NONE;
3851 SendToICS(ics_prefix);
3852 SendToICS("refresh\n");
3858 if(bookHit) { // [HGM] book: simulate book reply
3859 static char bookMove[MSG_SIZ]; // a bit generous?
3861 programStats.nodes = programStats.depth = programStats.time =
3862 programStats.score = programStats.got_only_move = 0;
3863 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3865 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3866 strcat(bookMove, bookHit);
3867 HandleMachineMove(bookMove, &first);
3872 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3873 started == STARTED_HOLDINGS ||
3874 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3875 /* Accumulate characters in move list or board */
3876 parse[parse_pos++] = buf[i];
3879 /* Start of game messages. Mostly we detect start of game
3880 when the first board image arrives. On some versions
3881 of the ICS, though, we need to do a "refresh" after starting
3882 to observe in order to get the current board right away. */
3883 if (looking_at(buf, &i, "Adding game * to observation list")) {
3884 started = STARTED_OBSERVE;
3888 /* Handle auto-observe */
3889 if (appData.autoObserve &&
3890 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3891 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3893 /* Choose the player that was highlighted, if any. */
3894 if (star_match[0][0] == '\033' ||
3895 star_match[1][0] != '\033') {
3896 player = star_match[0];
3898 player = star_match[2];
3900 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3901 ics_prefix, StripHighlightAndTitle(player));
3904 /* Save ratings from notify string */
3905 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3906 player1Rating = string_to_rating(star_match[1]);
3907 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3908 player2Rating = string_to_rating(star_match[3]);
3910 if (appData.debugMode)
3912 "Ratings from 'Game notification:' %s %d, %s %d\n",
3913 player1Name, player1Rating,
3914 player2Name, player2Rating);
3919 /* Deal with automatic examine mode after a game,
3920 and with IcsObserving -> IcsExamining transition */
3921 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3922 looking_at(buf, &i, "has made you an examiner of game *")) {
3924 int gamenum = atoi(star_match[0]);
3925 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3926 gamenum == ics_gamenum) {
3927 /* We were already playing or observing this game;
3928 no need to refetch history */
3929 gameMode = IcsExamining;
3931 pauseExamForwardMostMove = forwardMostMove;
3932 } else if (currentMove < forwardMostMove) {
3933 ForwardInner(forwardMostMove);
3936 /* I don't think this case really can happen */
3937 SendToICS(ics_prefix);
3938 SendToICS("refresh\n");
3943 /* Error messages */
3944 // if (ics_user_moved) {
3945 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3946 if (looking_at(buf, &i, "Illegal move") ||
3947 looking_at(buf, &i, "Not a legal move") ||
3948 looking_at(buf, &i, "Your king is in check") ||
3949 looking_at(buf, &i, "It isn't your turn") ||
3950 looking_at(buf, &i, "It is not your move")) {
3952 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3953 currentMove = forwardMostMove-1;
3954 DisplayMove(currentMove - 1); /* before DMError */
3955 DrawPosition(FALSE, boards[currentMove]);
3956 SwitchClocks(forwardMostMove-1); // [HGM] race
3957 DisplayBothClocks();
3959 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3965 if (looking_at(buf, &i, "still have time") ||
3966 looking_at(buf, &i, "not out of time") ||
3967 looking_at(buf, &i, "either player is out of time") ||
3968 looking_at(buf, &i, "has timeseal; checking")) {
3969 /* We must have called his flag a little too soon */
3970 whiteFlag = blackFlag = FALSE;
3974 if (looking_at(buf, &i, "added * seconds to") ||
3975 looking_at(buf, &i, "seconds were added to")) {
3976 /* Update the clocks */
3977 SendToICS(ics_prefix);
3978 SendToICS("refresh\n");
3982 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3983 ics_clock_paused = TRUE;
3988 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3989 ics_clock_paused = FALSE;
3994 /* Grab player ratings from the Creating: message.
3995 Note we have to check for the special case when
3996 the ICS inserts things like [white] or [black]. */
3997 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3998 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
4000 0 player 1 name (not necessarily white)
4002 2 empty, white, or black (IGNORED)
4003 3 player 2 name (not necessarily black)
4006 The names/ratings are sorted out when the game
4007 actually starts (below).
4009 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4010 player1Rating = string_to_rating(star_match[1]);
4011 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4012 player2Rating = string_to_rating(star_match[4]);
4014 if (appData.debugMode)
4016 "Ratings from 'Creating:' %s %d, %s %d\n",
4017 player1Name, player1Rating,
4018 player2Name, player2Rating);
4023 /* Improved generic start/end-of-game messages */
4024 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4025 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4026 /* If tkind == 0: */
4027 /* star_match[0] is the game number */
4028 /* [1] is the white player's name */
4029 /* [2] is the black player's name */
4030 /* For end-of-game: */
4031 /* [3] is the reason for the game end */
4032 /* [4] is a PGN end game-token, preceded by " " */
4033 /* For start-of-game: */
4034 /* [3] begins with "Creating" or "Continuing" */
4035 /* [4] is " *" or empty (don't care). */
4036 int gamenum = atoi(star_match[0]);
4037 char *whitename, *blackname, *why, *endtoken;
4038 ChessMove endtype = EndOfFile;
4041 whitename = star_match[1];
4042 blackname = star_match[2];
4043 why = star_match[3];
4044 endtoken = star_match[4];
4046 whitename = star_match[1];
4047 blackname = star_match[3];
4048 why = star_match[5];
4049 endtoken = star_match[6];
4052 /* Game start messages */
4053 if (strncmp(why, "Creating ", 9) == 0 ||
4054 strncmp(why, "Continuing ", 11) == 0) {
4055 gs_gamenum = gamenum;
4056 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4057 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4058 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4060 if (appData.zippyPlay) {
4061 ZippyGameStart(whitename, blackname);
4064 partnerBoardValid = FALSE; // [HGM] bughouse
4068 /* Game end messages */
4069 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4070 ics_gamenum != gamenum) {
4073 while (endtoken[0] == ' ') endtoken++;
4074 switch (endtoken[0]) {
4077 endtype = GameUnfinished;
4080 endtype = BlackWins;
4083 if (endtoken[1] == '/')
4084 endtype = GameIsDrawn;
4086 endtype = WhiteWins;
4089 GameEnds(endtype, why, GE_ICS);
4091 if (appData.zippyPlay && first.initDone) {
4092 ZippyGameEnd(endtype, why);
4093 if (first.pr == NoProc) {
4094 /* Start the next process early so that we'll
4095 be ready for the next challenge */
4096 StartChessProgram(&first);
4098 /* Send "new" early, in case this command takes
4099 a long time to finish, so that we'll be ready
4100 for the next challenge. */
4101 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4105 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4109 if (looking_at(buf, &i, "Removing game * from observation") ||
4110 looking_at(buf, &i, "no longer observing game *") ||
4111 looking_at(buf, &i, "Game * (*) has no examiners")) {
4112 if (gameMode == IcsObserving &&
4113 atoi(star_match[0]) == ics_gamenum)
4115 /* icsEngineAnalyze */
4116 if (appData.icsEngineAnalyze) {
4123 ics_user_moved = FALSE;
4128 if (looking_at(buf, &i, "no longer examining game *")) {
4129 if (gameMode == IcsExamining &&
4130 atoi(star_match[0]) == ics_gamenum)
4134 ics_user_moved = FALSE;
4139 /* Advance leftover_start past any newlines we find,
4140 so only partial lines can get reparsed */
4141 if (looking_at(buf, &i, "\n")) {
4142 prevColor = curColor;
4143 if (curColor != ColorNormal) {
4144 if (oldi > next_out) {
4145 SendToPlayer(&buf[next_out], oldi - next_out);
4148 Colorize(ColorNormal, FALSE);
4149 curColor = ColorNormal;
4151 if (started == STARTED_BOARD) {
4152 started = STARTED_NONE;
4153 parse[parse_pos] = NULLCHAR;
4154 ParseBoard12(parse);
4157 /* Send premove here */
4158 if (appData.premove) {
4160 if (currentMove == 0 &&
4161 gameMode == IcsPlayingWhite &&
4162 appData.premoveWhite) {
4163 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4164 if (appData.debugMode)
4165 fprintf(debugFP, "Sending premove:\n");
4167 } else if (currentMove == 1 &&
4168 gameMode == IcsPlayingBlack &&
4169 appData.premoveBlack) {
4170 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4171 if (appData.debugMode)
4172 fprintf(debugFP, "Sending premove:\n");
4174 } else if (gotPremove) {
4175 int oldFMM = forwardMostMove;
4177 ClearPremoveHighlights();
4178 if (appData.debugMode)
4179 fprintf(debugFP, "Sending premove:\n");
4180 UserMoveEvent(premoveFromX, premoveFromY,
4181 premoveToX, premoveToY,
4183 if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4184 if(moveList[oldFMM-1][1] != '@')
4185 SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4186 moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4188 SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4193 /* Usually suppress following prompt */
4194 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4195 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4196 if (looking_at(buf, &i, "*% ")) {
4197 savingComment = FALSE;
4202 } else if (started == STARTED_HOLDINGS) {
4204 char new_piece[MSG_SIZ];
4205 started = STARTED_NONE;
4206 parse[parse_pos] = NULLCHAR;
4207 if (appData.debugMode)
4208 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4209 parse, currentMove);
4210 if (sscanf(parse, " game %d", &gamenum) == 1) {
4211 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4212 new_piece[0] = NULLCHAR;
4213 sscanf(parse, "game %d white [%s black [%s <- %s",
4214 &gamenum, white_holding, black_holding,
4216 white_holding[strlen(white_holding)-1] = NULLCHAR;
4217 black_holding[strlen(black_holding)-1] = NULLCHAR;
4218 if (gameInfo.variant == VariantNormal) {
4219 /* [HGM] We seem to switch variant during a game!
4220 * Presumably no holdings were displayed, so we have
4221 * to move the position two files to the right to
4222 * create room for them!
4224 VariantClass newVariant;
4225 switch(gameInfo.boardWidth) { // base guess on board width
4226 case 9: newVariant = VariantShogi; break;
4227 case 10: newVariant = VariantGreat; break;
4228 default: newVariant = VariantCrazyhouse;
4229 if(strchr(white_holding, 'E') || strchr(black_holding, 'E') ||
4230 strchr(white_holding, 'H') || strchr(black_holding, 'H') )
4231 newVariant = VariantSChess;
4233 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4234 /* Get a move list just to see the header, which
4235 will tell us whether this is really bug or zh */
4236 if (ics_getting_history == H_FALSE) {
4237 ics_getting_history = H_REQUESTED;
4238 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4242 /* [HGM] copy holdings to board holdings area */
4243 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4244 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4245 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4247 if (appData.zippyPlay && first.initDone) {
4248 ZippyHoldings(white_holding, black_holding,
4252 if (tinyLayout || smallLayout) {
4253 char wh[16], bh[16];
4254 PackHolding(wh, white_holding);
4255 PackHolding(bh, black_holding);
4256 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4257 gameInfo.white, gameInfo.black);
4259 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4260 gameInfo.white, white_holding, _("vs."),
4261 gameInfo.black, black_holding);
4263 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4264 DrawPosition(FALSE, boards[currentMove]);
4266 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4267 sscanf(parse, "game %d white [%s black [%s <- %s",
4268 &gamenum, white_holding, black_holding,
4270 white_holding[strlen(white_holding)-1] = NULLCHAR;
4271 black_holding[strlen(black_holding)-1] = NULLCHAR;
4272 /* [HGM] copy holdings to partner-board holdings area */
4273 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4274 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4275 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4276 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4277 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4280 /* Suppress following prompt */
4281 if (looking_at(buf, &i, "*% ")) {
4282 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4283 savingComment = FALSE;
4291 i++; /* skip unparsed character and loop back */
4294 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4295 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4296 // SendToPlayer(&buf[next_out], i - next_out);
4297 started != STARTED_HOLDINGS && leftover_start > next_out) {
4298 SendToPlayer(&buf[next_out], leftover_start - next_out);
4302 leftover_len = buf_len - leftover_start;
4303 /* if buffer ends with something we couldn't parse,
4304 reparse it after appending the next read */
4306 } else if (count == 0) {
4307 RemoveInputSource(isr);
4308 DisplayFatalError(_("Connection closed by ICS"), 0, 6666);
4310 DisplayFatalError(_("Error reading from ICS"), error, 1);
4315 /* Board style 12 looks like this:
4317 <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
4319 * The "<12> " is stripped before it gets to this routine. The two
4320 * trailing 0's (flip state and clock ticking) are later addition, and
4321 * some chess servers may not have them, or may have only the first.
4322 * Additional trailing fields may be added in the future.
4325 #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"
4327 #define RELATION_OBSERVING_PLAYED 0
4328 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4329 #define RELATION_PLAYING_MYMOVE 1
4330 #define RELATION_PLAYING_NOTMYMOVE -1
4331 #define RELATION_EXAMINING 2
4332 #define RELATION_ISOLATED_BOARD -3
4333 #define RELATION_STARTING_POSITION -4 /* FICS only */
4336 ParseBoard12 (char *string)
4340 char *bookHit = NULL; // [HGM] book
4342 GameMode newGameMode;
4343 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4344 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4345 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4346 char to_play, board_chars[200];
4347 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4348 char black[32], white[32];
4350 int prevMove = currentMove;
4353 int fromX, fromY, toX, toY;
4355 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4356 Boolean weird = FALSE, reqFlag = FALSE;
4358 fromX = fromY = toX = toY = -1;
4362 if (appData.debugMode)
4363 fprintf(debugFP, "Parsing board: %s\n", string);
4365 move_str[0] = NULLCHAR;
4366 elapsed_time[0] = NULLCHAR;
4367 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4369 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4370 if(string[i] == ' ') { ranks++; files = 0; }
4372 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4375 for(j = 0; j <i; j++) board_chars[j] = string[j];
4376 board_chars[i] = '\0';
4379 n = sscanf(string, PATTERN, &to_play, &double_push,
4380 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4381 &gamenum, white, black, &relation, &basetime, &increment,
4382 &white_stren, &black_stren, &white_time, &black_time,
4383 &moveNum, str, elapsed_time, move_str, &ics_flip,
4387 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4388 DisplayError(str, 0);
4392 /* Convert the move number to internal form */
4393 moveNum = (moveNum - 1) * 2;
4394 if (to_play == 'B') moveNum++;
4395 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4396 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4402 case RELATION_OBSERVING_PLAYED:
4403 case RELATION_OBSERVING_STATIC:
4404 if (gamenum == -1) {
4405 /* Old ICC buglet */
4406 relation = RELATION_OBSERVING_STATIC;
4408 newGameMode = IcsObserving;
4410 case RELATION_PLAYING_MYMOVE:
4411 case RELATION_PLAYING_NOTMYMOVE:
4413 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4414 IcsPlayingWhite : IcsPlayingBlack;
4415 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4417 case RELATION_EXAMINING:
4418 newGameMode = IcsExamining;
4420 case RELATION_ISOLATED_BOARD:
4422 /* Just display this board. If user was doing something else,
4423 we will forget about it until the next board comes. */
4424 newGameMode = IcsIdle;
4426 case RELATION_STARTING_POSITION:
4427 newGameMode = gameMode;
4431 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4432 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4433 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4434 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4435 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4436 static int lastBgGame = -1;
4438 for (k = 0; k < ranks; k++) {
4439 for (j = 0; j < files; j++)
4440 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4441 if(gameInfo.holdingsWidth > 1) {
4442 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4443 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4446 CopyBoard(partnerBoard, board);
4447 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4448 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4449 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4450 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4451 if(toSqr = strchr(str, '-')) {
4452 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4453 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4454 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4455 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4456 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4457 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4459 DisplayWhiteClock(white_time*fac, to_play == 'W');
4460 DisplayBlackClock(black_time*fac, to_play != 'W');
4461 activePartner = to_play;
4462 if(gamenum != lastBgGame) {
4464 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4467 lastBgGame = gamenum;
4468 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4469 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4470 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4471 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4472 if(!twoBoards) DisplayMessage(partnerStatus, "");
4473 partnerBoardValid = TRUE;
4477 if(appData.dualBoard && appData.bgObserve) {
4478 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4479 SendToICS(ics_prefix), SendToICS("pobserve\n");
4480 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4482 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4487 /* Modify behavior for initial board display on move listing
4490 switch (ics_getting_history) {
4494 case H_GOT_REQ_HEADER:
4495 case H_GOT_UNREQ_HEADER:
4496 /* This is the initial position of the current game */
4497 gamenum = ics_gamenum;
4498 moveNum = 0; /* old ICS bug workaround */
4499 if (to_play == 'B') {
4500 startedFromSetupPosition = TRUE;
4501 blackPlaysFirst = TRUE;
4503 if (forwardMostMove == 0) forwardMostMove = 1;
4504 if (backwardMostMove == 0) backwardMostMove = 1;
4505 if (currentMove == 0) currentMove = 1;
4507 newGameMode = gameMode;
4508 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4510 case H_GOT_UNWANTED_HEADER:
4511 /* This is an initial board that we don't want */
4513 case H_GETTING_MOVES:
4514 /* Should not happen */
4515 DisplayError(_("Error gathering move list: extra board"), 0);
4516 ics_getting_history = H_FALSE;
4520 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4521 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4522 weird && (int)gameInfo.variant < (int)VariantShogi) {
4523 /* [HGM] We seem to have switched variant unexpectedly
4524 * Try to guess new variant from board size
4526 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4527 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4528 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4529 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4530 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4531 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4532 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4533 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4534 /* Get a move list just to see the header, which
4535 will tell us whether this is really bug or zh */
4536 if (ics_getting_history == H_FALSE) {
4537 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4538 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4543 /* Take action if this is the first board of a new game, or of a
4544 different game than is currently being displayed. */
4545 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4546 relation == RELATION_ISOLATED_BOARD) {
4548 /* Forget the old game and get the history (if any) of the new one */
4549 if (gameMode != BeginningOfGame) {
4553 if (appData.autoRaiseBoard) BoardToTop();
4555 if (gamenum == -1) {
4556 newGameMode = IcsIdle;
4557 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4558 appData.getMoveList && !reqFlag) {
4559 /* Need to get game history */
4560 ics_getting_history = H_REQUESTED;
4561 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4565 /* Initially flip the board to have black on the bottom if playing
4566 black or if the ICS flip flag is set, but let the user change
4567 it with the Flip View button. */
4568 flipView = appData.autoFlipView ?
4569 (newGameMode == IcsPlayingBlack) || ics_flip :
4572 /* Done with values from previous mode; copy in new ones */
4573 gameMode = newGameMode;
4575 ics_gamenum = gamenum;
4576 if (gamenum == gs_gamenum) {
4577 int klen = strlen(gs_kind);
4578 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4579 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4580 gameInfo.event = StrSave(str);
4582 gameInfo.event = StrSave("ICS game");
4584 gameInfo.site = StrSave(appData.icsHost);
4585 gameInfo.date = PGNDate();
4586 gameInfo.round = StrSave("-");
4587 gameInfo.white = StrSave(white);
4588 gameInfo.black = StrSave(black);
4589 timeControl = basetime * 60 * 1000;
4591 timeIncrement = increment * 1000;
4592 movesPerSession = 0;
4593 gameInfo.timeControl = TimeControlTagValue();
4594 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4595 if (appData.debugMode) {
4596 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4597 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4598 setbuf(debugFP, NULL);
4601 gameInfo.outOfBook = NULL;
4603 /* Do we have the ratings? */
4604 if (strcmp(player1Name, white) == 0 &&
4605 strcmp(player2Name, black) == 0) {
4606 if (appData.debugMode)
4607 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4608 player1Rating, player2Rating);
4609 gameInfo.whiteRating = player1Rating;
4610 gameInfo.blackRating = player2Rating;
4611 } else if (strcmp(player2Name, white) == 0 &&
4612 strcmp(player1Name, black) == 0) {
4613 if (appData.debugMode)
4614 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4615 player2Rating, player1Rating);
4616 gameInfo.whiteRating = player2Rating;
4617 gameInfo.blackRating = player1Rating;
4619 player1Name[0] = player2Name[0] = NULLCHAR;
4621 /* Silence shouts if requested */
4622 if (appData.quietPlay &&
4623 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4624 SendToICS(ics_prefix);
4625 SendToICS("set shout 0\n");
4629 /* Deal with midgame name changes */
4631 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4632 if (gameInfo.white) free(gameInfo.white);
4633 gameInfo.white = StrSave(white);
4635 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4636 if (gameInfo.black) free(gameInfo.black);
4637 gameInfo.black = StrSave(black);
4641 /* Throw away game result if anything actually changes in examine mode */
4642 if (gameMode == IcsExamining && !newGame) {
4643 gameInfo.result = GameUnfinished;
4644 if (gameInfo.resultDetails != NULL) {
4645 free(gameInfo.resultDetails);
4646 gameInfo.resultDetails = NULL;
4650 /* In pausing && IcsExamining mode, we ignore boards coming
4651 in if they are in a different variation than we are. */
4652 if (pauseExamInvalid) return;
4653 if (pausing && gameMode == IcsExamining) {
4654 if (moveNum <= pauseExamForwardMostMove) {
4655 pauseExamInvalid = TRUE;
4656 forwardMostMove = pauseExamForwardMostMove;
4661 if (appData.debugMode) {
4662 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4664 /* Parse the board */
4665 for (k = 0; k < ranks; k++) {
4666 for (j = 0; j < files; j++)
4667 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4668 if(gameInfo.holdingsWidth > 1) {
4669 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4670 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4673 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4674 board[5][BOARD_RGHT+1] = WhiteAngel;
4675 board[6][BOARD_RGHT+1] = WhiteMarshall;
4676 board[1][0] = BlackMarshall;
4677 board[2][0] = BlackAngel;
4678 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4680 CopyBoard(boards[moveNum], board);
4681 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4683 startedFromSetupPosition =
4684 !CompareBoards(board, initialPosition);
4685 if(startedFromSetupPosition)
4686 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4689 /* [HGM] Set castling rights. Take the outermost Rooks,
4690 to make it also work for FRC opening positions. Note that board12
4691 is really defective for later FRC positions, as it has no way to
4692 indicate which Rook can castle if they are on the same side of King.
4693 For the initial position we grant rights to the outermost Rooks,
4694 and remember thos rights, and we then copy them on positions
4695 later in an FRC game. This means WB might not recognize castlings with
4696 Rooks that have moved back to their original position as illegal,
4697 but in ICS mode that is not its job anyway.
4699 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4700 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4702 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4703 if(board[0][i] == WhiteRook) j = i;
4704 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4705 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4706 if(board[0][i] == WhiteRook) j = i;
4707 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4708 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4709 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4710 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4711 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4712 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4713 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4715 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4716 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4717 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4718 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4719 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4720 if(board[BOARD_HEIGHT-1][k] == bKing)
4721 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4722 if(gameInfo.variant == VariantTwoKings) {
4723 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4724 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4725 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4728 r = boards[moveNum][CASTLING][0] = initialRights[0];
4729 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4730 r = boards[moveNum][CASTLING][1] = initialRights[1];
4731 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4732 r = boards[moveNum][CASTLING][3] = initialRights[3];
4733 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4734 r = boards[moveNum][CASTLING][4] = initialRights[4];
4735 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4736 /* wildcastle kludge: always assume King has rights */
4737 r = boards[moveNum][CASTLING][2] = initialRights[2];
4738 r = boards[moveNum][CASTLING][5] = initialRights[5];
4740 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4741 boards[moveNum][EP_STATUS] = EP_NONE;
4742 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4743 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4744 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4747 if (ics_getting_history == H_GOT_REQ_HEADER ||
4748 ics_getting_history == H_GOT_UNREQ_HEADER) {
4749 /* This was an initial position from a move list, not
4750 the current position */
4754 /* Update currentMove and known move number limits */
4755 newMove = newGame || moveNum > forwardMostMove;
4758 forwardMostMove = backwardMostMove = currentMove = moveNum;
4759 if (gameMode == IcsExamining && moveNum == 0) {
4760 /* Workaround for ICS limitation: we are not told the wild
4761 type when starting to examine a game. But if we ask for
4762 the move list, the move list header will tell us */
4763 ics_getting_history = H_REQUESTED;
4764 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4767 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4768 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4770 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4771 /* [HGM] applied this also to an engine that is silently watching */
4772 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4773 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4774 gameInfo.variant == currentlyInitializedVariant) {
4775 takeback = forwardMostMove - moveNum;
4776 for (i = 0; i < takeback; i++) {
4777 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4778 SendToProgram("undo\n", &first);
4783 forwardMostMove = moveNum;
4784 if (!pausing || currentMove > forwardMostMove)
4785 currentMove = forwardMostMove;
4787 /* New part of history that is not contiguous with old part */
4788 if (pausing && gameMode == IcsExamining) {
4789 pauseExamInvalid = TRUE;
4790 forwardMostMove = pauseExamForwardMostMove;
4793 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4795 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4796 // [HGM] when we will receive the move list we now request, it will be
4797 // fed to the engine from the first move on. So if the engine is not
4798 // in the initial position now, bring it there.
4799 InitChessProgram(&first, 0);
4802 ics_getting_history = H_REQUESTED;
4803 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4806 forwardMostMove = backwardMostMove = currentMove = moveNum;
4809 /* Update the clocks */
4810 if (strchr(elapsed_time, '.')) {
4812 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4813 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4815 /* Time is in seconds */
4816 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4817 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4822 if (appData.zippyPlay && newGame &&
4823 gameMode != IcsObserving && gameMode != IcsIdle &&
4824 gameMode != IcsExamining)
4825 ZippyFirstBoard(moveNum, basetime, increment);
4828 /* Put the move on the move list, first converting
4829 to canonical algebraic form. */
4831 if (appData.debugMode) {
4832 int f = forwardMostMove;
4833 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4834 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4835 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4836 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4837 fprintf(debugFP, "moveNum = %d\n", moveNum);
4838 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4839 setbuf(debugFP, NULL);
4841 if (moveNum <= backwardMostMove) {
4842 /* We don't know what the board looked like before
4844 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4845 strcat(parseList[moveNum - 1], " ");
4846 strcat(parseList[moveNum - 1], elapsed_time);
4847 moveList[moveNum - 1][0] = NULLCHAR;
4848 } else if (strcmp(move_str, "none") == 0) {
4849 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4850 /* Again, we don't know what the board looked like;
4851 this is really the start of the game. */
4852 parseList[moveNum - 1][0] = NULLCHAR;
4853 moveList[moveNum - 1][0] = NULLCHAR;
4854 backwardMostMove = moveNum;
4855 startedFromSetupPosition = TRUE;
4856 fromX = fromY = toX = toY = -1;
4858 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4859 // So we parse the long-algebraic move string in stead of the SAN move
4860 int valid; char buf[MSG_SIZ], *prom;
4862 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4863 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4864 // str looks something like "Q/a1-a2"; kill the slash
4866 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4867 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4868 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4869 strcat(buf, prom); // long move lacks promo specification!
4870 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4871 if(appData.debugMode)
4872 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4873 safeStrCpy(move_str, buf, MSG_SIZ);
4875 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4876 &fromX, &fromY, &toX, &toY, &promoChar)
4877 || ParseOneMove(buf, moveNum - 1, &moveType,
4878 &fromX, &fromY, &toX, &toY, &promoChar);
4879 // end of long SAN patch
4881 (void) CoordsToAlgebraic(boards[moveNum - 1],
4882 PosFlags(moveNum - 1),
4883 fromY, fromX, toY, toX, promoChar,
4884 parseList[moveNum-1]);
4885 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4891 if(!IS_SHOGI(gameInfo.variant))
4892 strcat(parseList[moveNum - 1], "+");
4895 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4896 strcat(parseList[moveNum - 1], "#");
4899 strcat(parseList[moveNum - 1], " ");
4900 strcat(parseList[moveNum - 1], elapsed_time);
4901 /* currentMoveString is set as a side-effect of ParseOneMove */
4902 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4903 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4904 strcat(moveList[moveNum - 1], "\n");
4906 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4907 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4908 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4909 ChessSquare old, new = boards[moveNum][k][j];
4910 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4911 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4912 if(old == new) continue;
4913 if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4914 else if(new == WhiteWazir || new == BlackWazir) {
4915 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4916 boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4917 else boards[moveNum][k][j] = old; // preserve type of Gold
4918 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4919 boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4922 /* Move from ICS was illegal!? Punt. */
4923 if (appData.debugMode) {
4924 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4925 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4927 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4928 strcat(parseList[moveNum - 1], " ");
4929 strcat(parseList[moveNum - 1], elapsed_time);
4930 moveList[moveNum - 1][0] = NULLCHAR;
4931 fromX = fromY = toX = toY = -1;
4934 if (appData.debugMode) {
4935 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4936 setbuf(debugFP, NULL);
4940 /* Send move to chess program (BEFORE animating it). */
4941 if (appData.zippyPlay && !newGame && newMove &&
4942 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4944 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4945 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4946 if (moveList[moveNum - 1][0] == NULLCHAR) {
4947 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4949 DisplayError(str, 0);
4951 if (first.sendTime) {
4952 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4954 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4955 if (firstMove && !bookHit) {
4957 if (first.useColors) {
4958 SendToProgram(gameMode == IcsPlayingWhite ?
4960 "black\ngo\n", &first);
4962 SendToProgram("go\n", &first);
4964 first.maybeThinking = TRUE;
4967 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4968 if (moveList[moveNum - 1][0] == NULLCHAR) {
4969 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4970 DisplayError(str, 0);
4972 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4973 SendMoveToProgram(moveNum - 1, &first);
4980 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4981 /* If move comes from a remote source, animate it. If it
4982 isn't remote, it will have already been animated. */
4983 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4984 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4986 if (!pausing && appData.highlightLastMove) {
4987 SetHighlights(fromX, fromY, toX, toY);
4991 /* Start the clocks */
4992 whiteFlag = blackFlag = FALSE;
4993 appData.clockMode = !(basetime == 0 && increment == 0);
4995 ics_clock_paused = TRUE;
4997 } else if (ticking == 1) {
4998 ics_clock_paused = FALSE;
5000 if (gameMode == IcsIdle ||
5001 relation == RELATION_OBSERVING_STATIC ||
5002 relation == RELATION_EXAMINING ||
5004 DisplayBothClocks();
5008 /* Display opponents and material strengths */
5009 if (gameInfo.variant != VariantBughouse &&
5010 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
5011 if (tinyLayout || smallLayout) {
5012 if(gameInfo.variant == VariantNormal)
5013 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5014 gameInfo.white, white_stren, gameInfo.black, black_stren,
5015 basetime, increment);
5017 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5018 gameInfo.white, white_stren, gameInfo.black, black_stren,
5019 basetime, increment, (int) gameInfo.variant);
5021 if(gameInfo.variant == VariantNormal)
5022 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5023 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5024 basetime, increment);
5026 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5027 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5028 basetime, increment, VariantName(gameInfo.variant));
5031 if (appData.debugMode) {
5032 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5037 /* Display the board */
5038 if (!pausing && !appData.noGUI) {
5040 if (appData.premove)
5042 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5043 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5044 ClearPremoveHighlights();
5046 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5047 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5048 DrawPosition(j, boards[currentMove]);
5050 DisplayMove(moveNum - 1);
5051 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5052 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5053 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
5054 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5058 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5060 if(bookHit) { // [HGM] book: simulate book reply
5061 static char bookMove[MSG_SIZ]; // a bit generous?
5063 programStats.nodes = programStats.depth = programStats.time =
5064 programStats.score = programStats.got_only_move = 0;
5065 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5067 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5068 strcat(bookMove, bookHit);
5069 HandleMachineMove(bookMove, &first);
5078 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5079 ics_getting_history = H_REQUESTED;
5080 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5086 SendToBoth (char *msg)
5087 { // to make it easy to keep two engines in step in dual analysis
5088 SendToProgram(msg, &first);
5089 if(second.analyzing) SendToProgram(msg, &second);
5093 AnalysisPeriodicEvent (int force)
5095 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5096 && !force) || !appData.periodicUpdates)
5099 /* Send . command to Crafty to collect stats */
5102 /* Don't send another until we get a response (this makes
5103 us stop sending to old Crafty's which don't understand
5104 the "." command (sending illegal cmds resets node count & time,
5105 which looks bad)) */
5106 programStats.ok_to_send = 0;
5110 ics_update_width (int new_width)
5112 ics_printf("set width %d\n", new_width);
5116 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5120 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5121 if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5122 sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5123 SendToProgram(buf, cps);
5126 // null move in variant where engine does not understand it (for analysis purposes)
5127 SendBoard(cps, moveNum + 1); // send position after move in stead.
5130 if (cps->useUsermove) {
5131 SendToProgram("usermove ", cps);
5135 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5136 int len = space - parseList[moveNum];
5137 memcpy(buf, parseList[moveNum], len);
5139 buf[len] = NULLCHAR;
5141 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5143 SendToProgram(buf, cps);
5145 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5146 AlphaRank(moveList[moveNum], 4);
5147 SendToProgram(moveList[moveNum], cps);
5148 AlphaRank(moveList[moveNum], 4); // and back
5150 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5151 * the engine. It would be nice to have a better way to identify castle
5153 if(appData.fischerCastling && cps->useOOCastle) {
5154 int fromX = moveList[moveNum][0] - AAA;
5155 int fromY = moveList[moveNum][1] - ONE;
5156 int toX = moveList[moveNum][2] - AAA;
5157 int toY = moveList[moveNum][3] - ONE;
5158 if((boards[moveNum][fromY][fromX] == WhiteKing
5159 && boards[moveNum][toY][toX] == WhiteRook)
5160 || (boards[moveNum][fromY][fromX] == BlackKing
5161 && boards[moveNum][toY][toX] == BlackRook)) {
5162 if(toX > fromX) SendToProgram("O-O\n", cps);
5163 else SendToProgram("O-O-O\n", cps);
5165 else SendToProgram(moveList[moveNum], cps);
5167 if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5168 char *m = moveList[moveNum];
5170 *c = m[7]; if(*c == '\n') *c = NULLCHAR; // promoChar
5171 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
5172 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5175 m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5176 else if(*c && m[8] != '\n') { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5177 *c = m[9]; if(*c == '\n') *c = NULLCHAR;
5178 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
5183 m[2], m[3] - '0', c);
5185 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5188 m[2], m[3] - '0', c);
5189 SendToProgram(buf, cps);
5191 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5192 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5193 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5194 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5195 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5197 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5198 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5199 SendToProgram(buf, cps);
5201 else SendToProgram(moveList[moveNum], cps);
5202 /* End of additions by Tord */
5205 /* [HGM] setting up the opening has brought engine in force mode! */
5206 /* Send 'go' if we are in a mode where machine should play. */
5207 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5208 (gameMode == TwoMachinesPlay ||
5210 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5212 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5213 SendToProgram("go\n", cps);
5214 if (appData.debugMode) {
5215 fprintf(debugFP, "(extra)\n");
5218 setboardSpoiledMachineBlack = 0;
5222 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5224 char user_move[MSG_SIZ];
5227 if(gameInfo.variant == VariantSChess && promoChar) {
5228 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5229 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5230 } else suffix[0] = NULLCHAR;
5234 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5235 (int)moveType, fromX, fromY, toX, toY);
5236 DisplayError(user_move + strlen("say "), 0);
5238 case WhiteKingSideCastle:
5239 case BlackKingSideCastle:
5240 case WhiteQueenSideCastleWild:
5241 case BlackQueenSideCastleWild:
5243 case WhiteHSideCastleFR:
5244 case BlackHSideCastleFR:
5246 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5248 case WhiteQueenSideCastle:
5249 case BlackQueenSideCastle:
5250 case WhiteKingSideCastleWild:
5251 case BlackKingSideCastleWild:
5253 case WhiteASideCastleFR:
5254 case BlackASideCastleFR:
5256 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5258 case WhiteNonPromotion:
5259 case BlackNonPromotion:
5260 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5262 case WhitePromotion:
5263 case BlackPromotion:
5264 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5265 gameInfo.variant == VariantMakruk)
5266 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5267 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5268 PieceToChar(WhiteFerz));
5269 else if(gameInfo.variant == VariantGreat)
5270 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5271 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5272 PieceToChar(WhiteMan));
5274 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5275 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5281 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5282 ToUpper(PieceToChar((ChessSquare) fromX)),
5283 AAA + toX, ONE + toY);
5285 case IllegalMove: /* could be a variant we don't quite understand */
5286 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5288 case WhiteCapturesEnPassant:
5289 case BlackCapturesEnPassant:
5290 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5291 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5294 SendToICS(user_move);
5295 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5296 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5301 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5302 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5303 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5304 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5305 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5308 if(gameMode != IcsExamining) { // is this ever not the case?
5309 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5311 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5312 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5313 } else { // on FICS we must first go to general examine mode
5314 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5316 if(gameInfo.variant != VariantNormal) {
5317 // try figure out wild number, as xboard names are not always valid on ICS
5318 for(i=1; i<=36; i++) {
5319 snprintf(buf, MSG_SIZ, "wild/%d", i);
5320 if(StringToVariant(buf) == gameInfo.variant) break;
5322 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5323 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5324 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5325 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5326 SendToICS(ics_prefix);
5328 if(startedFromSetupPosition || backwardMostMove != 0) {
5329 fen = PositionToFEN(backwardMostMove, NULL, 1);
5330 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5331 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5333 } else { // FICS: everything has to set by separate bsetup commands
5334 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5335 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5337 if(!WhiteOnMove(backwardMostMove)) {
5338 SendToICS("bsetup tomove black\n");
5340 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5341 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5343 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5344 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5346 i = boards[backwardMostMove][EP_STATUS];
5347 if(i >= 0) { // set e.p.
5348 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5354 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5355 SendToICS("bsetup done\n"); // switch to normal examining.
5357 for(i = backwardMostMove; i<last; i++) {
5359 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5360 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5361 int len = strlen(moveList[i]);
5362 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5363 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5367 SendToICS(ics_prefix);
5368 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5371 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5375 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5377 if (rf == DROP_RANK) {
5378 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5379 sprintf(move, "%c@%c%c\n",
5380 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5382 if (promoChar == 'x' || promoChar == NULLCHAR) {
5383 sprintf(move, "%c%c%c%c\n",
5384 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5385 if(killX >= 0 && killY >= 0) {
5386 sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5387 if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5390 sprintf(move, "%c%c%c%c%c\n",
5391 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5392 if(killX >= 0 && killY >= 0) {
5393 sprintf(move+4, ";%c%c%c\n", AAA + killX, ONE + killY, promoChar);
5394 if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5401 ProcessICSInitScript (FILE *f)
5405 while (fgets(buf, MSG_SIZ, f)) {
5406 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5413 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5415 static ClickType lastClickType;
5418 PieceInString (char *s, ChessSquare piece)
5420 char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5421 while((p = strchr(s, ID))) {
5422 if(!suffix || p[1] == suffix) return TRUE;
5429 Partner (ChessSquare *p)
5430 { // change piece into promotion partner if one shogi-promotes to the other
5431 ChessSquare partner = promoPartner[*p];
5432 if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5433 if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5441 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5442 static int toggleFlag;
5443 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5444 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5445 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5446 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5447 if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5448 if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5450 if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5451 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5452 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5453 else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5454 else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5455 if(!step) step = -1;
5456 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5457 !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5458 promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it
5459 promoSweep == pawn ||
5460 appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5461 (promoSweep == WhiteLion || promoSweep == BlackLion)));
5463 int victim = boards[currentMove][toY][toX];
5464 boards[currentMove][toY][toX] = promoSweep;
5465 DrawPosition(FALSE, boards[currentMove]);
5466 boards[currentMove][toY][toX] = victim;
5468 ChangeDragPiece(promoSweep);
5472 PromoScroll (int x, int y)
5476 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5477 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5478 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5479 if(!step) return FALSE;
5480 lastX = x; lastY = y;
5481 if((promoSweep < BlackPawn) == flipView) step = -step;
5482 if(step > 0) selectFlag = 1;
5483 if(!selectFlag) Sweep(step);
5488 NextPiece (int step)
5490 ChessSquare piece = boards[currentMove][toY][toX];
5493 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5494 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5495 if(!step) step = -1;
5496 } while(PieceToChar(pieceSweep) == '.');
5497 boards[currentMove][toY][toX] = pieceSweep;
5498 DrawPosition(FALSE, boards[currentMove]);
5499 boards[currentMove][toY][toX] = piece;
5501 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5503 AlphaRank (char *move, int n)
5505 // char *p = move, c; int x, y;
5507 if (appData.debugMode) {
5508 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5512 move[2]>='0' && move[2]<='9' &&
5513 move[3]>='a' && move[3]<='x' ) {
5515 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5516 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5518 if(move[0]>='0' && move[0]<='9' &&
5519 move[1]>='a' && move[1]<='x' &&
5520 move[2]>='0' && move[2]<='9' &&
5521 move[3]>='a' && move[3]<='x' ) {
5522 /* input move, Shogi -> normal */
5523 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5524 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5525 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5526 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5529 move[3]>='0' && move[3]<='9' &&
5530 move[2]>='a' && move[2]<='x' ) {
5532 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5533 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5536 move[0]>='a' && move[0]<='x' &&
5537 move[3]>='0' && move[3]<='9' &&
5538 move[2]>='a' && move[2]<='x' ) {
5539 /* output move, normal -> Shogi */
5540 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5541 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5542 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5543 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5544 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5546 if (appData.debugMode) {
5547 fprintf(debugFP, " out = '%s'\n", move);
5551 char yy_textstr[8000];
5553 /* Parser for moves from gnuchess, ICS, or user typein box */
5555 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5557 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5559 switch (*moveType) {
5560 case WhitePromotion:
5561 case BlackPromotion:
5562 case WhiteNonPromotion:
5563 case BlackNonPromotion:
5566 case WhiteCapturesEnPassant:
5567 case BlackCapturesEnPassant:
5568 case WhiteKingSideCastle:
5569 case WhiteQueenSideCastle:
5570 case BlackKingSideCastle:
5571 case BlackQueenSideCastle:
5572 case WhiteKingSideCastleWild:
5573 case WhiteQueenSideCastleWild:
5574 case BlackKingSideCastleWild:
5575 case BlackQueenSideCastleWild:
5576 /* Code added by Tord: */
5577 case WhiteHSideCastleFR:
5578 case WhiteASideCastleFR:
5579 case BlackHSideCastleFR:
5580 case BlackASideCastleFR:
5581 /* End of code added by Tord */
5582 case IllegalMove: /* bug or odd chess variant */
5583 if(currentMoveString[1] == '@') { // illegal drop
5584 *fromX = WhiteOnMove(moveNum) ?
5585 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5586 (int) CharToPiece(ToLower(currentMoveString[0]));
5589 *fromX = currentMoveString[0] - AAA;
5590 *fromY = currentMoveString[1] - ONE;
5591 *toX = currentMoveString[2] - AAA;
5592 *toY = currentMoveString[3] - ONE;
5593 *promoChar = currentMoveString[4];
5594 if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5595 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5596 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5597 if (appData.debugMode) {
5598 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5600 *fromX = *fromY = *toX = *toY = 0;
5603 if (appData.testLegality) {
5604 return (*moveType != IllegalMove);
5606 return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5607 // [HGM] lion: if this is a double move we are less critical
5608 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5613 *fromX = *moveType == WhiteDrop ?
5614 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5615 (int) CharToPiece(ToLower(currentMoveString[0]));
5618 *toX = currentMoveString[2] - AAA;
5619 *toY = currentMoveString[3] - ONE;
5620 *promoChar = NULLCHAR;
5624 case ImpossibleMove:
5634 if (appData.debugMode) {
5635 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5638 *fromX = *fromY = *toX = *toY = 0;
5639 *promoChar = NULLCHAR;
5644 Boolean pushed = FALSE;
5645 char *lastParseAttempt;
5648 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5649 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5650 int fromX, fromY, toX, toY; char promoChar;
5655 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5656 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5657 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5660 endPV = forwardMostMove;
5662 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5663 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5664 lastParseAttempt = pv;
5665 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5666 if(!valid && nr == 0 &&
5667 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5668 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5669 // Hande case where played move is different from leading PV move
5670 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5671 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5672 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5673 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5674 endPV += 2; // if position different, keep this
5675 moveList[endPV-1][0] = fromX + AAA;
5676 moveList[endPV-1][1] = fromY + ONE;
5677 moveList[endPV-1][2] = toX + AAA;
5678 moveList[endPV-1][3] = toY + ONE;
5679 parseList[endPV-1][0] = NULLCHAR;
5680 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5683 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5684 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5685 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5686 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5687 valid++; // allow comments in PV
5691 if(endPV+1 > framePtr) break; // no space, truncate
5694 CopyBoard(boards[endPV], boards[endPV-1]);
5695 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5696 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5697 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5698 CoordsToAlgebraic(boards[endPV - 1],
5699 PosFlags(endPV - 1),
5700 fromY, fromX, toY, toX, promoChar,
5701 parseList[endPV - 1]);
5703 if(atEnd == 2) return; // used hidden, for PV conversion
5704 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5705 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5706 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5707 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5708 DrawPosition(TRUE, boards[currentMove]);
5712 MultiPV (ChessProgramState *cps, int kind)
5713 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5715 for(i=0; i<cps->nrOptions; i++) {
5716 char *s = cps->option[i].name;
5717 if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5718 if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5719 && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5724 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5725 static int multi, pv_margin;
5726 static ChessProgramState *activeCps;
5729 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5731 int startPV, lineStart, origIndex = index;
5732 char *p, buf2[MSG_SIZ];
5733 ChessProgramState *cps = (pane ? &second : &first);
5735 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5736 lastX = x; lastY = y;
5737 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5738 lineStart = startPV = index;
5739 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5740 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5742 do{ while(buf[index] && buf[index] != '\n') index++;
5743 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5745 if(lineStart == 0 && gameMode == AnalyzeMode) {
5747 if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5748 if(n == 0) { // click not on "fewer" or "more"
5749 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5750 pv_margin = cps->option[multi].value;
5751 activeCps = cps; // non-null signals margin adjustment
5753 } else if((multi = MultiPV(cps, 1)) >= 0) {
5754 n += cps->option[multi].value; if(n < 1) n = 1;
5755 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5756 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5757 cps->option[multi].value = n;
5761 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5762 ExcludeClick(origIndex - lineStart);
5764 } else if(!strncmp(buf+lineStart, "dep\t", 4)) { // column headers clicked
5765 Collapse(origIndex - lineStart);
5768 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5769 *start = startPV; *end = index-1;
5770 extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5777 static char buf[10*MSG_SIZ];
5778 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5780 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5781 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5782 for(i = forwardMostMove; i<endPV; i++){
5783 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5784 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5787 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5788 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5789 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5795 LoadPV (int x, int y)
5796 { // called on right mouse click to load PV
5797 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5798 lastX = x; lastY = y;
5799 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5807 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5809 if(pv_margin != activeCps->option[multi].value) {
5811 snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5812 SendToProgram(buf, activeCps);
5813 activeCps->option[multi].value = pv_margin;
5818 if(endPV < 0) return;
5819 if(appData.autoCopyPV) CopyFENToClipboard();
5821 if(extendGame && currentMove > forwardMostMove) {
5822 Boolean saveAnimate = appData.animate;
5824 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5825 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5826 } else storedGames--; // abandon shelved tail of original game
5829 forwardMostMove = currentMove;
5830 currentMove = oldFMM;
5831 appData.animate = FALSE;
5832 ToNrEvent(forwardMostMove);
5833 appData.animate = saveAnimate;
5835 currentMove = forwardMostMove;
5836 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5837 ClearPremoveHighlights();
5838 DrawPosition(TRUE, boards[currentMove]);
5842 MovePV (int x, int y, int h)
5843 { // step through PV based on mouse coordinates (called on mouse move)
5844 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5846 if(activeCps) { // adjusting engine's multi-pv margin
5847 if(x > lastX) pv_margin++; else
5848 if(x < lastX) pv_margin -= (pv_margin > 0);
5851 snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5852 DisplayMessage(buf, "");
5857 // we must somehow check if right button is still down (might be released off board!)
5858 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5859 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5860 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5862 lastX = x; lastY = y;
5864 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5865 if(endPV < 0) return;
5866 if(y < margin) step = 1; else
5867 if(y > h - margin) step = -1;
5868 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5869 currentMove += step;
5870 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5871 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5872 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5873 DrawPosition(FALSE, boards[currentMove]);
5877 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5878 // All positions will have equal probability, but the current method will not provide a unique
5879 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5885 int piecesLeft[(int)BlackPawn];
5886 int seed, nrOfShuffles;
5889 GetPositionNumber ()
5890 { // sets global variable seed
5893 seed = appData.defaultFrcPosition;
5894 if(seed < 0) { // randomize based on time for negative FRC position numbers
5895 for(i=0; i<50; i++) seed += random();
5896 seed = random() ^ random() >> 8 ^ random() << 8;
5897 if(seed<0) seed = -seed;
5902 put (Board board, int pieceType, int rank, int n, int shade)
5903 // put the piece on the (n-1)-th empty squares of the given shade
5907 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5908 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5909 board[rank][i] = (ChessSquare) pieceType;
5910 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5912 piecesLeft[pieceType]--;
5921 AddOnePiece (Board board, int pieceType, int rank, int shade)
5922 // calculate where the next piece goes, (any empty square), and put it there
5926 i = seed % squaresLeft[shade];
5927 nrOfShuffles *= squaresLeft[shade];
5928 seed /= squaresLeft[shade];
5929 put(board, pieceType, rank, i, shade);
5933 AddTwoPieces (Board board, int pieceType, int rank)
5934 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5936 int i, n=squaresLeft[ANY], j=n-1, k;
5938 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5939 i = seed % k; // pick one
5942 while(i >= j) i -= j--;
5943 j = n - 1 - j; i += j;
5944 put(board, pieceType, rank, j, ANY);
5945 put(board, pieceType, rank, i, ANY);
5949 SetUpShuffle (Board board, int number)
5953 GetPositionNumber(); nrOfShuffles = 1;
5955 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5956 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5957 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5959 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5961 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5962 p = (int) board[0][i];
5963 if(p < (int) BlackPawn) piecesLeft[p] ++;
5964 board[0][i] = EmptySquare;
5967 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5968 // shuffles restricted to allow normal castling put KRR first
5969 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5970 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5971 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5972 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5973 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5974 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5975 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5976 put(board, WhiteRook, 0, 0, ANY);
5977 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5980 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5981 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5982 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5983 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5984 while(piecesLeft[p] >= 2) {
5985 AddOnePiece(board, p, 0, LITE);
5986 AddOnePiece(board, p, 0, DARK);
5988 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5991 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5992 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5993 // but we leave King and Rooks for last, to possibly obey FRC restriction
5994 if(p == (int)WhiteRook) continue;
5995 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5996 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5999 // now everything is placed, except perhaps King (Unicorn) and Rooks
6001 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
6002 // Last King gets castling rights
6003 while(piecesLeft[(int)WhiteUnicorn]) {
6004 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6005 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
6008 while(piecesLeft[(int)WhiteKing]) {
6009 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6010 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
6015 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
6016 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6019 // Only Rooks can be left; simply place them all
6020 while(piecesLeft[(int)WhiteRook]) {
6021 i = put(board, WhiteRook, 0, 0, ANY);
6022 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6025 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
6027 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
6030 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6031 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6034 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6038 ptclen (const char *s, char *escapes)
6041 if(!*escapes) return strlen(s);
6042 while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6047 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6048 /* [HGM] moved here from winboard.c because of its general usefulness */
6049 /* Basically a safe strcpy that uses the last character as King */
6051 int result = FALSE; int NrPieces;
6052 unsigned char partner[EmptySquare];
6054 if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6055 && NrPieces >= 12 && !(NrPieces&1)) {
6056 int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6058 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6059 for( i=offs=0; i<NrPieces/2-1; i++ ) {
6061 if(map[j] == '/') offs = WhitePBishop - i, j++;
6062 if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6063 table[i+offs] = map[j++];
6064 if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6065 if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6066 if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6068 table[(int) WhiteKing] = map[j++];
6069 for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6071 if(map[j] == '/') offs = WhitePBishop - ii, j++;
6072 i = WHITE_TO_BLACK ii;
6073 if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6074 table[i+offs] = map[j++];
6075 if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6076 if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6077 if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6079 table[(int) BlackKing] = map[j++];
6082 if(*escapes) { // set up promotion pairing
6083 for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6084 // pieceToChar entirely filled, so we can look up specified partners
6085 for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6087 if(c == '^' || c == '-') { // has specified partner
6089 for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6090 if(c == '^') table[i] = '+';
6091 if(p < EmptySquare) {
6092 if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6093 if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6094 promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6096 } else if(c == '*') {
6097 table[i] = partner[i];
6098 promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6110 SetCharTable (unsigned char *table, const char * map)
6112 return SetCharTableEsc(table, map, "");
6116 Prelude (Board board)
6117 { // [HGM] superchess: random selection of exo-pieces
6118 int i, j, k; ChessSquare p;
6119 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6121 GetPositionNumber(); // use FRC position number
6123 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6124 SetCharTable(pieceToChar, appData.pieceToCharTable);
6125 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6126 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6129 j = seed%4; seed /= 4;
6130 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6131 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6132 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
6133 j = seed%3 + (seed%3 >= j); seed /= 3;
6134 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6135 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6136 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
6137 j = seed%3; seed /= 3;
6138 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6139 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6140 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
6141 j = seed%2 + (seed%2 >= j); seed /= 2;
6142 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6143 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6144 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
6145 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
6146 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
6147 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6148 put(board, exoPieces[0], 0, 0, ANY);
6149 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6153 InitPosition (int redraw)
6155 ChessSquare (* pieces)[BOARD_FILES];
6156 int i, j, pawnRow=1, pieceRows=1, overrule,
6157 oldx = gameInfo.boardWidth,
6158 oldy = gameInfo.boardHeight,
6159 oldh = gameInfo.holdingsWidth;
6162 if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6164 /* [AS] Initialize pv info list [HGM] and game status */
6166 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6167 pvInfoList[i].depth = 0;
6168 boards[i][EP_STATUS] = EP_NONE;
6169 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6172 initialRulePlies = 0; /* 50-move counter start */
6174 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6175 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6179 /* [HGM] logic here is completely changed. In stead of full positions */
6180 /* the initialized data only consist of the two backranks. The switch */
6181 /* selects which one we will use, which is than copied to the Board */
6182 /* initialPosition, which for the rest is initialized by Pawns and */
6183 /* empty squares. This initial position is then copied to boards[0], */
6184 /* possibly after shuffling, so that it remains available. */
6186 gameInfo.holdingsWidth = 0; /* default board sizes */
6187 gameInfo.boardWidth = 8;
6188 gameInfo.boardHeight = 8;
6189 gameInfo.holdingsSize = 0;
6190 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6191 for(i=0; i<BOARD_FILES-6; i++)
6192 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6193 initialPosition[EP_STATUS] = EP_NONE;
6194 initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6195 SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6196 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6197 SetCharTable(pieceNickName, appData.pieceNickNames);
6198 else SetCharTable(pieceNickName, "............");
6201 switch (gameInfo.variant) {
6202 case VariantFischeRandom:
6203 shuffleOpenings = TRUE;
6204 appData.fischerCastling = TRUE;
6207 case VariantShatranj:
6208 pieces = ShatranjArray;
6209 nrCastlingRights = 0;
6210 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6213 pieces = makrukArray;
6214 nrCastlingRights = 0;
6215 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6218 pieces = aseanArray;
6219 nrCastlingRights = 0;
6220 SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6222 case VariantTwoKings:
6223 pieces = twoKingsArray;
6226 pieces = GrandArray;
6227 nrCastlingRights = 0;
6228 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6229 gameInfo.boardWidth = 10;
6230 gameInfo.boardHeight = 10;
6231 gameInfo.holdingsSize = 7;
6233 case VariantCapaRandom:
6234 shuffleOpenings = TRUE;
6235 appData.fischerCastling = TRUE;
6236 case VariantCapablanca:
6237 pieces = CapablancaArray;
6238 gameInfo.boardWidth = 10;
6239 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6242 pieces = GothicArray;
6243 gameInfo.boardWidth = 10;
6244 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6247 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6248 gameInfo.holdingsSize = 7;
6249 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6252 pieces = JanusArray;
6253 gameInfo.boardWidth = 10;
6254 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6255 nrCastlingRights = 6;
6256 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6257 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6258 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6259 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6260 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6261 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6264 pieces = FalconArray;
6265 gameInfo.boardWidth = 10;
6266 SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6268 case VariantXiangqi:
6269 pieces = XiangqiArray;
6270 gameInfo.boardWidth = 9;
6271 gameInfo.boardHeight = 10;
6272 nrCastlingRights = 0;
6273 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6276 pieces = ShogiArray;
6277 gameInfo.boardWidth = 9;
6278 gameInfo.boardHeight = 9;
6279 gameInfo.holdingsSize = 7;
6280 nrCastlingRights = 0;
6281 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6284 pieces = ChuArray; pieceRows = 3;
6285 gameInfo.boardWidth = 12;
6286 gameInfo.boardHeight = 12;
6287 nrCastlingRights = 0;
6288 // SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6289 // "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6290 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"
6291 "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);
6293 case VariantCourier:
6294 pieces = CourierArray;
6295 gameInfo.boardWidth = 12;
6296 nrCastlingRights = 0;
6297 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6299 case VariantKnightmate:
6300 pieces = KnightmateArray;
6301 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6303 case VariantSpartan:
6304 pieces = SpartanArray;
6305 SetCharTable(pieceToChar, "PNBRQ.....................K......lw......g...h......ck");
6309 SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6311 case VariantChuChess:
6312 pieces = ChuChessArray;
6313 gameInfo.boardWidth = 10;
6314 gameInfo.boardHeight = 10;
6315 SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6318 pieces = fairyArray;
6319 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6322 pieces = GreatArray;
6323 gameInfo.boardWidth = 10;
6324 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6325 gameInfo.holdingsSize = 8;
6329 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6330 gameInfo.holdingsSize = 8;
6331 startedFromSetupPosition = TRUE;
6333 case VariantCrazyhouse:
6334 case VariantBughouse:
6336 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6337 gameInfo.holdingsSize = 5;
6339 case VariantWildCastle:
6341 /* !!?shuffle with kings guaranteed to be on d or e file */
6342 shuffleOpenings = 1;
6344 case VariantNoCastle:
6346 nrCastlingRights = 0;
6347 /* !!?unconstrained back-rank shuffle */
6348 shuffleOpenings = 1;
6353 if(appData.NrFiles >= 0) {
6354 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6355 gameInfo.boardWidth = appData.NrFiles;
6357 if(appData.NrRanks >= 0) {
6358 gameInfo.boardHeight = appData.NrRanks;
6360 if(appData.holdingsSize >= 0) {
6361 i = appData.holdingsSize;
6362 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6363 gameInfo.holdingsSize = i;
6365 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6366 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6367 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6369 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6370 if(pawnRow < 1) pawnRow = 1;
6371 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6372 gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6373 if(gameInfo.variant == VariantChu) pawnRow = 3;
6375 /* User pieceToChar list overrules defaults */
6376 if(appData.pieceToCharTable != NULL)
6377 SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6379 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6381 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6382 s = (ChessSquare) 0; /* account holding counts in guard band */
6383 for( i=0; i<BOARD_HEIGHT; i++ )
6384 initialPosition[i][j] = s;
6386 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6387 initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6388 initialPosition[pawnRow][j] = WhitePawn;
6389 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6390 if(gameInfo.variant == VariantXiangqi) {
6392 initialPosition[pawnRow][j] =
6393 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6394 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6395 initialPosition[2][j] = WhiteCannon;
6396 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6400 if(gameInfo.variant == VariantChu) {
6401 if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6402 initialPosition[pawnRow+1][j] = WhiteCobra,
6403 initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6404 for(i=1; i<pieceRows; i++) {
6405 initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6406 initialPosition[BOARD_HEIGHT-1-i][j] = pieces[2*i+1][j-gameInfo.holdingsWidth];
6409 if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6410 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6411 initialPosition[0][j] = WhiteRook;
6412 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6415 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] = pieces[1][j-gameInfo.holdingsWidth];
6417 if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6418 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6421 initialPosition[1][j] = WhiteBishop;
6422 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6424 initialPosition[1][j] = WhiteRook;
6425 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6428 if( nrCastlingRights == -1) {
6429 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6430 /* This sets default castling rights from none to normal corners */
6431 /* Variants with other castling rights must set them themselves above */
6432 nrCastlingRights = 6;
6434 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6435 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6436 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6437 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6438 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6439 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6442 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6443 if(gameInfo.variant == VariantGreat) { // promotion commoners
6444 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6445 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6446 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6447 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6449 if( gameInfo.variant == VariantSChess ) {
6450 initialPosition[1][0] = BlackMarshall;
6451 initialPosition[2][0] = BlackAngel;
6452 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6453 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6454 initialPosition[1][1] = initialPosition[2][1] =
6455 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6457 initialPosition[CHECK_COUNT] = (gameInfo.variant == Variant3Check ? 0x303 : 0);
6458 if (appData.debugMode) {
6459 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6461 if(shuffleOpenings) {
6462 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6463 startedFromSetupPosition = TRUE;
6465 if(startedFromPositionFile) {
6466 /* [HGM] loadPos: use PositionFile for every new game */
6467 CopyBoard(initialPosition, filePosition);
6468 for(i=0; i<nrCastlingRights; i++)
6469 initialRights[i] = filePosition[CASTLING][i];
6470 startedFromSetupPosition = TRUE;
6472 if(*appData.men) LoadPieceDesc(appData.men);
6474 CopyBoard(boards[0], initialPosition);
6476 if(oldx != gameInfo.boardWidth ||
6477 oldy != gameInfo.boardHeight ||
6478 oldv != gameInfo.variant ||
6479 oldh != gameInfo.holdingsWidth
6481 InitDrawingSizes(-2 ,0);
6483 oldv = gameInfo.variant;
6485 DrawPosition(TRUE, boards[currentMove]);
6489 SendBoard (ChessProgramState *cps, int moveNum)
6491 char message[MSG_SIZ];
6493 if (cps->useSetboard) {
6494 char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6495 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6496 SendToProgram(message, cps);
6501 int i, j, left=0, right=BOARD_WIDTH;
6502 /* Kludge to set black to move, avoiding the troublesome and now
6503 * deprecated "black" command.
6505 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6506 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn && !pieceDesc[WhitePawn] ? "a2a3\n" : "black\nforce\n", cps);
6508 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6510 SendToProgram("edit\n", cps);
6511 SendToProgram("#\n", cps);
6512 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6513 bp = &boards[moveNum][i][left];
6514 for (j = left; j < right; j++, bp++) {
6515 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6516 if ((int) *bp < (int) BlackPawn) {
6517 if(j == BOARD_RGHT+1)
6518 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6519 else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6520 if(message[0] == '+' || message[0] == '~') {
6521 snprintf(message, MSG_SIZ,"%c%c%d+\n",
6522 PieceToChar((ChessSquare)(DEMOTED(*bp))),
6523 AAA + j, ONE + i - '0');
6525 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6526 message[1] = BOARD_RGHT - 1 - j + '1';
6527 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6529 SendToProgram(message, cps);
6534 SendToProgram("c\n", cps);
6535 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6536 bp = &boards[moveNum][i][left];
6537 for (j = left; j < right; j++, bp++) {
6538 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6539 if (((int) *bp != (int) EmptySquare)
6540 && ((int) *bp >= (int) BlackPawn)) {
6541 if(j == BOARD_LEFT-2)
6542 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6543 else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6544 AAA + j, ONE + i - '0');
6545 if(message[0] == '+' || message[0] == '~') {
6546 snprintf(message, MSG_SIZ,"%c%c%d+\n",
6547 PieceToChar((ChessSquare)(DEMOTED(*bp))),
6548 AAA + j, ONE + i - '0');
6550 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6551 message[1] = BOARD_RGHT - 1 - j + '1';
6552 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6554 SendToProgram(message, cps);
6559 SendToProgram(".\n", cps);
6561 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6564 char exclusionHeader[MSG_SIZ];
6565 int exCnt, excludePtr;
6566 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6567 static Exclusion excluTab[200];
6568 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6574 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6575 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6581 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6582 excludePtr = 24; exCnt = 0;
6587 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6588 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6589 char buf[2*MOVE_LEN], *p;
6590 Exclusion *e = excluTab;
6592 for(i=0; i<exCnt; i++)
6593 if(e[i].ff == fromX && e[i].fr == fromY &&
6594 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6595 if(i == exCnt) { // was not in exclude list; add it
6596 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6597 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6598 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6601 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6602 excludePtr++; e[i].mark = excludePtr++;
6603 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6606 exclusionHeader[e[i].mark] = state;
6610 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6611 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6615 if((signed char)promoChar == -1) { // kludge to indicate best move
6616 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6617 return 1; // if unparsable, abort
6619 // update exclusion map (resolving toggle by consulting existing state)
6620 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6622 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6623 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6624 excludeMap[k] |= 1<<j;
6625 else excludeMap[k] &= ~(1<<j);
6627 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6629 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6630 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6632 return (state == '+');
6636 ExcludeClick (int index)
6639 Exclusion *e = excluTab;
6640 if(index < 25) { // none, best or tail clicked
6641 if(index < 13) { // none: include all
6642 WriteMap(0); // clear map
6643 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6644 SendToBoth("include all\n"); // and inform engine
6645 } else if(index > 18) { // tail
6646 if(exclusionHeader[19] == '-') { // tail was excluded
6647 SendToBoth("include all\n");
6648 WriteMap(0); // clear map completely
6649 // now re-exclude selected moves
6650 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6651 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6652 } else { // tail was included or in mixed state
6653 SendToBoth("exclude all\n");
6654 WriteMap(0xFF); // fill map completely
6655 // now re-include selected moves
6656 j = 0; // count them
6657 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6658 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6659 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6662 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6665 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6666 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6667 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6674 DefaultPromoChoice (int white)
6677 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6678 gameInfo.variant == VariantMakruk)
6679 result = WhiteFerz; // no choice
6680 else if(gameInfo.variant == VariantASEAN)
6681 result = WhiteRook; // no choice
6682 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6683 result= WhiteKing; // in Suicide Q is the last thing we want
6684 else if(gameInfo.variant == VariantSpartan)
6685 result = white ? WhiteQueen : WhiteAngel;
6686 else result = WhiteQueen;
6687 if(!white) result = WHITE_TO_BLACK result;
6691 static int autoQueen; // [HGM] oneclick
6694 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6696 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6697 /* [HGM] add Shogi promotions */
6698 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6699 ChessSquare piece, partner;
6703 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6704 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6706 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6707 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6710 if(legal[toY][toX] == 4) return FALSE;
6712 piece = boards[currentMove][fromY][fromX];
6713 if(gameInfo.variant == VariantChu) {
6714 promotionZoneSize = BOARD_HEIGHT/3;
6715 if(legal[toY][toX] == 6) return FALSE; // no promotion if highlights deny it
6716 highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6717 } else if(gameInfo.variant == VariantShogi) {
6718 promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6719 highestPromotingPiece = (int)WhiteAlfil;
6720 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6721 promotionZoneSize = 3;
6724 // Treat Lance as Pawn when it is not representing Amazon or Lance
6725 if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6726 if(piece == WhiteLance) piece = WhitePawn; else
6727 if(piece == BlackLance) piece = BlackPawn;
6730 // next weed out all moves that do not touch the promotion zone at all
6731 if((int)piece >= BlackPawn) {
6732 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6734 if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6735 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6737 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6738 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6739 if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6743 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6745 // weed out mandatory Shogi promotions
6746 if(gameInfo.variant == VariantShogi) {
6747 if(piece >= BlackPawn) {
6748 if(toY == 0 && piece == BlackPawn ||
6749 toY == 0 && piece == BlackQueen ||
6750 toY <= 1 && piece == BlackKnight) {
6755 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6756 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6757 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6764 // weed out obviously illegal Pawn moves
6765 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6766 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6767 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6768 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6769 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6770 // note we are not allowed to test for valid (non-)capture, due to premove
6773 // we either have a choice what to promote to, or (in Shogi) whether to promote
6774 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6775 gameInfo.variant == VariantMakruk) {
6776 ChessSquare p=BlackFerz; // no choice
6777 while(p < EmptySquare) { //but make sure we use piece that exists
6778 *promoChoice = PieceToChar(p++);
6779 if(*promoChoice != '.') break;
6781 if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6783 // no sense asking what we must promote to if it is going to explode...
6784 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6785 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6788 // give caller the default choice even if we will not make it
6789 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6790 partner = piece; // pieces can promote if the pieceToCharTable says so
6791 if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6792 else if(Partner(&partner)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6793 if( sweepSelect && gameInfo.variant != VariantGreat
6794 && gameInfo.variant != VariantGrand
6795 && gameInfo.variant != VariantSuper) return FALSE;
6796 if(autoQueen) return FALSE; // predetermined
6798 // suppress promotion popup on illegal moves that are not premoves
6799 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6800 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6801 if(appData.testLegality && !premove) {
6802 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6803 fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6804 if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6805 if(moveType != WhitePromotion && moveType != BlackPromotion)
6813 InPalace (int row, int column)
6814 { /* [HGM] for Xiangqi */
6815 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6816 column < (BOARD_WIDTH + 4)/2 &&
6817 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6822 PieceForSquare (int x, int y)
6824 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6827 return boards[currentMove][y][x];
6831 OKToStartUserMove (int x, int y)
6833 ChessSquare from_piece;
6836 if (matchMode) return FALSE;
6837 if (gameMode == EditPosition) return TRUE;
6839 if (x >= 0 && y >= 0)
6840 from_piece = boards[currentMove][y][x];
6842 from_piece = EmptySquare;
6844 if (from_piece == EmptySquare) return FALSE;
6846 white_piece = (int)from_piece >= (int)WhitePawn &&
6847 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6851 case TwoMachinesPlay:
6859 case MachinePlaysWhite:
6860 case IcsPlayingBlack:
6861 if (appData.zippyPlay) return FALSE;
6863 DisplayMoveError(_("You are playing Black"));
6868 case MachinePlaysBlack:
6869 case IcsPlayingWhite:
6870 if (appData.zippyPlay) return FALSE;
6872 DisplayMoveError(_("You are playing White"));
6877 case PlayFromGameFile:
6878 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6881 if (!white_piece && WhiteOnMove(currentMove)) {
6882 DisplayMoveError(_("It is White's turn"));
6885 if (white_piece && !WhiteOnMove(currentMove)) {
6886 DisplayMoveError(_("It is Black's turn"));
6889 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6890 /* Editing correspondence game history */
6891 /* Could disallow this or prompt for confirmation */
6896 case BeginningOfGame:
6897 if (appData.icsActive) return FALSE;
6898 if (!appData.noChessProgram) {
6900 DisplayMoveError(_("You are playing White"));
6907 if (!white_piece && WhiteOnMove(currentMove)) {
6908 DisplayMoveError(_("It is White's turn"));
6911 if (white_piece && !WhiteOnMove(currentMove)) {
6912 DisplayMoveError(_("It is Black's turn"));
6921 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6922 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6923 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6924 && gameMode != AnalyzeFile && gameMode != Training) {
6925 DisplayMoveError(_("Displayed position is not current"));
6932 OnlyMove (int *x, int *y, Boolean captures)
6934 DisambiguateClosure cl;
6935 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6937 case MachinePlaysBlack:
6938 case IcsPlayingWhite:
6939 case BeginningOfGame:
6940 if(!WhiteOnMove(currentMove)) return FALSE;
6942 case MachinePlaysWhite:
6943 case IcsPlayingBlack:
6944 if(WhiteOnMove(currentMove)) return FALSE;
6951 cl.pieceIn = EmptySquare;
6956 cl.promoCharIn = NULLCHAR;
6957 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6958 if( cl.kind == NormalMove ||
6959 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6960 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6961 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6968 if(cl.kind != ImpossibleMove) return FALSE;
6969 cl.pieceIn = EmptySquare;
6974 cl.promoCharIn = NULLCHAR;
6975 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6976 if( cl.kind == NormalMove ||
6977 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6978 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6979 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6984 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6990 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6991 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6992 int lastLoadGameUseList = FALSE;
6993 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6994 ChessMove lastLoadGameStart = EndOfFile;
6996 Boolean addToBookFlag;
6997 static Board rightsBoard, nullBoard;
7000 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
7004 int ff=fromX, rf=fromY, ft=toX, rt=toY;
7006 /* Check if the user is playing in turn. This is complicated because we
7007 let the user "pick up" a piece before it is his turn. So the piece he
7008 tried to pick up may have been captured by the time he puts it down!
7009 Therefore we use the color the user is supposed to be playing in this
7010 test, not the color of the piece that is currently on the starting
7011 square---except in EditGame mode, where the user is playing both
7012 sides; fortunately there the capture race can't happen. (It can
7013 now happen in IcsExamining mode, but that's just too bad. The user
7014 will get a somewhat confusing message in that case.)
7019 case TwoMachinesPlay:
7023 /* We switched into a game mode where moves are not accepted,
7024 perhaps while the mouse button was down. */
7027 case MachinePlaysWhite:
7028 /* User is moving for Black */
7029 if (WhiteOnMove(currentMove)) {
7030 DisplayMoveError(_("It is White's turn"));
7035 case MachinePlaysBlack:
7036 /* User is moving for White */
7037 if (!WhiteOnMove(currentMove)) {
7038 DisplayMoveError(_("It is Black's turn"));
7043 case PlayFromGameFile:
7044 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7047 case BeginningOfGame:
7050 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7051 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7052 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7053 /* User is moving for Black */
7054 if (WhiteOnMove(currentMove)) {
7055 DisplayMoveError(_("It is White's turn"));
7059 /* User is moving for White */
7060 if (!WhiteOnMove(currentMove)) {
7061 DisplayMoveError(_("It is Black's turn"));
7067 case IcsPlayingBlack:
7068 /* User is moving for Black */
7069 if (WhiteOnMove(currentMove)) {
7070 if (!appData.premove) {
7071 DisplayMoveError(_("It is White's turn"));
7072 } else if (toX >= 0 && toY >= 0) {
7075 premoveFromX = fromX;
7076 premoveFromY = fromY;
7077 premovePromoChar = promoChar;
7079 if (appData.debugMode)
7080 fprintf(debugFP, "Got premove: fromX %d,"
7081 "fromY %d, toX %d, toY %d\n",
7082 fromX, fromY, toX, toY);
7084 DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7089 case IcsPlayingWhite:
7090 /* User is moving for White */
7091 if (!WhiteOnMove(currentMove)) {
7092 if (!appData.premove) {
7093 DisplayMoveError(_("It is Black's turn"));
7094 } else if (toX >= 0 && toY >= 0) {
7097 premoveFromX = fromX;
7098 premoveFromY = fromY;
7099 premovePromoChar = promoChar;
7101 if (appData.debugMode)
7102 fprintf(debugFP, "Got premove: fromX %d,"
7103 "fromY %d, toX %d, toY %d\n",
7104 fromX, fromY, toX, toY);
7106 DrawPosition(TRUE, boards[currentMove]);
7115 /* EditPosition, empty square, or different color piece;
7116 click-click move is possible */
7117 if (toX == -2 || toY == -2) {
7118 boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7119 DrawPosition(FALSE, boards[currentMove]);
7121 } else if (toX >= 0 && toY >= 0) {
7122 if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7123 ChessSquare p = boards[0][rf][ff];
7124 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7125 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7126 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
7127 int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7128 DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7131 } else rightsBoard[toY][toX] = 0; // revoke rights on moving
7132 boards[0][toY][toX] = boards[0][fromY][fromX];
7133 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7134 if(boards[0][fromY][0] != EmptySquare) {
7135 if(boards[0][fromY][1]) boards[0][fromY][1]--;
7136 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
7139 if(fromX == BOARD_RGHT+1) {
7140 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7141 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7142 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7145 boards[0][fromY][fromX] = gatingPiece;
7147 DrawPosition(FALSE, boards[currentMove]);
7153 if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7154 pup = boards[currentMove][toY][toX];
7156 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7157 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7158 if( pup != EmptySquare ) return;
7159 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7160 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7161 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7162 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7163 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7164 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7165 while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7169 /* [HGM] always test for legality, to get promotion info */
7170 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7171 fromY, fromX, toY, toX, promoChar);
7173 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7175 if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7177 /* [HGM] but possibly ignore an IllegalMove result */
7178 if (appData.testLegality) {
7179 if (moveType == IllegalMove || moveType == ImpossibleMove) {
7180 DisplayMoveError(_("Illegal move"));
7185 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7186 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7187 ClearPremoveHighlights(); // was included
7188 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
7189 DrawPosition(FALSE, NULL);
7193 if(addToBookFlag) { // adding moves to book
7194 char buf[MSG_SIZ], move[MSG_SIZ];
7195 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7196 if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7197 killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7198 snprintf(buf, MSG_SIZ, " 0.0%% 1 %s\n", move);
7200 addToBookFlag = FALSE;
7205 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7208 /* Common tail of UserMoveEvent and DropMenuEvent */
7210 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7214 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7215 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7216 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7217 if(WhiteOnMove(currentMove)) {
7218 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7220 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7224 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7225 move type in caller when we know the move is a legal promotion */
7226 if(moveType == NormalMove && promoChar)
7227 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7229 /* [HGM] <popupFix> The following if has been moved here from
7230 UserMoveEvent(). Because it seemed to belong here (why not allow
7231 piece drops in training games?), and because it can only be
7232 performed after it is known to what we promote. */
7233 if (gameMode == Training) {
7234 /* compare the move played on the board to the next move in the
7235 * game. If they match, display the move and the opponent's response.
7236 * If they don't match, display an error message.
7240 CopyBoard(testBoard, boards[currentMove]);
7241 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7243 if (CompareBoards(testBoard, boards[currentMove+1])) {
7244 ForwardInner(currentMove+1);
7246 /* Autoplay the opponent's response.
7247 * if appData.animate was TRUE when Training mode was entered,
7248 * the response will be animated.
7250 saveAnimate = appData.animate;
7251 appData.animate = animateTraining;
7252 ForwardInner(currentMove+1);
7253 appData.animate = saveAnimate;
7255 /* check for the end of the game */
7256 if (currentMove >= forwardMostMove) {
7257 gameMode = PlayFromGameFile;
7259 SetTrainingModeOff();
7260 DisplayInformation(_("End of game"));
7263 DisplayError(_("Incorrect move"), 0);
7268 /* Ok, now we know that the move is good, so we can kill
7269 the previous line in Analysis Mode */
7270 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7271 && currentMove < forwardMostMove) {
7272 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7273 else forwardMostMove = currentMove;
7278 /* If we need the chess program but it's dead, restart it */
7279 ResurrectChessProgram();
7281 /* A user move restarts a paused game*/
7285 thinkOutput[0] = NULLCHAR;
7287 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7289 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7290 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7294 if (gameMode == BeginningOfGame) {
7295 if (appData.noChessProgram) {
7296 gameMode = EditGame;
7300 gameMode = MachinePlaysBlack;
7303 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7305 if (first.sendName) {
7306 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7307 SendToProgram(buf, &first);
7314 /* Relay move to ICS or chess engine */
7315 if (appData.icsActive) {
7316 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7317 gameMode == IcsExamining) {
7318 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7319 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7321 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7323 // also send plain move, in case ICS does not understand atomic claims
7324 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7328 if (first.sendTime && (gameMode == BeginningOfGame ||
7329 gameMode == MachinePlaysWhite ||
7330 gameMode == MachinePlaysBlack)) {
7331 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7333 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7334 // [HGM] book: if program might be playing, let it use book
7335 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7336 first.maybeThinking = TRUE;
7337 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7338 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7339 SendBoard(&first, currentMove+1);
7340 if(second.analyzing) {
7341 if(!second.useSetboard) SendToProgram("undo\n", &second);
7342 SendBoard(&second, currentMove+1);
7345 SendMoveToProgram(forwardMostMove-1, &first);
7346 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7348 if (currentMove == cmailOldMove + 1) {
7349 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7353 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7357 if(appData.testLegality)
7358 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7364 if (WhiteOnMove(currentMove)) {
7365 GameEnds(BlackWins, "Black mates", GE_PLAYER);
7367 GameEnds(WhiteWins, "White mates", GE_PLAYER);
7371 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7376 case MachinePlaysBlack:
7377 case MachinePlaysWhite:
7378 /* disable certain menu options while machine is thinking */
7379 SetMachineThinkingEnables();
7386 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7387 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7389 if(bookHit) { // [HGM] book: simulate book reply
7390 static char bookMove[MSG_SIZ]; // a bit generous?
7392 programStats.nodes = programStats.depth = programStats.time =
7393 programStats.score = programStats.got_only_move = 0;
7394 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7396 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7397 strcat(bookMove, bookHit);
7398 HandleMachineMove(bookMove, &first);
7404 MarkByFEN(char *fen)
7407 if(!appData.markers || !appData.highlightDragging) return;
7408 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = marker[r][f] = 0;
7409 r=BOARD_HEIGHT-1-deadRanks; f=BOARD_LEFT;
7412 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7413 if(*fen == 'B') legal[r][f] = 4; else // request auto-promotion to victim
7414 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 6; else
7415 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7416 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7417 if(*fen == 'T') marker[r][f++] = 0; else
7418 if(*fen == 'Y') marker[r][f++] = 1; else
7419 if(*fen == 'G') marker[r][f++] = 3; else
7420 if(*fen == 'B') marker[r][f++] = 4; else
7421 if(*fen == 'C') marker[r][f++] = 5; else
7422 if(*fen == 'M') marker[r][f++] = 6; else
7423 if(*fen == 'W') marker[r][f++] = 7; else
7424 if(*fen == 'D') marker[r][f++] = 8; else
7425 if(*fen == 'R') marker[r][f++] = 2; else {
7426 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7429 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7433 DrawPosition(TRUE, NULL);
7436 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7439 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7441 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7442 Markers *m = (Markers *) closure;
7443 if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7444 kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7445 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7446 || kind == WhiteCapturesEnPassant
7447 || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7448 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7451 static int hoverSavedValid;
7454 MarkTargetSquares (int clear)
7457 if(clear) { // no reason to ever suppress clearing
7458 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7459 hoverSavedValid = 0;
7460 if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7463 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7464 !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7465 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7466 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7467 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7469 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;
7472 DrawPosition(FALSE, NULL);
7476 Explode (Board board, int fromX, int fromY, int toX, int toY)
7478 if(gameInfo.variant == VariantAtomic &&
7479 (board[toY][toX] != EmptySquare || // capture?
7480 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7481 board[fromY][fromX] == BlackPawn )
7483 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7489 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7492 CanPromote (ChessSquare piece, int y)
7494 int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7495 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7496 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7497 if(IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantXiangqi ||
7498 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7499 (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7500 gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7501 return (piece == BlackPawn && y <= zone ||
7502 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7503 piece == BlackLance && y <= zone ||
7504 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7508 HoverEvent (int xPix, int yPix, int x, int y)
7510 static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7512 if(!first.highlight) return;
7513 if(fromX != oldFromX || fromY != oldFromY) oldX = oldY = -1; // kludge to fake entry on from-click
7514 if(x == oldX && y == oldY) return; // only do something if we enter new square
7515 oldFromX = fromX; oldFromY = fromY;
7516 if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7517 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7518 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7519 hoverSavedValid = 1;
7520 } else if(oldX != x || oldY != y) {
7521 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7522 if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7523 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7524 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7525 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7527 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7528 SendToProgram(buf, &first);
7531 // SetHighlights(fromX, fromY, x, y);
7535 void ReportClick(char *action, int x, int y)
7537 char buf[MSG_SIZ]; // Inform engine of what user does
7539 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7540 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7541 legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7542 if(!first.highlight || gameMode == EditPosition) return;
7543 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7544 SendToProgram(buf, &first);
7547 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7548 Boolean deferChoice;
7551 LeftClick (ClickType clickType, int xPix, int yPix)
7554 static Boolean saveAnimate;
7555 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7556 char promoChoice = NULLCHAR;
7558 static TimeMark lastClickTime, prevClickTime;
7560 if(flashing) return;
7562 if(!deferChoice) { // when called for a retry, skip everything to the point where we left off
7563 x = EventToSquare(xPix, BOARD_WIDTH);
7564 y = EventToSquare(yPix, BOARD_HEIGHT);
7565 if (!flipView && y >= 0) {
7566 y = BOARD_HEIGHT - 1 - y;
7568 if (flipView && x >= 0) {
7569 x = BOARD_WIDTH - 1 - x;
7572 if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7574 RightClick(clickType, xPix, yPix, &dummy, &dummy);
7579 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7581 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7583 if (clickType == Press) ErrorPopDown();
7584 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7586 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7587 defaultPromoChoice = promoSweep;
7588 promoSweep = EmptySquare; // terminate sweep
7589 promoDefaultAltered = TRUE;
7590 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7593 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7594 if(clickType == Release) return; // ignore upclick of click-click destination
7595 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7596 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7597 if(gameInfo.holdingsWidth &&
7598 (WhiteOnMove(currentMove)
7599 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7600 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7601 // click in right holdings, for determining promotion piece
7602 ChessSquare p = boards[currentMove][y][x];
7603 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7604 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7605 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7606 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7611 DrawPosition(FALSE, boards[currentMove]);
7615 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7616 if(clickType == Press
7617 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7618 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7619 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7622 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7623 // could be static click on premove from-square: abort premove
7625 ClearPremoveHighlights();
7628 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7629 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7631 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7632 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7633 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7634 defaultPromoChoice = DefaultPromoChoice(side);
7637 autoQueen = appData.alwaysPromoteToQueen;
7641 gatingPiece = EmptySquare;
7642 if (clickType != Press) {
7643 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7644 DragPieceEnd(xPix, yPix); dragging = 0;
7645 DrawPosition(FALSE, NULL);
7649 doubleClick = FALSE;
7650 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7651 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7653 fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7654 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7655 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7656 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7658 if (OKToStartUserMove(fromX, fromY)) {
7660 ReportClick("lift", x, y);
7661 MarkTargetSquares(0);
7662 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7663 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7664 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7665 promoSweep = defaultPromoChoice;
7666 selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7667 Sweep(0); // Pawn that is going to promote: preview promotion piece
7668 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7670 if (appData.highlightDragging) {
7671 SetHighlights(fromX, fromY, -1, -1);
7675 } else fromX = fromY = -1;
7681 if (clickType == Press && gameMode != EditPosition) {
7686 // ignore off-board to clicks
7687 if(y < 0 || x < 0) return;
7689 /* Check if clicking again on the same color piece */
7690 fromP = boards[currentMove][fromY][fromX];
7691 toP = boards[currentMove][y][x];
7692 frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7693 if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7694 marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7695 ((WhitePawn <= fromP && fromP <= WhiteKing &&
7696 WhitePawn <= toP && toP <= WhiteKing &&
7697 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7698 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7699 (BlackPawn <= fromP && fromP <= BlackKing &&
7700 BlackPawn <= toP && toP <= BlackKing &&
7701 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7702 !(fromP == BlackKing && toP == BlackRook && frc)))) {
7703 /* Clicked again on same color piece -- changed his mind */
7704 second = (x == fromX && y == fromY);
7705 killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7706 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7707 second = FALSE; // first double-click rather than scond click
7708 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7710 promoDefaultAltered = FALSE;
7711 if(!second) MarkTargetSquares(1);
7712 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7713 if (appData.highlightDragging) {
7714 SetHighlights(x, y, -1, -1);
7718 if (OKToStartUserMove(x, y)) {
7719 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7720 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7721 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7722 gatingPiece = boards[currentMove][fromY][fromX];
7723 else gatingPiece = doubleClick ? fromP : EmptySquare;
7725 fromY = y; dragging = 1;
7726 if(!second) ReportClick("lift", x, y);
7727 MarkTargetSquares(0);
7728 DragPieceBegin(xPix, yPix, FALSE);
7729 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7730 promoSweep = defaultPromoChoice;
7731 selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7732 Sweep(0); // Pawn that is going to promote: preview promotion piece
7736 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7739 // ignore clicks on holdings
7740 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7743 if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7744 gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7745 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7749 if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7750 DragPieceEnd(xPix, yPix); dragging = 0;
7752 // a deferred attempt to click-click move an empty square on top of a piece
7753 boards[currentMove][y][x] = EmptySquare;
7755 DrawPosition(FALSE, boards[currentMove]);
7756 fromX = fromY = -1; clearFlag = 0;
7759 if (appData.animateDragging) {
7760 /* Undo animation damage if any */
7761 DrawPosition(FALSE, NULL);
7764 /* Second up/down in same square; just abort move */
7767 gatingPiece = EmptySquare;
7770 ClearPremoveHighlights();
7771 MarkTargetSquares(-1);
7772 DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7774 /* First upclick in same square; start click-click mode */
7775 SetHighlights(x, y, -1, -1);
7782 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7783 fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7784 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7785 DisplayMessage(_("only marked squares are legal"),"");
7786 DrawPosition(TRUE, NULL);
7787 return; // ignore to-click
7790 /* we now have a different from- and (possibly off-board) to-square */
7791 /* Completed move */
7792 if(!sweepSelecting) {
7797 piece = boards[currentMove][fromY][fromX];
7799 saveAnimate = appData.animate;
7800 if (clickType == Press) {
7801 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7802 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7803 // must be Edit Position mode with empty-square selected
7804 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7805 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7808 if(dragging == 2) { // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7811 if(x == killX && y == killY) { // second click on this square, which was selected as first-leg target
7812 killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1; // this informs us no second leg is coming, so treat as to-click without intermediate
7814 if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7815 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7816 if(appData.sweepSelect) {
7817 promoSweep = defaultPromoChoice;
7818 if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7819 selectFlag = 0; lastX = xPix; lastY = yPix;
7820 ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7821 saveFlash = appData.flashCount; appData.flashCount = 0;
7822 Sweep(0); // Pawn that is going to promote: preview promotion piece
7824 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7825 MarkTargetSquares(1);
7827 return; // promo popup appears on up-click
7829 /* Finish clickclick move */
7830 if (appData.animate || appData.highlightLastMove) {
7831 SetHighlights(fromX, fromY, toX, toY);
7835 MarkTargetSquares(1);
7836 } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7837 sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7838 *promoRestrict = 0; appData.flashCount = saveFlash;
7839 if (appData.animate || appData.highlightLastMove) {
7840 SetHighlights(fromX, fromY, toX, toY);
7844 MarkTargetSquares(1);
7847 // [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
7848 /* Finish drag move */
7849 if (appData.highlightLastMove) {
7850 SetHighlights(fromX, fromY, toX, toY);
7855 if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7856 defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7857 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7858 if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7859 dragging *= 2; // flag button-less dragging if we are dragging
7860 MarkTargetSquares(1);
7861 if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7863 kill2X = killX; kill2Y = killY;
7864 killX = x; killY = y; // remember this square as intermediate
7865 ReportClick("put", x, y); // and inform engine
7866 ReportClick("lift", x, y);
7867 MarkTargetSquares(0);
7871 DragPieceEnd(xPix, yPix); dragging = 0;
7872 /* Don't animate move and drag both */
7873 appData.animate = FALSE;
7874 MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7877 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7878 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7879 ChessSquare piece = boards[currentMove][fromY][fromX];
7880 if(gameMode == EditPosition && piece != EmptySquare &&
7881 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7884 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7885 n = PieceToNumber(piece - (int)BlackPawn);
7886 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7887 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7888 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7890 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7891 n = PieceToNumber(piece);
7892 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7893 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7894 boards[currentMove][n][BOARD_WIDTH-2]++;
7896 boards[currentMove][fromY][fromX] = EmptySquare;
7900 MarkTargetSquares(1);
7901 DrawPosition(TRUE, boards[currentMove]);
7905 // off-board moves should not be highlighted
7906 if(x < 0 || y < 0) {
7908 DrawPosition(FALSE, NULL);
7909 } else ReportClick("put", x, y);
7911 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7914 if(legal[toY][toX] == 2) { // highlight-induced promotion
7915 if(piece == defaultPromoChoice) promoChoice = NULLCHAR; // deferral
7916 else promoChoice = ToLower(PieceToChar(defaultPromoChoice));
7917 } else if(legal[toY][toX] == 4) { // blue target square: engine must supply promotion choice
7918 if(!*promoRestrict) { // but has not done that yet
7919 deferChoice = TRUE; // set up retry for when it does
7920 return; // and wait for that
7922 promoChoice = ToLower(*promoRestrict); // force engine's choice
7923 deferChoice = FALSE;
7926 if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7927 SetHighlights(fromX, fromY, toX, toY);
7928 MarkTargetSquares(1);
7929 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7930 // [HGM] super: promotion to captured piece selected from holdings
7931 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7932 promotionChoice = TRUE;
7933 // kludge follows to temporarily execute move on display, without promoting yet
7934 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7935 boards[currentMove][toY][toX] = p;
7936 DrawPosition(FALSE, boards[currentMove]);
7937 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7938 boards[currentMove][toY][toX] = q;
7939 DisplayMessage("Click in holdings to choose piece", "");
7942 DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
7943 PromotionPopUp(promoChoice);
7945 int oldMove = currentMove;
7946 flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
7947 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7948 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7949 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
7950 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7951 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7952 DrawPosition(TRUE, boards[currentMove]);
7953 else DrawPosition(FALSE, NULL);
7957 appData.animate = saveAnimate;
7958 if (appData.animate || appData.animateDragging) {
7959 /* Undo animation damage if needed */
7960 // DrawPosition(FALSE, NULL);
7965 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7966 { // front-end-free part taken out of PieceMenuPopup
7967 int whichMenu; int xSqr, ySqr;
7969 if(seekGraphUp) { // [HGM] seekgraph
7970 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7971 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7975 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7976 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7977 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7978 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7979 if(action == Press) {
7980 originalFlip = flipView;
7981 flipView = !flipView; // temporarily flip board to see game from partners perspective
7982 DrawPosition(TRUE, partnerBoard);
7983 DisplayMessage(partnerStatus, "");
7985 } else if(action == Release) {
7986 flipView = originalFlip;
7987 DrawPosition(TRUE, boards[currentMove]);
7993 xSqr = EventToSquare(x, BOARD_WIDTH);
7994 ySqr = EventToSquare(y, BOARD_HEIGHT);
7995 if (action == Release) {
7996 if(pieceSweep != EmptySquare) {
7997 EditPositionMenuEvent(pieceSweep, toX, toY);
7998 pieceSweep = EmptySquare;
7999 } else UnLoadPV(); // [HGM] pv
8001 if (action != Press) return -2; // return code to be ignored
8004 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
8006 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
8007 if (xSqr < 0 || ySqr < 0) return -1;
8008 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
8009 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
8010 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
8011 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
8015 if(!appData.icsEngineAnalyze) return -1;
8016 case IcsPlayingWhite:
8017 case IcsPlayingBlack:
8018 if(!appData.zippyPlay) goto noZip;
8021 case MachinePlaysWhite:
8022 case MachinePlaysBlack:
8023 case TwoMachinesPlay: // [HGM] pv: use for showing PV
8024 if (!appData.dropMenu) {
8026 return 2; // flag front-end to grab mouse events
8028 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8029 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8032 if (xSqr < 0 || ySqr < 0) return -1;
8033 if (!appData.dropMenu || appData.testLegality &&
8034 gameInfo.variant != VariantBughouse &&
8035 gameInfo.variant != VariantCrazyhouse) return -1;
8036 whichMenu = 1; // drop menu
8042 if (((*fromX = xSqr) < 0) ||
8043 ((*fromY = ySqr) < 0)) {
8044 *fromX = *fromY = -1;
8048 *fromX = BOARD_WIDTH - 1 - *fromX;
8050 *fromY = BOARD_HEIGHT - 1 - *fromY;
8056 Wheel (int dir, int x, int y)
8058 if(gameMode == EditPosition) {
8059 int xSqr = EventToSquare(x, BOARD_WIDTH);
8060 int ySqr = EventToSquare(y, BOARD_HEIGHT);
8061 if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8062 if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8064 boards[currentMove][ySqr][xSqr] += dir;
8065 if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8066 if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8067 } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8068 DrawPosition(FALSE, boards[currentMove]);
8069 } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8073 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8075 // char * hint = lastHint;
8076 FrontEndProgramStats stats;
8078 stats.which = cps == &first ? 0 : 1;
8079 stats.depth = cpstats->depth;
8080 stats.nodes = cpstats->nodes;
8081 stats.score = cpstats->score;
8082 stats.time = cpstats->time;
8083 stats.pv = cpstats->movelist;
8084 stats.hint = lastHint;
8085 stats.an_move_index = 0;
8086 stats.an_move_count = 0;
8088 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8089 stats.hint = cpstats->move_name;
8090 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8091 stats.an_move_count = cpstats->nr_moves;
8094 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
8096 if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8097 && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8099 SetProgramStats( &stats );
8103 ClearEngineOutputPane (int which)
8105 static FrontEndProgramStats dummyStats;
8106 dummyStats.which = which;
8107 dummyStats.pv = "#";
8108 SetProgramStats( &dummyStats );
8111 #define MAXPLAYERS 500
8114 TourneyStandings (int display)
8116 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8117 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8118 char result, *p, *names[MAXPLAYERS];
8120 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8121 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8122 names[0] = p = strdup(appData.participants);
8123 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8125 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8127 while(result = appData.results[nr]) {
8128 color = Pairing(nr, nPlayers, &w, &b, &dummy);
8129 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8130 wScore = bScore = 0;
8132 case '+': wScore = 2; break;
8133 case '-': bScore = 2; break;
8134 case '=': wScore = bScore = 1; break;
8136 case '*': return strdup("busy"); // tourney not finished
8144 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8145 for(w=0; w<nPlayers; w++) {
8147 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8148 ranking[w] = b; points[w] = bScore; score[b] = -2;
8150 p = malloc(nPlayers*34+1);
8151 for(w=0; w<nPlayers && w<display; w++)
8152 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8158 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8159 { // count all piece types
8161 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8162 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8163 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8166 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8167 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8168 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8169 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8170 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
8171 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8176 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8178 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8179 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8181 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8182 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8183 if(myPawns == 2 && nMine == 3) // KPP
8184 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8185 if(myPawns == 1 && nMine == 2) // KP
8186 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8187 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8188 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8189 if(myPawns) return FALSE;
8190 if(pCnt[WhiteRook+side])
8191 return pCnt[BlackRook-side] ||
8192 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8193 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8194 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8195 if(pCnt[WhiteCannon+side]) {
8196 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8197 return majorDefense || pCnt[BlackAlfil-side] >= 2;
8199 if(pCnt[WhiteKnight+side])
8200 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8205 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8207 VariantClass v = gameInfo.variant;
8209 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8210 if(v == VariantShatranj) return TRUE; // always winnable through baring
8211 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8212 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8214 if(v == VariantXiangqi) {
8215 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8217 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8218 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8219 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8220 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8221 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8222 if(stale) // we have at least one last-rank P plus perhaps C
8223 return majors // KPKX
8224 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8226 return pCnt[WhiteFerz+side] // KCAK
8227 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8228 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8229 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8231 } else if(v == VariantKnightmate) {
8232 if(nMine == 1) return FALSE;
8233 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8234 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8235 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8237 if(nMine == 1) return FALSE; // bare King
8238 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
8239 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8240 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8241 // by now we have King + 1 piece (or multiple Bishops on the same color)
8242 if(pCnt[WhiteKnight+side])
8243 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8244 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8245 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8247 return (pCnt[BlackKnight-side]); // KBKN, KFKN
8248 if(pCnt[WhiteAlfil+side])
8249 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8250 if(pCnt[WhiteWazir+side])
8251 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8258 CompareWithRights (Board b1, Board b2)
8261 if(!CompareBoards(b1, b2)) return FALSE;
8262 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8263 /* compare castling rights */
8264 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8265 rights++; /* King lost rights, while rook still had them */
8266 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8267 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8268 rights++; /* but at least one rook lost them */
8270 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8272 if( b1[CASTLING][5] != NoRights ) {
8273 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8280 Adjudicate (ChessProgramState *cps)
8281 { // [HGM] some adjudications useful with buggy engines
8282 // [HGM] adjudicate: made into separate routine, which now can be called after every move
8283 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8284 // Actually ending the game is now based on the additional internal condition canAdjudicate.
8285 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8286 int k, drop, count = 0; static int bare = 1;
8287 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8288 Boolean canAdjudicate = !appData.icsActive;
8290 // most tests only when we understand the game, i.e. legality-checking on
8291 if( appData.testLegality )
8292 { /* [HGM] Some more adjudications for obstinate engines */
8293 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8294 static int moveCount = 6;
8296 char *reason = NULL;
8298 /* Count what is on board. */
8299 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8301 /* Some material-based adjudications that have to be made before stalemate test */
8302 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8303 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8304 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8305 if(canAdjudicate && appData.checkMates) {
8307 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8308 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8309 "Xboard adjudication: King destroyed", GE_XBOARD );
8314 /* Bare King in Shatranj (loses) or Losers (wins) */
8315 if( nrW == 1 || nrB == 1) {
8316 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8317 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
8318 if(canAdjudicate && appData.checkMates) {
8320 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8321 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8322 "Xboard adjudication: Bare king", GE_XBOARD );
8326 if( gameInfo.variant == VariantShatranj && --bare < 0)
8328 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8329 if(canAdjudicate && appData.checkMates) {
8330 /* but only adjudicate if adjudication enabled */
8332 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8333 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8334 "Xboard adjudication: Bare king", GE_XBOARD );
8341 // don't wait for engine to announce game end if we can judge ourselves
8342 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8344 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8345 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
8346 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8347 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8350 reason = "Xboard adjudication: 3rd check";
8351 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8362 reason = "Xboard adjudication: Stalemate";
8363 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8364 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
8365 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8366 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
8367 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8368 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8369 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8370 EP_CHECKMATE : EP_WINS);
8371 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8372 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8376 reason = "Xboard adjudication: Checkmate";
8377 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8378 if(gameInfo.variant == VariantShogi) {
8379 if(forwardMostMove > backwardMostMove
8380 && moveList[forwardMostMove-1][1] == '@'
8381 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8382 reason = "XBoard adjudication: pawn-drop mate";
8383 boards[forwardMostMove][EP_STATUS] = EP_WINS;
8389 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8391 result = GameIsDrawn; break;
8393 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8395 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8399 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8401 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8402 GameEnds( result, reason, GE_XBOARD );
8406 /* Next absolutely insufficient mating material. */
8407 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8408 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8409 { /* includes KBK, KNK, KK of KBKB with like Bishops */
8411 /* always flag draws, for judging claims */
8412 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8414 if(canAdjudicate && appData.materialDraws) {
8415 /* but only adjudicate them if adjudication enabled */
8416 if(engineOpponent) {
8417 SendToProgram("force\n", engineOpponent); // suppress reply
8418 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8420 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8425 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8426 if(gameInfo.variant == VariantXiangqi ?
8427 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8429 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8430 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
8431 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
8432 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8434 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8435 { /* if the first 3 moves do not show a tactical win, declare draw */
8436 if(engineOpponent) {
8437 SendToProgram("force\n", engineOpponent); // suppress reply
8438 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8440 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8443 } else moveCount = 6;
8446 // Repetition draws and 50-move rule can be applied independently of legality testing
8448 /* Check for rep-draws */
8450 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8451 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8452 for(k = forwardMostMove-2;
8453 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8454 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8455 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8458 if(CompareBoards(boards[k], boards[forwardMostMove])) {
8459 /* compare castling rights */
8460 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8461 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8462 rights++; /* King lost rights, while rook still had them */
8463 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8464 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8465 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8466 rights++; /* but at least one rook lost them */
8468 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8469 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8471 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8472 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8473 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8476 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8477 && appData.drawRepeats > 1) {
8478 /* adjudicate after user-specified nr of repeats */
8479 int result = GameIsDrawn;
8480 char *details = "XBoard adjudication: repetition draw";
8481 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8482 // [HGM] xiangqi: check for forbidden perpetuals
8483 int m, ourPerpetual = 1, hisPerpetual = 1;
8484 for(m=forwardMostMove; m>k; m-=2) {
8485 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8486 ourPerpetual = 0; // the current mover did not always check
8487 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8488 hisPerpetual = 0; // the opponent did not always check
8490 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8491 ourPerpetual, hisPerpetual);
8492 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8493 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8494 details = "Xboard adjudication: perpetual checking";
8496 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8497 break; // (or we would have caught him before). Abort repetition-checking loop.
8499 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8500 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8502 details = "Xboard adjudication: repetition";
8504 } else // it must be XQ
8505 // Now check for perpetual chases
8506 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8507 hisPerpetual = PerpetualChase(k, forwardMostMove);
8508 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8509 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8510 static char resdet[MSG_SIZ];
8511 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8513 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8515 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8516 break; // Abort repetition-checking loop.
8518 // if neither of us is checking or chasing all the time, or both are, it is draw
8520 if(engineOpponent) {
8521 SendToProgram("force\n", engineOpponent); // suppress reply
8522 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8524 GameEnds( result, details, GE_XBOARD );
8527 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8528 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8532 /* Now we test for 50-move draws. Determine ply count */
8533 count = forwardMostMove;
8534 /* look for last irreversble move */
8535 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8537 /* if we hit starting position, add initial plies */
8538 if( count == backwardMostMove )
8539 count -= initialRulePlies;
8540 count = forwardMostMove - count;
8541 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8542 // adjust reversible move counter for checks in Xiangqi
8543 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8544 if(i < backwardMostMove) i = backwardMostMove;
8545 while(i <= forwardMostMove) {
8546 lastCheck = inCheck; // check evasion does not count
8547 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8548 if(inCheck || lastCheck) count--; // check does not count
8553 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8554 /* this is used to judge if draw claims are legal */
8555 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8556 if(engineOpponent) {
8557 SendToProgram("force\n", engineOpponent); // suppress reply
8558 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8560 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8564 /* if draw offer is pending, treat it as a draw claim
8565 * when draw condition present, to allow engines a way to
8566 * claim draws before making their move to avoid a race
8567 * condition occurring after their move
8569 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8571 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8572 p = "Draw claim: 50-move rule";
8573 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8574 p = "Draw claim: 3-fold repetition";
8575 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8576 p = "Draw claim: insufficient mating material";
8577 if( p != NULL && canAdjudicate) {
8578 if(engineOpponent) {
8579 SendToProgram("force\n", engineOpponent); // suppress reply
8580 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8582 GameEnds( GameIsDrawn, p, GE_XBOARD );
8587 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8588 if(engineOpponent) {
8589 SendToProgram("force\n", engineOpponent); // suppress reply
8590 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8592 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8598 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8599 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8600 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8605 int pieces[10], squares[10], cnt=0, r, f, res;
8607 static PPROBE_EGBB probeBB;
8608 if(!appData.testLegality) return 10;
8609 if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8610 if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8611 if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8612 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8613 ChessSquare piece = boards[forwardMostMove][r][f];
8614 int black = (piece >= BlackPawn);
8615 int type = piece - black*BlackPawn;
8616 if(piece == EmptySquare) continue;
8617 if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8618 if(type == WhiteKing) type = WhiteQueen + 1;
8619 type = egbbCode[type];
8620 squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8621 pieces[cnt] = type + black*6;
8622 if(++cnt > 5) return 11;
8624 pieces[cnt] = squares[cnt] = 0;
8626 if(loaded == 2) return 13; // loading failed before
8628 char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8631 loaded = 2; // prepare for failure
8632 if(!path) return 13; // no egbb installed
8633 strncpy(buf, path + 8, MSG_SIZ);
8634 if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8635 snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8636 lib = LoadLibrary(buf);
8637 if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8638 loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8639 probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8640 if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8641 p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8642 loaded = 1; // success!
8644 res = probeBB(forwardMostMove & 1, pieces, squares);
8645 return res > 0 ? 1 : res < 0 ? -1 : 0;
8649 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8650 { // [HGM] book: this routine intercepts moves to simulate book replies
8651 char *bookHit = NULL;
8653 if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8655 snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8656 SendToProgram(buf, cps);
8658 //first determine if the incoming move brings opponent into his book
8659 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8660 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8661 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8662 if(bookHit != NULL && !cps->bookSuspend) {
8663 // make sure opponent is not going to reply after receiving move to book position
8664 SendToProgram("force\n", cps);
8665 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8667 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8668 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8669 // now arrange restart after book miss
8671 // after a book hit we never send 'go', and the code after the call to this routine
8672 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8673 char buf[MSG_SIZ], *move = bookHit;
8675 int fromX, fromY, toX, toY;
8679 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8680 &fromX, &fromY, &toX, &toY, &promoChar)) {
8681 (void) CoordsToAlgebraic(boards[forwardMostMove],
8682 PosFlags(forwardMostMove),
8683 fromY, fromX, toY, toX, promoChar, move);
8685 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8689 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8690 SendToProgram(buf, cps);
8691 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8692 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8693 SendToProgram("go\n", cps);
8694 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8695 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8696 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8697 SendToProgram("go\n", cps);
8698 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8700 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8704 LoadError (char *errmess, ChessProgramState *cps)
8705 { // unloads engine and switches back to -ncp mode if it was first
8706 if(cps->initDone) return FALSE;
8707 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8708 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8711 appData.noChessProgram = TRUE;
8712 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8713 gameMode = BeginningOfGame; ModeHighlight();
8716 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8717 DisplayMessage("", ""); // erase waiting message
8718 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8723 ChessProgramState *savedState;
8725 DeferredBookMove (void)
8727 if(savedState->lastPing != savedState->lastPong)
8728 ScheduleDelayedEvent(DeferredBookMove, 10);
8730 HandleMachineMove(savedMessage, savedState);
8733 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8734 static ChessProgramState *stalledEngine;
8735 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8738 HandleMachineMove (char *message, ChessProgramState *cps)
8740 static char firstLeg[20], legs;
8741 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8742 char realname[MSG_SIZ];
8743 int fromX, fromY, toX, toY;
8745 char promoChar, roar;
8750 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8751 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8752 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8753 DisplayError(_("Invalid pairing from pairing engine"), 0);
8756 pairingReceived = 1;
8758 return; // Skim the pairing messages here.
8761 oldError = cps->userError; cps->userError = 0;
8763 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8765 * Kludge to ignore BEL characters
8767 while (*message == '\007') message++;
8770 * [HGM] engine debug message: ignore lines starting with '#' character
8772 if(cps->debug && *message == '#') return;
8775 * Look for book output
8777 if (cps == &first && bookRequested) {
8778 if (message[0] == '\t' || message[0] == ' ') {
8779 /* Part of the book output is here; append it */
8780 strcat(bookOutput, message);
8781 strcat(bookOutput, " \n");
8783 } else if (bookOutput[0] != NULLCHAR) {
8784 /* All of book output has arrived; display it */
8785 char *p = bookOutput;
8786 while (*p != NULLCHAR) {
8787 if (*p == '\t') *p = ' ';
8790 DisplayInformation(bookOutput);
8791 bookRequested = FALSE;
8792 /* Fall through to parse the current output */
8797 * Look for machine move.
8799 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8800 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8802 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8803 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8804 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8805 stalledEngine = cps;
8806 if(appData.ponderNextMove) { // bring opponent out of ponder
8807 if(gameMode == TwoMachinesPlay) {
8808 if(cps->other->pause)
8809 PauseEngine(cps->other);
8811 SendToProgram("easy\n", cps->other);
8820 /* This method is only useful on engines that support ping */
8821 if(abortEngineThink) {
8822 if (appData.debugMode) {
8823 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8825 SendToProgram("undo\n", cps);
8829 if (cps->lastPing != cps->lastPong) {
8830 /* Extra move from before last new; ignore */
8831 if (appData.debugMode) {
8832 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8839 int machineWhite = FALSE;
8842 case BeginningOfGame:
8843 /* Extra move from before last reset; ignore */
8844 if (appData.debugMode) {
8845 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8852 /* Extra move after we tried to stop. The mode test is
8853 not a reliable way of detecting this problem, but it's
8854 the best we can do on engines that don't support ping.
8856 if (appData.debugMode) {
8857 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8858 cps->which, gameMode);
8860 SendToProgram("undo\n", cps);
8863 case MachinePlaysWhite:
8864 case IcsPlayingWhite:
8865 machineWhite = TRUE;
8868 case MachinePlaysBlack:
8869 case IcsPlayingBlack:
8870 machineWhite = FALSE;
8873 case TwoMachinesPlay:
8874 machineWhite = (cps->twoMachinesColor[0] == 'w');
8877 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8878 if (appData.debugMode) {
8880 "Ignoring move out of turn by %s, gameMode %d"
8881 ", forwardMost %d\n",
8882 cps->which, gameMode, forwardMostMove);
8888 if(cps->alphaRank) AlphaRank(machineMove, 4);
8890 // [HGM] lion: (some very limited) support for Alien protocol
8891 killX = killY = kill2X = kill2Y = -1;
8892 if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8893 if(legs++) return; // middle leg contains only redundant info, ignore (but count it)
8894 safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8897 if(p = strchr(machineMove, ',')) { // we got both legs in one (happens on book move)
8898 char *q = strchr(p+1, ','); // second comma?
8899 safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8900 if(q) legs = 2, p = q; else legs = 1; // with 3-leg move we clipof first two legs!
8901 safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8903 if(firstLeg[0]) { // there was a previous leg;
8904 // only support case where same piece makes two step
8905 char buf[20], *p = machineMove+1, *q = buf+1, f;
8906 safeStrCpy(buf, machineMove, 20);
8907 while(isdigit(*q)) q++; // find start of to-square
8908 safeStrCpy(machineMove, firstLeg, 20);
8909 while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8910 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
8911 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)
8912 safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8913 sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8914 firstLeg[0] = NULLCHAR; legs = 0;
8917 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8918 &fromX, &fromY, &toX, &toY, &promoChar)) {
8919 /* Machine move could not be parsed; ignore it. */
8920 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8921 machineMove, _(cps->which));
8922 DisplayMoveError(buf1);
8923 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8924 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8925 if (gameMode == TwoMachinesPlay) {
8926 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8932 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8933 /* So we have to redo legality test with true e.p. status here, */
8934 /* to make sure an illegal e.p. capture does not slip through, */
8935 /* to cause a forfeit on a justified illegal-move complaint */
8936 /* of the opponent. */
8937 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8939 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8940 fromY, fromX, toY, toX, promoChar);
8941 if(moveType == IllegalMove) {
8942 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8943 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8944 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8947 } else if(!appData.fischerCastling)
8948 /* [HGM] Kludge to handle engines that send FRC-style castling
8949 when they shouldn't (like TSCP-Gothic) */
8951 case WhiteASideCastleFR:
8952 case BlackASideCastleFR:
8954 currentMoveString[2]++;
8956 case WhiteHSideCastleFR:
8957 case BlackHSideCastleFR:
8959 currentMoveString[2]--;
8961 default: ; // nothing to do, but suppresses warning of pedantic compilers
8964 hintRequested = FALSE;
8965 lastHint[0] = NULLCHAR;
8966 bookRequested = FALSE;
8967 /* Program may be pondering now */
8968 cps->maybeThinking = TRUE;
8969 if (cps->sendTime == 2) cps->sendTime = 1;
8970 if (cps->offeredDraw) cps->offeredDraw--;
8972 /* [AS] Save move info*/
8973 pvInfoList[ forwardMostMove ].score = programStats.score;
8974 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8975 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8977 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8979 /* Test suites abort the 'game' after one move */
8980 if(*appData.finger) {
8982 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8983 if(!f) f = fopen(appData.finger, "w");
8984 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8985 else { DisplayFatalError("Bad output file", errno, 0); return; }
8987 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8990 if(solvingTime >= 0) {
8991 snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
8992 totalTime += solvingTime; first.matchWins++; solvingTime = -1;
8994 snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
8995 if(solvingTime == -2) second.matchWins++;
8997 OutputKibitz(2, buf1);
8998 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9001 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
9002 if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
9005 while( count < adjudicateLossPlies ) {
9006 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
9009 score = -score; /* Flip score for winning side */
9012 if( score > appData.adjudicateLossThreshold ) {
9019 if( count >= adjudicateLossPlies ) {
9020 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9022 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
9023 "Xboard adjudication",
9030 if(Adjudicate(cps)) {
9031 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9032 return; // [HGM] adjudicate: for all automatic game ends
9036 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9038 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9039 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9041 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9043 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9045 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9046 char buf[3*MSG_SIZ];
9048 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9049 programStats.score / 100.,
9051 programStats.time / 100.,
9052 (unsigned int)programStats.nodes,
9053 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9054 programStats.movelist);
9060 /* [AS] Clear stats for next move */
9061 ClearProgramStats();
9062 thinkOutput[0] = NULLCHAR;
9063 hiddenThinkOutputState = 0;
9066 if (gameMode == TwoMachinesPlay) {
9067 /* [HGM] relaying draw offers moved to after reception of move */
9068 /* and interpreting offer as claim if it brings draw condition */
9069 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9070 SendToProgram("draw\n", cps->other);
9072 if (cps->other->sendTime) {
9073 SendTimeRemaining(cps->other,
9074 cps->other->twoMachinesColor[0] == 'w');
9076 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9077 if (firstMove && !bookHit) {
9079 if (cps->other->useColors) {
9080 SendToProgram(cps->other->twoMachinesColor, cps->other);
9082 SendToProgram("go\n", cps->other);
9084 cps->other->maybeThinking = TRUE;
9087 roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9089 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9091 if (!pausing && appData.ringBellAfterMoves) {
9092 if(!roar) RingBell();
9096 * Reenable menu items that were disabled while
9097 * machine was thinking
9099 if (gameMode != TwoMachinesPlay)
9100 SetUserThinkingEnables();
9102 // [HGM] book: after book hit opponent has received move and is now in force mode
9103 // force the book reply into it, and then fake that it outputted this move by jumping
9104 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9106 static char bookMove[MSG_SIZ]; // a bit generous?
9108 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9109 strcat(bookMove, bookHit);
9112 programStats.nodes = programStats.depth = programStats.time =
9113 programStats.score = programStats.got_only_move = 0;
9114 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9116 if(cps->lastPing != cps->lastPong) {
9117 savedMessage = message; // args for deferred call
9119 ScheduleDelayedEvent(DeferredBookMove, 10);
9128 /* Set special modes for chess engines. Later something general
9129 * could be added here; for now there is just one kludge feature,
9130 * needed because Crafty 15.10 and earlier don't ignore SIGINT
9131 * when "xboard" is given as an interactive command.
9133 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9134 cps->useSigint = FALSE;
9135 cps->useSigterm = FALSE;
9137 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9138 ParseFeatures(message+8, cps);
9139 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9142 if (!strncmp(message, "setup ", 6) &&
9143 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9144 NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9145 ) { // [HGM] allow first engine to define opening position
9146 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9147 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9149 if(sscanf(message, "setup (%s", buf) == 1) {
9150 s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9151 ASSIGN(appData.pieceToCharTable, buf);
9153 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9155 while(message[s] && message[s++] != ' ');
9156 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9157 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9158 if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
9159 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9160 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
9161 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9162 if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9163 startedFromSetupPosition = FALSE;
9166 if(startedFromSetupPosition) return;
9167 ParseFEN(boards[0], &dummy, message+s, FALSE);
9168 DrawPosition(TRUE, boards[0]);
9169 CopyBoard(initialPosition, boards[0]);
9170 startedFromSetupPosition = TRUE;
9173 if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9174 ChessSquare piece = WhitePawn;
9175 char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0;
9176 if(*p == '+') promoted++, ID = *++p;
9177 if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9178 piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece);
9179 if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9180 /* always accept definition of */ && piece != WhiteFalcon && piece != BlackFalcon
9181 /* wild-card pieces. */ && piece != WhiteCobra && piece != BlackCobra
9182 /* For variants we don't have */ && gameInfo.variant != VariantBerolina
9183 /* correct rules for, we cannot */ && gameInfo.variant != VariantCylinder
9184 /* enforce legality on our own! */ && gameInfo.variant != VariantUnknown
9185 && gameInfo.variant != VariantGreat
9186 && gameInfo.variant != VariantFairy ) return;
9187 if(piece < EmptySquare) {
9189 ASSIGN(pieceDesc[piece], buf1);
9190 if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9194 if(!appData.testLegality && sscanf(message, "choice %s", promoRestrict) == 1) {
9196 LeftClick(Press, 0, 0); // finish the click that was interrupted
9197 } else if(promoSweep != EmptySquare) {
9198 promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9199 if(strlen(promoRestrict) > 1) Sweep(0);
9203 /* [HGM] Allow engine to set up a position. Don't ask me why one would
9204 * want this, I was asked to put it in, and obliged.
9206 if (!strncmp(message, "setboard ", 9)) {
9207 Board initial_position;
9209 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9211 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9212 DisplayError(_("Bad FEN received from engine"), 0);
9216 CopyBoard(boards[0], initial_position);
9217 initialRulePlies = FENrulePlies;
9218 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9219 else gameMode = MachinePlaysBlack;
9220 DrawPosition(FALSE, boards[currentMove]);
9226 * Look for communication commands
9228 if (!strncmp(message, "telluser ", 9)) {
9229 if(message[9] == '\\' && message[10] == '\\')
9230 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9232 DisplayNote(message + 9);
9235 if (!strncmp(message, "tellusererror ", 14)) {
9237 if(message[14] == '\\' && message[15] == '\\')
9238 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9240 DisplayError(message + 14, 0);
9243 if (!strncmp(message, "tellopponent ", 13)) {
9244 if (appData.icsActive) {
9246 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9250 DisplayNote(message + 13);
9254 if (!strncmp(message, "tellothers ", 11)) {
9255 if (appData.icsActive) {
9257 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9260 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9263 if (!strncmp(message, "tellall ", 8)) {
9264 if (appData.icsActive) {
9266 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9270 DisplayNote(message + 8);
9274 if (strncmp(message, "warning", 7) == 0) {
9275 /* Undocumented feature, use tellusererror in new code */
9276 DisplayError(message, 0);
9279 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9280 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9281 strcat(realname, " query");
9282 AskQuestion(realname, buf2, buf1, cps->pr);
9285 /* Commands from the engine directly to ICS. We don't allow these to be
9286 * sent until we are logged on. Crafty kibitzes have been known to
9287 * interfere with the login process.
9290 if (!strncmp(message, "tellics ", 8)) {
9291 SendToICS(message + 8);
9295 if (!strncmp(message, "tellicsnoalias ", 15)) {
9296 SendToICS(ics_prefix);
9297 SendToICS(message + 15);
9301 /* The following are for backward compatibility only */
9302 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9303 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9304 SendToICS(ics_prefix);
9310 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9311 if(initPing == cps->lastPong) {
9312 if(gameInfo.variant == VariantUnknown) {
9313 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9314 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9315 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9319 if(cps->lastPing == cps->lastPong && abortEngineThink) {
9320 abortEngineThink = FALSE;
9321 DisplayMessage("", "");
9326 if(!strncmp(message, "highlight ", 10)) {
9327 if(appData.testLegality && !*engineVariant && appData.markers) return;
9328 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9331 if(!strncmp(message, "click ", 6)) {
9332 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9333 if(appData.testLegality || !appData.oneClick) return;
9334 sscanf(message+6, "%c%d%c", &f, &y, &c);
9335 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9336 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9337 x = x*squareSize + (x+1)*lineGap + squareSize/2;
9338 y = y*squareSize + (y+1)*lineGap + squareSize/2;
9339 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9340 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9341 LeftClick(Release, lastLeftX, lastLeftY);
9342 controlKey = (c == ',');
9343 LeftClick(Press, x, y);
9344 LeftClick(Release, x, y);
9345 first.highlight = f;
9349 * If the move is illegal, cancel it and redraw the board.
9350 * Also deal with other error cases. Matching is rather loose
9351 * here to accommodate engines written before the spec.
9353 if (strncmp(message + 1, "llegal move", 11) == 0 ||
9354 strncmp(message, "Error", 5) == 0) {
9355 if (StrStr(message, "name") ||
9356 StrStr(message, "rating") || StrStr(message, "?") ||
9357 StrStr(message, "result") || StrStr(message, "board") ||
9358 StrStr(message, "bk") || StrStr(message, "computer") ||
9359 StrStr(message, "variant") || StrStr(message, "hint") ||
9360 StrStr(message, "random") || StrStr(message, "depth") ||
9361 StrStr(message, "accepted")) {
9364 if (StrStr(message, "protover")) {
9365 /* Program is responding to input, so it's apparently done
9366 initializing, and this error message indicates it is
9367 protocol version 1. So we don't need to wait any longer
9368 for it to initialize and send feature commands. */
9369 FeatureDone(cps, 1);
9370 cps->protocolVersion = 1;
9373 cps->maybeThinking = FALSE;
9375 if (StrStr(message, "draw")) {
9376 /* Program doesn't have "draw" command */
9377 cps->sendDrawOffers = 0;
9380 if (cps->sendTime != 1 &&
9381 (StrStr(message, "time") || StrStr(message, "otim"))) {
9382 /* Program apparently doesn't have "time" or "otim" command */
9386 if (StrStr(message, "analyze")) {
9387 cps->analysisSupport = FALSE;
9388 cps->analyzing = FALSE;
9389 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9390 EditGameEvent(); // [HGM] try to preserve loaded game
9391 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9392 DisplayError(buf2, 0);
9395 if (StrStr(message, "(no matching move)st")) {
9396 /* Special kludge for GNU Chess 4 only */
9397 cps->stKludge = TRUE;
9398 SendTimeControl(cps, movesPerSession, timeControl,
9399 timeIncrement, appData.searchDepth,
9403 if (StrStr(message, "(no matching move)sd")) {
9404 /* Special kludge for GNU Chess 4 only */
9405 cps->sdKludge = TRUE;
9406 SendTimeControl(cps, movesPerSession, timeControl,
9407 timeIncrement, appData.searchDepth,
9411 if (!StrStr(message, "llegal")) {
9414 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9415 gameMode == IcsIdle) return;
9416 if (forwardMostMove <= backwardMostMove) return;
9417 if (pausing) PauseEvent();
9418 if(appData.forceIllegal) {
9419 // [HGM] illegal: machine refused move; force position after move into it
9420 SendToProgram("force\n", cps);
9421 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9422 // we have a real problem now, as SendBoard will use the a2a3 kludge
9423 // when black is to move, while there might be nothing on a2 or black
9424 // might already have the move. So send the board as if white has the move.
9425 // But first we must change the stm of the engine, as it refused the last move
9426 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9427 if(WhiteOnMove(forwardMostMove)) {
9428 SendToProgram("a7a6\n", cps); // for the engine black still had the move
9429 SendBoard(cps, forwardMostMove); // kludgeless board
9431 SendToProgram("a2a3\n", cps); // for the engine white still had the move
9432 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9433 SendBoard(cps, forwardMostMove+1); // kludgeless board
9435 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9436 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9437 gameMode == TwoMachinesPlay)
9438 SendToProgram("go\n", cps);
9441 if (gameMode == PlayFromGameFile) {
9442 /* Stop reading this game file */
9443 gameMode = EditGame;
9446 /* [HGM] illegal-move claim should forfeit game when Xboard */
9447 /* only passes fully legal moves */
9448 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9449 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9450 "False illegal-move claim", GE_XBOARD );
9451 return; // do not take back move we tested as valid
9453 currentMove = forwardMostMove-1;
9454 DisplayMove(currentMove-1); /* before DisplayMoveError */
9455 SwitchClocks(forwardMostMove-1); // [HGM] race
9456 DisplayBothClocks();
9457 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9458 parseList[currentMove], _(cps->which));
9459 DisplayMoveError(buf1);
9460 DrawPosition(FALSE, boards[currentMove]);
9462 SetUserThinkingEnables();
9465 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9466 /* Program has a broken "time" command that
9467 outputs a string not ending in newline.
9471 if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9472 if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9473 sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1 ) return;
9477 * If chess program startup fails, exit with an error message.
9478 * Attempts to recover here are futile. [HGM] Well, we try anyway
9480 if ((StrStr(message, "unknown host") != NULL)
9481 || (StrStr(message, "No remote directory") != NULL)
9482 || (StrStr(message, "not found") != NULL)
9483 || (StrStr(message, "No such file") != NULL)
9484 || (StrStr(message, "can't alloc") != NULL)
9485 || (StrStr(message, "Permission denied") != NULL)) {
9487 cps->maybeThinking = FALSE;
9488 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9489 _(cps->which), cps->program, cps->host, message);
9490 RemoveInputSource(cps->isr);
9491 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9492 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9493 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9499 * Look for hint output
9501 if (sscanf(message, "Hint: %s", buf1) == 1) {
9502 if (cps == &first && hintRequested) {
9503 hintRequested = FALSE;
9504 if (ParseOneMove(buf1, forwardMostMove, &moveType,
9505 &fromX, &fromY, &toX, &toY, &promoChar)) {
9506 (void) CoordsToAlgebraic(boards[forwardMostMove],
9507 PosFlags(forwardMostMove),
9508 fromY, fromX, toY, toX, promoChar, buf1);
9509 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9510 DisplayInformation(buf2);
9512 /* Hint move could not be parsed!? */
9513 snprintf(buf2, sizeof(buf2),
9514 _("Illegal hint move \"%s\"\nfrom %s chess program"),
9515 buf1, _(cps->which));
9516 DisplayError(buf2, 0);
9519 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9525 * Ignore other messages if game is not in progress
9527 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9528 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9531 * look for win, lose, draw, or draw offer
9533 if (strncmp(message, "1-0", 3) == 0) {
9534 char *p, *q, *r = "";
9535 p = strchr(message, '{');
9543 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9545 } else if (strncmp(message, "0-1", 3) == 0) {
9546 char *p, *q, *r = "";
9547 p = strchr(message, '{');
9555 /* Kludge for Arasan 4.1 bug */
9556 if (strcmp(r, "Black resigns") == 0) {
9557 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9560 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9562 } else if (strncmp(message, "1/2", 3) == 0) {
9563 char *p, *q, *r = "";
9564 p = strchr(message, '{');
9573 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9576 } else if (strncmp(message, "White resign", 12) == 0) {
9577 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9579 } else if (strncmp(message, "Black resign", 12) == 0) {
9580 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9582 } else if (strncmp(message, "White matches", 13) == 0 ||
9583 strncmp(message, "Black matches", 13) == 0 ) {
9584 /* [HGM] ignore GNUShogi noises */
9586 } else if (strncmp(message, "White", 5) == 0 &&
9587 message[5] != '(' &&
9588 StrStr(message, "Black") == NULL) {
9589 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9591 } else if (strncmp(message, "Black", 5) == 0 &&
9592 message[5] != '(') {
9593 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9595 } else if (strcmp(message, "resign") == 0 ||
9596 strcmp(message, "computer resigns") == 0) {
9598 case MachinePlaysBlack:
9599 case IcsPlayingBlack:
9600 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9602 case MachinePlaysWhite:
9603 case IcsPlayingWhite:
9604 GameEnds(BlackWins, "White resigns", GE_ENGINE);
9606 case TwoMachinesPlay:
9607 if (cps->twoMachinesColor[0] == 'w')
9608 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9610 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9617 } else if (strncmp(message, "opponent mates", 14) == 0) {
9619 case MachinePlaysBlack:
9620 case IcsPlayingBlack:
9621 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9623 case MachinePlaysWhite:
9624 case IcsPlayingWhite:
9625 GameEnds(BlackWins, "Black mates", GE_ENGINE);
9627 case TwoMachinesPlay:
9628 if (cps->twoMachinesColor[0] == 'w')
9629 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9631 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9638 } else if (strncmp(message, "computer mates", 14) == 0) {
9640 case MachinePlaysBlack:
9641 case IcsPlayingBlack:
9642 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9644 case MachinePlaysWhite:
9645 case IcsPlayingWhite:
9646 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9648 case TwoMachinesPlay:
9649 if (cps->twoMachinesColor[0] == 'w')
9650 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9652 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9659 } else if (strncmp(message, "checkmate", 9) == 0) {
9660 if (WhiteOnMove(forwardMostMove)) {
9661 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9663 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9666 } else if (strstr(message, "Draw") != NULL ||
9667 strstr(message, "game is a draw") != NULL) {
9668 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9670 } else if (strstr(message, "offer") != NULL &&
9671 strstr(message, "draw") != NULL) {
9673 if (appData.zippyPlay && first.initDone) {
9674 /* Relay offer to ICS */
9675 SendToICS(ics_prefix);
9676 SendToICS("draw\n");
9679 cps->offeredDraw = 2; /* valid until this engine moves twice */
9680 if (gameMode == TwoMachinesPlay) {
9681 if (cps->other->offeredDraw) {
9682 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9683 /* [HGM] in two-machine mode we delay relaying draw offer */
9684 /* until after we also have move, to see if it is really claim */
9686 } else if (gameMode == MachinePlaysWhite ||
9687 gameMode == MachinePlaysBlack) {
9688 if (userOfferedDraw) {
9689 DisplayInformation(_("Machine accepts your draw offer"));
9690 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9692 DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9699 * Look for thinking output
9701 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9702 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9704 int plylev, mvleft, mvtot, curscore, time;
9705 char mvname[MOVE_LEN];
9709 int prefixHint = FALSE;
9710 mvname[0] = NULLCHAR;
9713 case MachinePlaysBlack:
9714 case IcsPlayingBlack:
9715 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9717 case MachinePlaysWhite:
9718 case IcsPlayingWhite:
9719 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9724 case IcsObserving: /* [DM] icsEngineAnalyze */
9725 if (!appData.icsEngineAnalyze) ignore = TRUE;
9727 case TwoMachinesPlay:
9728 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9738 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9741 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9742 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9743 char score_buf[MSG_SIZ];
9745 if(nodes>>32 == u64Const(0xFFFFFFFF)) // [HGM] negative node count read
9746 nodes += u64Const(0x100000000);
9748 if (plyext != ' ' && plyext != '\t') {
9752 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9753 if( cps->scoreIsAbsolute &&
9754 ( gameMode == MachinePlaysBlack ||
9755 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9756 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9757 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9758 !WhiteOnMove(currentMove)
9761 curscore = -curscore;
9764 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9766 if(*bestMove) { // rememer time best EPD move was first found
9767 int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9768 ChessMove mt; char *p = bestMove;
9769 int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9771 while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9772 if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9773 solvingTime = (solvingTime < 0 ? time : solvingTime);
9777 while(*p && *p != ' ') p++;
9778 while(*p == ' ') p++;
9780 if(!solved) solvingTime = -1;
9782 if(*avoidMove && !solved) {
9783 int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9784 ChessMove mt; char *p = avoidMove, solved = 1;
9785 int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9786 while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9787 if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9788 solved = 0; solvingTime = -2;
9791 while(*p && *p != ' ') p++;
9792 while(*p == ' ') p++;
9794 if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9797 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9800 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9801 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9802 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9803 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9804 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9805 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9809 /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9810 DisplayError(_("failed writing PV"), 0);
9813 tempStats.depth = plylev;
9814 tempStats.nodes = nodes;
9815 tempStats.time = time;
9816 tempStats.score = curscore;
9817 tempStats.got_only_move = 0;
9819 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9822 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9823 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9824 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9825 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9826 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9827 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9828 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9829 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9832 /* Buffer overflow protection */
9833 if (pv[0] != NULLCHAR) {
9834 if (strlen(pv) >= sizeof(tempStats.movelist)
9835 && appData.debugMode) {
9837 "PV is too long; using the first %u bytes.\n",
9838 (unsigned) sizeof(tempStats.movelist) - 1);
9841 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9843 sprintf(tempStats.movelist, " no PV\n");
9846 if (tempStats.seen_stat) {
9847 tempStats.ok_to_send = 1;
9850 if (strchr(tempStats.movelist, '(') != NULL) {
9851 tempStats.line_is_book = 1;
9852 tempStats.nr_moves = 0;
9853 tempStats.moves_left = 0;
9855 tempStats.line_is_book = 0;
9858 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9859 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9861 SendProgramStatsToFrontend( cps, &tempStats );
9864 [AS] Protect the thinkOutput buffer from overflow... this
9865 is only useful if buf1 hasn't overflowed first!
9867 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9868 if(curscore >= MATE_SCORE)
9869 snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9870 else if(curscore <= -MATE_SCORE)
9871 snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9873 snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9874 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9876 (gameMode == TwoMachinesPlay ?
9877 ToUpper(cps->twoMachinesColor[0]) : ' '),
9879 prefixHint ? lastHint : "",
9880 prefixHint ? " " : "" );
9882 if( buf1[0] != NULLCHAR ) {
9883 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9885 if( strlen(pv) > max_len ) {
9886 if( appData.debugMode) {
9887 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9889 pv[max_len+1] = '\0';
9892 strcat( thinkOutput, pv);
9895 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9896 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9897 DisplayMove(currentMove - 1);
9901 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9902 /* crafty (9.25+) says "(only move) <move>"
9903 * if there is only 1 legal move
9905 sscanf(p, "(only move) %s", buf1);
9906 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9907 sprintf(programStats.movelist, "%s (only move)", buf1);
9908 programStats.depth = 1;
9909 programStats.nr_moves = 1;
9910 programStats.moves_left = 1;
9911 programStats.nodes = 1;
9912 programStats.time = 1;
9913 programStats.got_only_move = 1;
9915 /* Not really, but we also use this member to
9916 mean "line isn't going to change" (Crafty
9917 isn't searching, so stats won't change) */
9918 programStats.line_is_book = 1;
9920 SendProgramStatsToFrontend( cps, &programStats );
9922 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9923 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9924 DisplayMove(currentMove - 1);
9927 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9928 &time, &nodes, &plylev, &mvleft,
9929 &mvtot, mvname) >= 5) {
9930 /* The stat01: line is from Crafty (9.29+) in response
9931 to the "." command */
9932 programStats.seen_stat = 1;
9933 cps->maybeThinking = TRUE;
9935 if (programStats.got_only_move || !appData.periodicUpdates)
9938 programStats.depth = plylev;
9939 programStats.time = time;
9940 programStats.nodes = nodes;
9941 programStats.moves_left = mvleft;
9942 programStats.nr_moves = mvtot;
9943 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9944 programStats.ok_to_send = 1;
9945 programStats.movelist[0] = '\0';
9947 SendProgramStatsToFrontend( cps, &programStats );
9951 } else if (strncmp(message,"++",2) == 0) {
9952 /* Crafty 9.29+ outputs this */
9953 programStats.got_fail = 2;
9956 } else if (strncmp(message,"--",2) == 0) {
9957 /* Crafty 9.29+ outputs this */
9958 programStats.got_fail = 1;
9961 } else if (thinkOutput[0] != NULLCHAR &&
9962 strncmp(message, " ", 4) == 0) {
9963 unsigned message_len;
9966 while (*p && *p == ' ') p++;
9968 message_len = strlen( p );
9970 /* [AS] Avoid buffer overflow */
9971 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9972 strcat(thinkOutput, " ");
9973 strcat(thinkOutput, p);
9976 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9977 strcat(programStats.movelist, " ");
9978 strcat(programStats.movelist, p);
9981 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9982 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9983 DisplayMove(currentMove - 1);
9991 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9992 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9994 ChessProgramStats cpstats;
9996 if (plyext != ' ' && plyext != '\t') {
10000 /* [AS] Negate score if machine is playing black and reporting absolute scores */
10001 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
10002 curscore = -curscore;
10005 cpstats.depth = plylev;
10006 cpstats.nodes = nodes;
10007 cpstats.time = time;
10008 cpstats.score = curscore;
10009 cpstats.got_only_move = 0;
10010 cpstats.movelist[0] = '\0';
10012 if (buf1[0] != NULLCHAR) {
10013 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
10016 cpstats.ok_to_send = 0;
10017 cpstats.line_is_book = 0;
10018 cpstats.nr_moves = 0;
10019 cpstats.moves_left = 0;
10021 SendProgramStatsToFrontend( cps, &cpstats );
10028 /* Parse a game score from the character string "game", and
10029 record it as the history of the current game. The game
10030 score is NOT assumed to start from the standard position.
10031 The display is not updated in any way.
10034 ParseGameHistory (char *game)
10036 ChessMove moveType;
10037 int fromX, fromY, toX, toY, boardIndex, mask;
10042 if (appData.debugMode)
10043 fprintf(debugFP, "Parsing game history: %s\n", game);
10045 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10046 gameInfo.site = StrSave(appData.icsHost);
10047 gameInfo.date = PGNDate();
10048 gameInfo.round = StrSave("-");
10050 /* Parse out names of players */
10051 while (*game == ' ') game++;
10053 while (*game != ' ') *p++ = *game++;
10055 gameInfo.white = StrSave(buf);
10056 while (*game == ' ') game++;
10058 while (*game != ' ' && *game != '\n') *p++ = *game++;
10060 gameInfo.black = StrSave(buf);
10063 boardIndex = blackPlaysFirst ? 1 : 0;
10066 yyboardindex = boardIndex;
10067 moveType = (ChessMove) Myylex();
10068 switch (moveType) {
10069 case IllegalMove: /* maybe suicide chess, etc. */
10070 if (appData.debugMode) {
10071 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10072 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10073 setbuf(debugFP, NULL);
10075 case WhitePromotion:
10076 case BlackPromotion:
10077 case WhiteNonPromotion:
10078 case BlackNonPromotion:
10081 case WhiteCapturesEnPassant:
10082 case BlackCapturesEnPassant:
10083 case WhiteKingSideCastle:
10084 case WhiteQueenSideCastle:
10085 case BlackKingSideCastle:
10086 case BlackQueenSideCastle:
10087 case WhiteKingSideCastleWild:
10088 case WhiteQueenSideCastleWild:
10089 case BlackKingSideCastleWild:
10090 case BlackQueenSideCastleWild:
10092 case WhiteHSideCastleFR:
10093 case WhiteASideCastleFR:
10094 case BlackHSideCastleFR:
10095 case BlackASideCastleFR:
10097 fromX = currentMoveString[0] - AAA;
10098 fromY = currentMoveString[1] - ONE;
10099 toX = currentMoveString[2] - AAA;
10100 toY = currentMoveString[3] - ONE;
10101 promoChar = currentMoveString[4];
10105 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10106 fromX = moveType == WhiteDrop ?
10107 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10108 (int) CharToPiece(ToLower(currentMoveString[0]));
10110 toX = currentMoveString[2] - AAA;
10111 toY = currentMoveString[3] - ONE;
10112 promoChar = NULLCHAR;
10114 case AmbiguousMove:
10116 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10117 if (appData.debugMode) {
10118 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10119 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10120 setbuf(debugFP, NULL);
10122 DisplayError(buf, 0);
10124 case ImpossibleMove:
10126 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10127 if (appData.debugMode) {
10128 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10129 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10130 setbuf(debugFP, NULL);
10132 DisplayError(buf, 0);
10135 if (boardIndex < backwardMostMove) {
10136 /* Oops, gap. How did that happen? */
10137 DisplayError(_("Gap in move list"), 0);
10140 backwardMostMove = blackPlaysFirst ? 1 : 0;
10141 if (boardIndex > forwardMostMove) {
10142 forwardMostMove = boardIndex;
10146 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10147 strcat(parseList[boardIndex-1], " ");
10148 strcat(parseList[boardIndex-1], yy_text);
10160 case GameUnfinished:
10161 if (gameMode == IcsExamining) {
10162 if (boardIndex < backwardMostMove) {
10163 /* Oops, gap. How did that happen? */
10166 backwardMostMove = blackPlaysFirst ? 1 : 0;
10169 gameInfo.result = moveType;
10170 p = strchr(yy_text, '{');
10171 if (p == NULL) p = strchr(yy_text, '(');
10174 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10176 q = strchr(p, *p == '{' ? '}' : ')');
10177 if (q != NULL) *q = NULLCHAR;
10180 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10181 gameInfo.resultDetails = StrSave(p);
10184 if (boardIndex >= forwardMostMove &&
10185 !(gameMode == IcsObserving && ics_gamenum == -1)) {
10186 backwardMostMove = blackPlaysFirst ? 1 : 0;
10189 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10190 fromY, fromX, toY, toX, promoChar,
10191 parseList[boardIndex]);
10192 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10193 /* currentMoveString is set as a side-effect of yylex */
10194 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10195 strcat(moveList[boardIndex], "\n");
10197 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10198 mask = (WhiteOnMove(boardIndex) ? 0xFF : 0xFF00);
10199 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10205 if(boards[boardIndex][CHECK_COUNT]) boards[boardIndex][CHECK_COUNT] -= mask & 0x101;
10206 if(!boards[boardIndex][CHECK_COUNT] || boards[boardIndex][CHECK_COUNT] & mask) {
10207 if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[boardIndex - 1], "+");
10212 strcat(parseList[boardIndex - 1], "#");
10219 /* Apply a move to the given board */
10221 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10223 ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, epFile, lastFile, lastRank, berolina = 0;
10224 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10226 /* [HGM] compute & store e.p. status and castling rights for new position */
10227 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10229 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10230 oldEP = (signed char)board[EP_STATUS]; epRank = board[EP_RANK]; epFile = board[EP_FILE]; lastFile = board[LAST_TO] & 255,lastRank = board[LAST_TO] >> 8;
10231 board[EP_STATUS] = EP_NONE;
10232 board[EP_FILE] = board[EP_RANK] = 100, board[LAST_TO] = 0x4040;
10234 if (fromY == DROP_RANK) {
10235 /* must be first */
10236 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10237 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10240 piece = board[toY][toX] = (ChessSquare) fromX;
10242 // ChessSquare victim;
10245 if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10246 // victim = board[killY][killX],
10247 killed = board[killY][killX],
10248 board[killY][killX] = EmptySquare,
10249 board[EP_STATUS] = EP_CAPTURE;
10250 if( kill2X >= 0 && kill2Y >= 0)
10251 killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10254 if( board[toY][toX] != EmptySquare ) {
10255 board[EP_STATUS] = EP_CAPTURE;
10256 if( (fromX != toX || fromY != toY) && // not igui!
10257 (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10258 captured == BlackLion && board[fromY][fromX] != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules
10259 board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10263 pawn = board[fromY][fromX];
10264 if(pieceDesc[pawn] && strchr(pieceDesc[pawn], 'e')) { // piece with user-defined e.p. capture
10265 if(captured == EmptySquare && toX == epFile && (toY == (epRank & 127) || toY + (pawn < BlackPawn ? -1 : 1) == epRank - 128)) {
10266 captured = board[lastRank][lastFile]; // remove victim
10267 board[lastRank][lastFile] = EmptySquare;
10268 pawn = EmptySquare; // kludge to suppress old e.p. code
10271 if( pawn == WhiteLance || pawn == BlackLance ) {
10272 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10273 if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10274 else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10277 if( pawn == WhitePawn ) {
10278 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10279 board[EP_STATUS] = EP_PAWN_MOVE;
10280 if( toY-fromY>=2) {
10281 board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10282 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
10283 gameInfo.variant != VariantBerolina || toX < fromX)
10284 board[EP_STATUS] = toX | berolina;
10285 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10286 gameInfo.variant != VariantBerolina || toX > fromX)
10287 board[EP_STATUS] = toX;
10288 board[LAST_TO] = toX + 256*toY;
10291 if( pawn == BlackPawn ) {
10292 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10293 board[EP_STATUS] = EP_PAWN_MOVE;
10294 if( toY-fromY<= -2) {
10295 board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10296 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
10297 gameInfo.variant != VariantBerolina || toX < fromX)
10298 board[EP_STATUS] = toX | berolina;
10299 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10300 gameInfo.variant != VariantBerolina || toX > fromX)
10301 board[EP_STATUS] = toX;
10302 board[LAST_TO] = toX + 256*toY;
10306 if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10307 if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10308 if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10309 if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10311 for(i=0; i<nrCastlingRights; i++) {
10312 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10313 board[CASTLING][i] == toX && castlingRank[i] == toY
10314 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10317 if(gameInfo.variant == VariantSChess) { // update virginity
10318 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10319 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10320 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
10321 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
10324 if (fromX == toX && fromY == toY && killX < 0) return;
10326 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10327 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10328 if(gameInfo.variant == VariantKnightmate)
10329 king += (int) WhiteUnicorn - (int) WhiteKing;
10331 if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10332 && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) { // and tramples own
10333 board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10334 board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10335 board[EP_STATUS] = EP_NONE; // capture was fake!
10337 if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10338 board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10339 board[toY][toX] = piece;
10340 board[EP_STATUS] = EP_NONE; // capture was fake!
10342 /* Code added by Tord: */
10343 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10344 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10345 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10346 board[EP_STATUS] = EP_NONE; // capture was fake!
10347 board[fromY][fromX] = EmptySquare;
10348 board[toY][toX] = EmptySquare;
10349 if((toX > fromX) != (piece == WhiteRook)) {
10350 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10352 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10354 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10355 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10356 board[EP_STATUS] = EP_NONE;
10357 board[fromY][fromX] = EmptySquare;
10358 board[toY][toX] = EmptySquare;
10359 if((toX > fromX) != (piece == BlackRook)) {
10360 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10362 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10364 /* End of code added by Tord */
10366 } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10367 board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10368 board[toY][toX] = piece;
10369 } else if (board[fromY][fromX] == king
10370 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10371 && toY == fromY && toX > fromX+1) {
10372 for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10373 ; // castle with nearest piece
10374 board[fromY][toX-1] = board[fromY][rookX];
10375 board[fromY][rookX] = EmptySquare;
10376 board[fromY][fromX] = EmptySquare;
10377 board[toY][toX] = king;
10378 } else if (board[fromY][fromX] == king
10379 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10380 && toY == fromY && toX < fromX-1) {
10381 for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10382 ; // castle with nearest piece
10383 board[fromY][toX+1] = board[fromY][rookX];
10384 board[fromY][rookX] = EmptySquare;
10385 board[fromY][fromX] = EmptySquare;
10386 board[toY][toX] = king;
10387 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10388 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10389 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10391 /* white pawn promotion */
10392 board[toY][toX] = CharToPiece(ToUpper(promoChar));
10393 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10394 board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10395 board[fromY][fromX] = EmptySquare;
10396 } else if ((fromY >= BOARD_HEIGHT>>1)
10397 && (epFile == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10399 && gameInfo.variant != VariantXiangqi
10400 && gameInfo.variant != VariantBerolina
10401 && (pawn == WhitePawn)
10402 && (board[toY][toX] == EmptySquare)) {
10403 if(lastFile == 100) lastFile = (board[fromY][toX] == BlackPawn ? toX : fromX), lastRank = fromY; // assume FIDE e.p. if victim present
10404 board[fromY][fromX] = EmptySquare;
10405 board[toY][toX] = piece;
10406 captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10407 } else if ((fromY == BOARD_HEIGHT-4)
10409 && gameInfo.variant == VariantBerolina
10410 && (board[fromY][fromX] == WhitePawn)
10411 && (board[toY][toX] == EmptySquare)) {
10412 board[fromY][fromX] = EmptySquare;
10413 board[toY][toX] = WhitePawn;
10414 if(oldEP & EP_BEROLIN_A) {
10415 captured = board[fromY][fromX-1];
10416 board[fromY][fromX-1] = EmptySquare;
10417 }else{ captured = board[fromY][fromX+1];
10418 board[fromY][fromX+1] = EmptySquare;
10420 } else if (board[fromY][fromX] == king
10421 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10422 && toY == fromY && toX > fromX+1) {
10423 for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10425 board[fromY][toX-1] = board[fromY][rookX];
10426 board[fromY][rookX] = EmptySquare;
10427 board[fromY][fromX] = EmptySquare;
10428 board[toY][toX] = king;
10429 } else if (board[fromY][fromX] == king
10430 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10431 && toY == fromY && toX < fromX-1) {
10432 for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10434 board[fromY][toX+1] = board[fromY][rookX];
10435 board[fromY][rookX] = EmptySquare;
10436 board[fromY][fromX] = EmptySquare;
10437 board[toY][toX] = king;
10438 } else if (fromY == 7 && fromX == 3
10439 && board[fromY][fromX] == BlackKing
10440 && toY == 7 && toX == 5) {
10441 board[fromY][fromX] = EmptySquare;
10442 board[toY][toX] = BlackKing;
10443 board[fromY][7] = EmptySquare;
10444 board[toY][4] = BlackRook;
10445 } else if (fromY == 7 && fromX == 3
10446 && board[fromY][fromX] == BlackKing
10447 && toY == 7 && toX == 1) {
10448 board[fromY][fromX] = EmptySquare;
10449 board[toY][toX] = BlackKing;
10450 board[fromY][0] = EmptySquare;
10451 board[toY][2] = BlackRook;
10452 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10453 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10454 && toY < promoRank && promoChar
10456 /* black pawn promotion */
10457 board[toY][toX] = CharToPiece(ToLower(promoChar));
10458 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10459 board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10460 board[fromY][fromX] = EmptySquare;
10461 } else if ((fromY < BOARD_HEIGHT>>1)
10462 && (epFile == toX && epRank == toY || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10464 && gameInfo.variant != VariantXiangqi
10465 && gameInfo.variant != VariantBerolina
10466 && (pawn == BlackPawn)
10467 && (board[toY][toX] == EmptySquare)) {
10468 if(lastFile == 100) lastFile = (board[fromY][toX] == WhitePawn ? toX : fromX), lastRank = fromY;
10469 board[fromY][fromX] = EmptySquare;
10470 board[toY][toX] = piece;
10471 captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10472 } else if ((fromY == 3)
10474 && gameInfo.variant == VariantBerolina
10475 && (board[fromY][fromX] == BlackPawn)
10476 && (board[toY][toX] == EmptySquare)) {
10477 board[fromY][fromX] = EmptySquare;
10478 board[toY][toX] = BlackPawn;
10479 if(oldEP & EP_BEROLIN_A) {
10480 captured = board[fromY][fromX-1];
10481 board[fromY][fromX-1] = EmptySquare;
10482 }else{ captured = board[fromY][fromX+1];
10483 board[fromY][fromX+1] = EmptySquare;
10486 ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10487 board[fromY][fromX] = EmptySquare;
10488 board[toY][toX] = piece;
10492 if (gameInfo.holdingsWidth != 0) {
10494 /* !!A lot more code needs to be written to support holdings */
10495 /* [HGM] OK, so I have written it. Holdings are stored in the */
10496 /* penultimate board files, so they are automaticlly stored */
10497 /* in the game history. */
10498 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10499 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10500 /* Delete from holdings, by decreasing count */
10501 /* and erasing image if necessary */
10502 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10503 if(p < (int) BlackPawn) { /* white drop */
10504 p -= (int)WhitePawn;
10505 p = PieceToNumber((ChessSquare)p);
10506 if(p >= gameInfo.holdingsSize) p = 0;
10507 if(--board[p][BOARD_WIDTH-2] <= 0)
10508 board[p][BOARD_WIDTH-1] = EmptySquare;
10509 if((int)board[p][BOARD_WIDTH-2] < 0)
10510 board[p][BOARD_WIDTH-2] = 0;
10511 } else { /* black drop */
10512 p -= (int)BlackPawn;
10513 p = PieceToNumber((ChessSquare)p);
10514 if(p >= gameInfo.holdingsSize) p = 0;
10515 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10516 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10517 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10518 board[BOARD_HEIGHT-1-p][1] = 0;
10521 if (captured != EmptySquare && gameInfo.holdingsSize > 0
10522 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
10523 /* [HGM] holdings: Add to holdings, if holdings exist */
10524 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10525 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10526 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10528 p = (int) captured;
10529 if (p >= (int) BlackPawn) {
10530 p -= (int)BlackPawn;
10531 if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10532 /* Restore shogi-promoted piece to its original first */
10533 captured = (ChessSquare) (DEMOTED(captured));
10536 p = PieceToNumber((ChessSquare)p);
10537 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10538 board[p][BOARD_WIDTH-2]++;
10539 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10541 p -= (int)WhitePawn;
10542 if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10543 captured = (ChessSquare) (DEMOTED(captured));
10546 p = PieceToNumber((ChessSquare)p);
10547 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10548 board[BOARD_HEIGHT-1-p][1]++;
10549 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10552 } else if (gameInfo.variant == VariantAtomic) {
10553 if (captured != EmptySquare) {
10555 for (y = toY-1; y <= toY+1; y++) {
10556 for (x = toX-1; x <= toX+1; x++) {
10557 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10558 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10559 board[y][x] = EmptySquare;
10563 board[toY][toX] = EmptySquare;
10567 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10568 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10570 if(promoChar == '+') {
10571 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10572 board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10573 if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10574 board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10575 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10576 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10577 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10578 && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10579 board[toY][toX] = newPiece;
10581 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10582 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10583 // [HGM] superchess: take promotion piece out of holdings
10584 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10585 if((int)piece < (int)BlackPawn) { // determine stm from piece color
10586 if(!--board[k][BOARD_WIDTH-2])
10587 board[k][BOARD_WIDTH-1] = EmptySquare;
10589 if(!--board[BOARD_HEIGHT-1-k][1])
10590 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10595 /* Updates forwardMostMove */
10597 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10599 int x = toX, y = toY, mask;
10600 char *s = parseList[forwardMostMove];
10601 ChessSquare p = boards[forwardMostMove][toY][toX];
10602 // forwardMostMove++; // [HGM] bare: moved downstream
10604 if(kill2X >= 0) x = kill2X, y = kill2Y; else
10605 if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10606 (void) CoordsToAlgebraic(boards[forwardMostMove],
10607 PosFlags(forwardMostMove),
10608 fromY, fromX, y, x, (killX < 0)*promoChar,
10610 if(kill2X >= 0 && kill2Y >= 0)
10611 sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10612 if(killX >= 0 && killY >= 0)
10613 sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10614 toX + AAA, toY + ONE - '0', promoChar);
10616 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10617 int timeLeft; static int lastLoadFlag=0; int king, piece;
10618 piece = boards[forwardMostMove][fromY][fromX];
10619 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10620 if(gameInfo.variant == VariantKnightmate)
10621 king += (int) WhiteUnicorn - (int) WhiteKing;
10622 if(forwardMostMove == 0) {
10623 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10624 fprintf(serverMoves, "%s;", UserName());
10625 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10626 fprintf(serverMoves, "%s;", second.tidy);
10627 fprintf(serverMoves, "%s;", first.tidy);
10628 if(gameMode == MachinePlaysWhite)
10629 fprintf(serverMoves, "%s;", UserName());
10630 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10631 fprintf(serverMoves, "%s;", second.tidy);
10632 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10633 lastLoadFlag = loadFlag;
10635 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10636 // print castling suffix
10637 if( toY == fromY && piece == king ) {
10639 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10641 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10644 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10645 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
10646 boards[forwardMostMove][toY][toX] == EmptySquare
10647 && fromX != toX && fromY != toY)
10648 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10649 // promotion suffix
10650 if(promoChar != NULLCHAR) {
10651 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10652 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10653 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10654 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10657 char buf[MOVE_LEN*2], *p; int len;
10658 fprintf(serverMoves, "/%d/%d",
10659 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10660 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10661 else timeLeft = blackTimeRemaining/1000;
10662 fprintf(serverMoves, "/%d", timeLeft);
10663 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10664 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10665 if(p = strchr(buf, '=')) *p = NULLCHAR;
10666 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10667 fprintf(serverMoves, "/%s", buf);
10669 fflush(serverMoves);
10672 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10673 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10676 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10677 if (commentList[forwardMostMove+1] != NULL) {
10678 free(commentList[forwardMostMove+1]);
10679 commentList[forwardMostMove+1] = NULL;
10681 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10682 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10683 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10684 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10685 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10686 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10687 adjustedClock = FALSE;
10688 gameInfo.result = GameUnfinished;
10689 if (gameInfo.resultDetails != NULL) {
10690 free(gameInfo.resultDetails);
10691 gameInfo.resultDetails = NULL;
10693 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10694 moveList[forwardMostMove - 1]);
10695 mask = (WhiteOnMove(forwardMostMove) ? 0xFF : 0xFF00);
10696 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10702 if(boards[forwardMostMove][CHECK_COUNT]) boards[forwardMostMove][CHECK_COUNT] -= mask & 0x101;
10703 if(!boards[forwardMostMove][CHECK_COUNT] || boards[forwardMostMove][CHECK_COUNT] & mask) {
10704 if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[forwardMostMove - 1], "+");
10709 strcat(parseList[forwardMostMove - 1], "#");
10714 /* Updates currentMove if not pausing */
10716 ShowMove (int fromX, int fromY, int toX, int toY)
10718 int instant = (gameMode == PlayFromGameFile) ?
10719 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10720 if(appData.noGUI) return;
10721 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10723 if (forwardMostMove == currentMove + 1) {
10724 AnimateMove(boards[forwardMostMove - 1],
10725 fromX, fromY, toX, toY);
10728 currentMove = forwardMostMove;
10731 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10733 if (instant) return;
10735 DisplayMove(currentMove - 1);
10736 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10737 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10738 SetHighlights(fromX, fromY, toX, toY);
10741 DrawPosition(FALSE, boards[currentMove]);
10742 DisplayBothClocks();
10743 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10747 SendEgtPath (ChessProgramState *cps)
10748 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10749 char buf[MSG_SIZ], name[MSG_SIZ], *p;
10751 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10754 char c, *q = name+1, *r, *s;
10756 name[0] = ','; // extract next format name from feature and copy with prefixed ','
10757 while(*p && *p != ',') *q++ = *p++;
10758 *q++ = ':'; *q = 0;
10759 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10760 strcmp(name, ",nalimov:") == 0 ) {
10761 // take nalimov path from the menu-changeable option first, if it is defined
10762 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10763 SendToProgram(buf,cps); // send egtbpath command for nalimov
10765 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10766 (s = StrStr(appData.egtFormats, name)) != NULL) {
10767 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10768 s = r = StrStr(s, ":") + 1; // beginning of path info
10769 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10770 c = *r; *r = 0; // temporarily null-terminate path info
10771 *--q = 0; // strip of trailig ':' from name
10772 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10774 SendToProgram(buf,cps); // send egtbpath command for this format
10776 if(*p == ',') p++; // read away comma to position for next format name
10781 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10783 int width = 8, height = 8, holdings = 0; // most common sizes
10784 if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10785 // correct the deviations default for each variant
10786 if( v == VariantXiangqi ) width = 9, height = 10;
10787 if( v == VariantShogi ) width = 9, height = 9, holdings = 7;
10788 if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10789 if( v == VariantCapablanca || v == VariantCapaRandom ||
10790 v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10792 if( v == VariantCourier ) width = 12;
10793 if( v == VariantSuper ) holdings = 8;
10794 if( v == VariantGreat ) width = 10, holdings = 8;
10795 if( v == VariantSChess ) holdings = 7;
10796 if( v == VariantGrand ) width = 10, height = 10, holdings = 7;
10797 if( v == VariantChuChess) width = 10, height = 10;
10798 if( v == VariantChu ) width = 12, height = 12;
10799 return boardWidth >= 0 && boardWidth != width || // -1 is default,
10800 boardHeight >= 0 && boardHeight != height || // and thus by definition OK
10801 holdingsSize >= 0 && holdingsSize != holdings;
10804 char variantError[MSG_SIZ];
10807 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10808 { // returns error message (recognizable by upper-case) if engine does not support the variant
10809 char *p, *variant = VariantName(v);
10810 static char b[MSG_SIZ];
10811 if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10812 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10813 holdingsSize, variant); // cook up sized variant name
10814 /* [HGM] varsize: try first if this deviant size variant is specifically known */
10815 if(StrStr(list, b) == NULL) {
10816 // specific sized variant not known, check if general sizing allowed
10817 if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10818 snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10819 boardWidth, boardHeight, holdingsSize, engine);
10822 /* [HGM] here we really should compare with the maximum supported board size */
10824 } else snprintf(b, MSG_SIZ,"%s", variant);
10825 if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10826 p = StrStr(list, b);
10827 while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10829 // occurs not at all in list, or only as sub-string
10830 snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10831 if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10832 int l = strlen(variantError);
10834 while(p != list && p[-1] != ',') p--;
10835 q = strchr(p, ',');
10836 if(q) *q = NULLCHAR;
10837 snprintf(variantError + l, MSG_SIZ - l, _(", but %s is"), p);
10846 InitChessProgram (ChessProgramState *cps, int setup)
10847 /* setup needed to setup FRC opening position */
10849 char buf[MSG_SIZ], *b;
10850 if (appData.noChessProgram) return;
10851 hintRequested = FALSE;
10852 bookRequested = FALSE;
10854 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10855 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10856 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10857 if(cps->memSize) { /* [HGM] memory */
10858 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10859 SendToProgram(buf, cps);
10861 SendEgtPath(cps); /* [HGM] EGT */
10862 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10863 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10864 SendToProgram(buf, cps);
10867 setboardSpoiledMachineBlack = FALSE;
10868 SendToProgram(cps->initString, cps);
10869 if (gameInfo.variant != VariantNormal &&
10870 gameInfo.variant != VariantLoadable
10871 /* [HGM] also send variant if board size non-standard */
10872 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10874 b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10875 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10879 char c, *q = cps->variants, *p = strchr(q, ',');
10880 if(p) *p = NULLCHAR;
10881 v = StringToVariant(q);
10882 DisplayError(variantError, 0);
10883 if(v != VariantUnknown && cps == &first) {
10885 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10886 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10887 ASSIGN(appData.variant, q);
10888 Reset(TRUE, FALSE);
10894 snprintf(buf, MSG_SIZ, "variant %s\n", b);
10895 SendToProgram(buf, cps);
10897 currentlyInitializedVariant = gameInfo.variant;
10899 /* [HGM] send opening position in FRC to first engine */
10901 SendToProgram("force\n", cps);
10903 /* engine is now in force mode! Set flag to wake it up after first move. */
10904 setboardSpoiledMachineBlack = 1;
10907 if (cps->sendICS) {
10908 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10909 SendToProgram(buf, cps);
10911 cps->maybeThinking = FALSE;
10912 cps->offeredDraw = 0;
10913 if (!appData.icsActive) {
10914 SendTimeControl(cps, movesPerSession, timeControl,
10915 timeIncrement, appData.searchDepth,
10918 if (appData.showThinking
10919 // [HGM] thinking: four options require thinking output to be sent
10920 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10922 SendToProgram("post\n", cps);
10924 SendToProgram("hard\n", cps);
10925 if (!appData.ponderNextMove) {
10926 /* Warning: "easy" is a toggle in GNU Chess, so don't send
10927 it without being sure what state we are in first. "hard"
10928 is not a toggle, so that one is OK.
10930 SendToProgram("easy\n", cps);
10932 if (cps->usePing) {
10933 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10934 SendToProgram(buf, cps);
10936 cps->initDone = TRUE;
10937 ClearEngineOutputPane(cps == &second);
10942 ResendOptions (ChessProgramState *cps, int toEngine)
10943 { // send the stored value of the options
10945 static char buf2[MSG_SIZ*10];
10946 char buf[MSG_SIZ], *p = buf2;
10947 Option *opt = cps->option;
10949 for(i=0; i<cps->nrOptions; i++, opt++) {
10951 switch(opt->type) {
10955 if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
10956 snprintf(buf, MSG_SIZ, "%s=%d", opt->name, opt->value);
10959 if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
10960 snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->choice[opt->value]);
10963 if(strcmp(opt->textValue, opt->name + MSG_SIZ - 100))
10964 snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->textValue);
10972 snprintf(buf2, MSG_SIZ, "option %s\n", buf);
10973 SendToProgram(buf2, cps);
10975 if(p != buf2) *p++ = ',';
10976 strncpy(p, buf, 10*MSG_SIZ-1 - (p - buf2));
10985 StartChessProgram (ChessProgramState *cps)
10990 if (appData.noChessProgram) return;
10991 cps->initDone = FALSE;
10993 if (strcmp(cps->host, "localhost") == 0) {
10994 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10995 } else if (*appData.remoteShell == NULLCHAR) {
10996 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10998 if (*appData.remoteUser == NULLCHAR) {
10999 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
11002 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
11003 cps->host, appData.remoteUser, cps->program);
11005 err = StartChildProcess(buf, "", &cps->pr);
11009 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
11010 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
11011 if(cps != &first) return;
11012 appData.noChessProgram = TRUE;
11015 // DisplayFatalError(buf, err, 1);
11016 // cps->pr = NoProc;
11017 // cps->isr = NULL;
11021 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
11022 if (cps->protocolVersion > 1) {
11023 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
11024 if(!cps->reload) { // do not clear options when reloading because of -xreuse
11025 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
11026 cps->comboCnt = 0; // and values of combo boxes
11028 SendToProgram(buf, cps);
11029 if(cps->reload) ResendOptions(cps, TRUE);
11031 SendToProgram("xboard\n", cps);
11036 TwoMachinesEventIfReady P((void))
11038 static int curMess = 0;
11039 if (first.lastPing != first.lastPong) {
11040 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
11041 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11044 if (second.lastPing != second.lastPong) {
11045 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
11046 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11049 DisplayMessage("", ""); curMess = 0;
11050 TwoMachinesEvent();
11054 MakeName (char *template)
11058 static char buf[MSG_SIZ];
11062 clock = time((time_t *)NULL);
11063 tm = localtime(&clock);
11065 while(*p++ = *template++) if(p[-1] == '%') {
11066 switch(*template++) {
11067 case 0: *p = 0; return buf;
11068 case 'Y': i = tm->tm_year+1900; break;
11069 case 'y': i = tm->tm_year-100; break;
11070 case 'M': i = tm->tm_mon+1; break;
11071 case 'd': i = tm->tm_mday; break;
11072 case 'h': i = tm->tm_hour; break;
11073 case 'm': i = tm->tm_min; break;
11074 case 's': i = tm->tm_sec; break;
11077 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11083 CountPlayers (char *p)
11086 while(p = strchr(p, '\n')) p++, n++; // count participants
11091 WriteTourneyFile (char *results, FILE *f)
11092 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11093 if(f == NULL) f = fopen(appData.tourneyFile, "w");
11094 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11095 // create a file with tournament description
11096 fprintf(f, "-participants {%s}\n", appData.participants);
11097 fprintf(f, "-seedBase %d\n", appData.seedBase);
11098 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11099 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11100 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11101 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11102 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11103 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11104 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11105 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11106 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11107 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11108 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11109 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11110 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11111 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11112 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11113 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11114 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11115 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11116 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11117 fprintf(f, "-smpCores %d\n", appData.smpCores);
11119 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11121 fprintf(f, "-mps %d\n", appData.movesPerSession);
11122 fprintf(f, "-tc %s\n", appData.timeControl);
11123 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11125 fprintf(f, "-results \"%s\"\n", results);
11130 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11133 Substitute (char *participants, int expunge)
11135 int i, changed, changes=0, nPlayers=0;
11136 char *p, *q, *r, buf[MSG_SIZ];
11137 if(participants == NULL) return;
11138 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11139 r = p = participants; q = appData.participants;
11140 while(*p && *p == *q) {
11141 if(*p == '\n') r = p+1, nPlayers++;
11144 if(*p) { // difference
11145 while(*p && *p++ != '\n')
11147 while(*q && *q++ != '\n')
11149 changed = nPlayers;
11150 changes = 1 + (strcmp(p, q) != 0);
11152 if(changes == 1) { // a single engine mnemonic was changed
11153 q = r; while(*q) nPlayers += (*q++ == '\n');
11154 p = buf; while(*r && (*p = *r++) != '\n') p++;
11156 NamesToList(firstChessProgramNames, command, mnemonic, "all");
11157 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11158 if(mnemonic[i]) { // The substitute is valid
11160 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11161 flock(fileno(f), LOCK_EX);
11162 ParseArgsFromFile(f);
11163 fseek(f, 0, SEEK_SET);
11164 FREE(appData.participants); appData.participants = participants;
11165 if(expunge) { // erase results of replaced engine
11166 int len = strlen(appData.results), w, b, dummy;
11167 for(i=0; i<len; i++) {
11168 Pairing(i, nPlayers, &w, &b, &dummy);
11169 if((w == changed || b == changed) && appData.results[i] == '*') {
11170 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11175 for(i=0; i<len; i++) {
11176 Pairing(i, nPlayers, &w, &b, &dummy);
11177 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11180 WriteTourneyFile(appData.results, f);
11181 fclose(f); // release lock
11184 } else DisplayError(_("No engine with the name you gave is installed"), 0);
11186 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11187 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
11188 free(participants);
11193 CheckPlayers (char *participants)
11196 char buf[MSG_SIZ], *p;
11197 NamesToList(firstChessProgramNames, command, mnemonic, "all");
11198 while(p = strchr(participants, '\n')) {
11200 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11202 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11204 DisplayError(buf, 0);
11208 participants = p + 1;
11214 CreateTourney (char *name)
11217 if(matchMode && strcmp(name, appData.tourneyFile)) {
11218 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11220 if(name[0] == NULLCHAR) {
11221 if(appData.participants[0])
11222 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11225 f = fopen(name, "r");
11226 if(f) { // file exists
11227 ASSIGN(appData.tourneyFile, name);
11228 ParseArgsFromFile(f); // parse it
11230 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11231 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11232 DisplayError(_("Not enough participants"), 0);
11235 if(CheckPlayers(appData.participants)) return 0;
11236 ASSIGN(appData.tourneyFile, name);
11237 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11238 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11241 appData.noChessProgram = FALSE;
11242 appData.clockMode = TRUE;
11248 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11250 char buf[2*MSG_SIZ], *p, *q;
11251 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11252 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11253 skip = !all && group[0]; // if group requested, we start in skip mode
11254 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11255 p = names; q = buf; header = 0;
11256 while(*p && *p != '\n') *q++ = *p++;
11258 if(*p == '\n') p++;
11259 if(buf[0] == '#') {
11260 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11261 depth++; // we must be entering a new group
11262 if(all) continue; // suppress printing group headers when complete list requested
11264 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11266 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11267 if(engineList[i]) free(engineList[i]);
11268 engineList[i] = strdup(buf);
11269 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11270 if(engineMnemonic[i]) free(engineMnemonic[i]);
11271 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11273 sscanf(q + 8, "%s", buf + strlen(buf));
11276 engineMnemonic[i] = strdup(buf);
11279 engineList[i] = engineMnemonic[i] = NULL;
11284 SaveEngineSettings (int n)
11286 int len; char *p, *q, *s, buf[MSG_SIZ], *optionSettings;
11287 if(!currentEngine[n] || !currentEngine[n][0]) { DisplayMessage("saving failed: engine not from list", ""); return; } // no engine from list is loaded
11288 p = strstr(firstChessProgramNames, currentEngine[n]);
11289 if(!p) { DisplayMessage("saving failed: engine not found in list", ""); return; } // sanity check; engine could be deleted from list after loading
11290 optionSettings = ResendOptions(n ? &second : &first, FALSE);
11291 len = strlen(currentEngine[n]);
11292 q = p + len; *p = 0; // cut list into head and tail piece
11293 s = strstr(currentEngine[n], "firstOptions");
11294 if(s && (s[-1] == '-' || s[-1] == '/') && (s[12] == ' ' || s[12] == '=') && (s[13] == '"' || s[13] == '\'')) {
11296 while(*r && *r != s[13]) r++;
11297 s[14] = 0; // cut currentEngine into head and tail part, removing old settings
11298 snprintf(buf, MSG_SIZ, "%s%s%s", currentEngine[n], optionSettings, *r ? r : "\""); // synthesize new engine line
11299 } else if(*optionSettings) {
11300 snprintf(buf, MSG_SIZ, "%s -firstOptions \"%s\"", currentEngine[n], optionSettings);
11302 ASSIGN(currentEngine[n], buf); // updated engine line
11303 len = p - firstChessProgramNames + strlen(q) + strlen(currentEngine[n]) + 1;
11305 snprintf(s, len, "%s%s%s", firstChessProgramNames, currentEngine[n], q);
11306 FREE(firstChessProgramNames); firstChessProgramNames = s; // new list
11309 // following implemented as macro to avoid type limitations
11310 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11313 SwapEngines (int n)
11314 { // swap settings for first engine and other engine (so far only some selected options)
11319 SWAP(chessProgram, p)
11321 SWAP(hasOwnBookUCI, h)
11322 SWAP(protocolVersion, h)
11324 SWAP(scoreIsAbsolute, h)
11329 SWAP(engOptions, p)
11330 SWAP(engInitString, p)
11331 SWAP(computerString, p)
11333 SWAP(fenOverride, p)
11335 SWAP(accumulateTC, h)
11342 GetEngineLine (char *s, int n)
11346 extern char *icsNames;
11347 if(!s || !*s) return 0;
11348 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11349 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11350 if(!mnemonic[i]) return 0;
11351 if(n == 11) return 1; // just testing if there was a match
11352 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11353 if(n == 1) SwapEngines(n);
11354 ParseArgsFromString(buf);
11355 if(n == 1) SwapEngines(n);
11356 if(n < 2) { ASSIGN(currentEngine[n], command[i]); }
11357 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11358 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11359 ParseArgsFromString(buf);
11365 SetPlayer (int player, char *p)
11366 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
11368 char buf[MSG_SIZ], *engineName;
11369 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11370 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11371 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11373 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11374 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11375 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11376 ParseArgsFromString(buf);
11377 } else { // no engine with this nickname is installed!
11378 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11379 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11380 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11382 DisplayError(buf, 0);
11389 char *recentEngines;
11392 RecentEngineEvent (int nr)
11395 // SwapEngines(1); // bump first to second
11396 // ReplaceEngine(&second, 1); // and load it there
11397 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11398 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11399 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11400 ReplaceEngine(&first, 0);
11401 FloatToFront(&appData.recentEngineList, command[n]);
11406 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11407 { // determine players from game number
11408 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11410 if(appData.tourneyType == 0) {
11411 roundsPerCycle = (nPlayers - 1) | 1;
11412 pairingsPerRound = nPlayers / 2;
11413 } else if(appData.tourneyType > 0) {
11414 roundsPerCycle = nPlayers - appData.tourneyType;
11415 pairingsPerRound = appData.tourneyType;
11417 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11418 gamesPerCycle = gamesPerRound * roundsPerCycle;
11419 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11420 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11421 curRound = nr / gamesPerRound; nr %= gamesPerRound;
11422 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11423 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11424 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11426 if(appData.cycleSync) *syncInterval = gamesPerCycle;
11427 if(appData.roundSync) *syncInterval = gamesPerRound;
11429 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11431 if(appData.tourneyType == 0) {
11432 if(curPairing == (nPlayers-1)/2 ) {
11433 *whitePlayer = curRound;
11434 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11436 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11437 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11438 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11439 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11441 } else if(appData.tourneyType > 1) {
11442 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11443 *whitePlayer = curRound + appData.tourneyType;
11444 } else if(appData.tourneyType > 0) {
11445 *whitePlayer = curPairing;
11446 *blackPlayer = curRound + appData.tourneyType;
11449 // take care of white/black alternation per round.
11450 // For cycles and games this is already taken care of by default, derived from matchGame!
11451 return curRound & 1;
11455 NextTourneyGame (int nr, int *swapColors)
11456 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11458 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11460 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11461 tf = fopen(appData.tourneyFile, "r");
11462 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11463 ParseArgsFromFile(tf); fclose(tf);
11464 InitTimeControls(); // TC might be altered from tourney file
11466 nPlayers = CountPlayers(appData.participants); // count participants
11467 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11468 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11471 p = q = appData.results;
11472 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11473 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11474 DisplayMessage(_("Waiting for other game(s)"),"");
11475 waitingForGame = TRUE;
11476 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11479 waitingForGame = FALSE;
11482 if(appData.tourneyType < 0) {
11483 if(nr>=0 && !pairingReceived) {
11485 if(pairing.pr == NoProc) {
11486 if(!appData.pairingEngine[0]) {
11487 DisplayFatalError(_("No pairing engine specified"), 0, 1);
11490 StartChessProgram(&pairing); // starts the pairing engine
11492 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11493 SendToProgram(buf, &pairing);
11494 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11495 SendToProgram(buf, &pairing);
11496 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11498 pairingReceived = 0; // ... so we continue here
11500 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11501 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11502 matchGame = 1; roundNr = nr / syncInterval + 1;
11505 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11507 // redefine engines, engine dir, etc.
11508 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11509 if(first.pr == NoProc) {
11510 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11511 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
11513 if(second.pr == NoProc) {
11515 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11516 SwapEngines(1); // and make that valid for second engine by swapping
11517 InitEngine(&second, 1);
11519 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
11520 UpdateLogos(FALSE); // leave display to ModeHiglight()
11526 { // performs game initialization that does not invoke engines, and then tries to start the game
11527 int res, firstWhite, swapColors = 0;
11528 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11529 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
11531 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11532 if(strcmp(buf, currentDebugFile)) { // name has changed
11533 FILE *f = fopen(buf, "w");
11534 if(f) { // if opening the new file failed, just keep using the old one
11535 ASSIGN(currentDebugFile, buf);
11539 if(appData.serverFileName) {
11540 if(serverFP) fclose(serverFP);
11541 serverFP = fopen(appData.serverFileName, "w");
11542 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11543 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11547 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11548 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11549 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
11550 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11551 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11552 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11553 Reset(FALSE, first.pr != NoProc);
11554 res = LoadGameOrPosition(matchGame); // setup game
11555 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11556 if(!res) return; // abort when bad game/pos file
11557 if(appData.epd) {// in EPD mode we make sure first engine is to move
11558 firstWhite = !(forwardMostMove & 1);
11559 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
11560 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11562 TwoMachinesEvent();
11566 UserAdjudicationEvent (int result)
11568 ChessMove gameResult = GameIsDrawn;
11571 gameResult = WhiteWins;
11573 else if( result < 0 ) {
11574 gameResult = BlackWins;
11577 if( gameMode == TwoMachinesPlay ) {
11578 GameEnds( gameResult, "User adjudication", GE_XBOARD );
11583 // [HGM] save: calculate checksum of game to make games easily identifiable
11585 StringCheckSum (char *s)
11588 if(s==NULL) return 0;
11589 while(*s) i = i*259 + *s++;
11597 for(i=backwardMostMove; i<forwardMostMove; i++) {
11598 sum += pvInfoList[i].depth;
11599 sum += StringCheckSum(parseList[i]);
11600 sum += StringCheckSum(commentList[i]);
11603 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11604 return sum + StringCheckSum(commentList[i]);
11605 } // end of save patch
11608 GameEnds (ChessMove result, char *resultDetails, int whosays)
11610 GameMode nextGameMode;
11612 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11614 if(endingGame) return; /* [HGM] crash: forbid recursion */
11616 if(twoBoards) { // [HGM] dual: switch back to one board
11617 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11618 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11620 if (appData.debugMode) {
11621 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11622 result, resultDetails ? resultDetails : "(null)", whosays);
11625 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11627 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11629 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11630 /* If we are playing on ICS, the server decides when the
11631 game is over, but the engine can offer to draw, claim
11635 if (appData.zippyPlay && first.initDone) {
11636 if (result == GameIsDrawn) {
11637 /* In case draw still needs to be claimed */
11638 SendToICS(ics_prefix);
11639 SendToICS("draw\n");
11640 } else if (StrCaseStr(resultDetails, "resign")) {
11641 SendToICS(ics_prefix);
11642 SendToICS("resign\n");
11646 endingGame = 0; /* [HGM] crash */
11650 /* If we're loading the game from a file, stop */
11651 if (whosays == GE_FILE) {
11652 (void) StopLoadGameTimer();
11656 /* Cancel draw offers */
11657 first.offeredDraw = second.offeredDraw = 0;
11659 /* If this is an ICS game, only ICS can really say it's done;
11660 if not, anyone can. */
11661 isIcsGame = (gameMode == IcsPlayingWhite ||
11662 gameMode == IcsPlayingBlack ||
11663 gameMode == IcsObserving ||
11664 gameMode == IcsExamining);
11666 if (!isIcsGame || whosays == GE_ICS) {
11667 /* OK -- not an ICS game, or ICS said it was done */
11669 if (!isIcsGame && !appData.noChessProgram)
11670 SetUserThinkingEnables();
11672 /* [HGM] if a machine claims the game end we verify this claim */
11673 if(gameMode == TwoMachinesPlay && appData.testClaims) {
11674 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11676 ChessMove trueResult = (ChessMove) -1;
11678 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
11679 first.twoMachinesColor[0] :
11680 second.twoMachinesColor[0] ;
11682 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11683 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11684 /* [HGM] verify: engine mate claims accepted if they were flagged */
11685 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11687 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11688 /* [HGM] verify: engine mate claims accepted if they were flagged */
11689 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11691 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11692 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11695 // now verify win claims, but not in drop games, as we don't understand those yet
11696 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11697 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11698 (result == WhiteWins && claimer == 'w' ||
11699 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
11700 if (appData.debugMode) {
11701 fprintf(debugFP, "result=%d sp=%d move=%d\n",
11702 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11704 if(result != trueResult) {
11705 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11706 result = claimer == 'w' ? BlackWins : WhiteWins;
11707 resultDetails = buf;
11710 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11711 && (forwardMostMove <= backwardMostMove ||
11712 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11713 (claimer=='b')==(forwardMostMove&1))
11715 /* [HGM] verify: draws that were not flagged are false claims */
11716 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11717 result = claimer == 'w' ? BlackWins : WhiteWins;
11718 resultDetails = buf;
11720 /* (Claiming a loss is accepted no questions asked!) */
11721 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11722 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11723 result = GameUnfinished;
11724 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11726 /* [HGM] bare: don't allow bare King to win */
11727 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11728 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11729 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11730 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11731 && result != GameIsDrawn)
11732 { int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11733 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11734 int p = (int)boards[forwardMostMove][i][j] - color;
11735 if(p >= 0 && p <= (int)WhiteKing) k++;
11736 oppoKings += (p + color == WhiteKing + BlackPawn - color);
11738 if (appData.debugMode) {
11739 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11740 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11742 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11743 result = GameIsDrawn;
11744 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11745 resultDetails = buf;
11751 if(serverMoves != NULL && !loadFlag) { char c = '=';
11752 if(result==WhiteWins) c = '+';
11753 if(result==BlackWins) c = '-';
11754 if(resultDetails != NULL)
11755 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11757 if (resultDetails != NULL) {
11758 gameInfo.result = result;
11759 gameInfo.resultDetails = StrSave(resultDetails);
11761 /* display last move only if game was not loaded from file */
11762 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11763 DisplayMove(currentMove - 1);
11765 if (forwardMostMove != 0) {
11766 if (gameMode != PlayFromGameFile && gameMode != EditGame
11767 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11769 if (*appData.saveGameFile != NULLCHAR) {
11770 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11771 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11773 SaveGameToFile(appData.saveGameFile, TRUE);
11774 } else if (appData.autoSaveGames) {
11775 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11777 if (*appData.savePositionFile != NULLCHAR) {
11778 SavePositionToFile(appData.savePositionFile);
11780 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11784 /* Tell program how game ended in case it is learning */
11785 /* [HGM] Moved this to after saving the PGN, just in case */
11786 /* engine died and we got here through time loss. In that */
11787 /* case we will get a fatal error writing the pipe, which */
11788 /* would otherwise lose us the PGN. */
11789 /* [HGM] crash: not needed anymore, but doesn't hurt; */
11790 /* output during GameEnds should never be fatal anymore */
11791 if (gameMode == MachinePlaysWhite ||
11792 gameMode == MachinePlaysBlack ||
11793 gameMode == TwoMachinesPlay ||
11794 gameMode == IcsPlayingWhite ||
11795 gameMode == IcsPlayingBlack ||
11796 gameMode == BeginningOfGame) {
11798 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11800 if (first.pr != NoProc) {
11801 SendToProgram(buf, &first);
11803 if (second.pr != NoProc &&
11804 gameMode == TwoMachinesPlay) {
11805 SendToProgram(buf, &second);
11810 if (appData.icsActive) {
11811 if (appData.quietPlay &&
11812 (gameMode == IcsPlayingWhite ||
11813 gameMode == IcsPlayingBlack)) {
11814 SendToICS(ics_prefix);
11815 SendToICS("set shout 1\n");
11817 nextGameMode = IcsIdle;
11818 ics_user_moved = FALSE;
11819 /* clean up premove. It's ugly when the game has ended and the
11820 * premove highlights are still on the board.
11823 gotPremove = FALSE;
11824 ClearPremoveHighlights();
11825 DrawPosition(FALSE, boards[currentMove]);
11827 if (whosays == GE_ICS) {
11830 if (gameMode == IcsPlayingWhite)
11832 else if(gameMode == IcsPlayingBlack)
11833 PlayIcsLossSound();
11836 if (gameMode == IcsPlayingBlack)
11838 else if(gameMode == IcsPlayingWhite)
11839 PlayIcsLossSound();
11842 PlayIcsDrawSound();
11845 PlayIcsUnfinishedSound();
11848 if(appData.quitNext) { ExitEvent(0); return; }
11849 } else if (gameMode == EditGame ||
11850 gameMode == PlayFromGameFile ||
11851 gameMode == AnalyzeMode ||
11852 gameMode == AnalyzeFile) {
11853 nextGameMode = gameMode;
11855 nextGameMode = EndOfGame;
11860 nextGameMode = gameMode;
11863 if (appData.noChessProgram) {
11864 gameMode = nextGameMode;
11866 endingGame = 0; /* [HGM] crash */
11871 /* Put first chess program into idle state */
11872 if (first.pr != NoProc &&
11873 (gameMode == MachinePlaysWhite ||
11874 gameMode == MachinePlaysBlack ||
11875 gameMode == TwoMachinesPlay ||
11876 gameMode == IcsPlayingWhite ||
11877 gameMode == IcsPlayingBlack ||
11878 gameMode == BeginningOfGame)) {
11879 SendToProgram("force\n", &first);
11880 if (first.usePing) {
11882 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11883 SendToProgram(buf, &first);
11886 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11887 /* Kill off first chess program */
11888 if (first.isr != NULL)
11889 RemoveInputSource(first.isr);
11892 if (first.pr != NoProc) {
11894 DoSleep( appData.delayBeforeQuit );
11895 SendToProgram("quit\n", &first);
11896 DestroyChildProcess(first.pr, 4 + first.useSigterm);
11897 first.reload = TRUE;
11901 if (second.reuse) {
11902 /* Put second chess program into idle state */
11903 if (second.pr != NoProc &&
11904 gameMode == TwoMachinesPlay) {
11905 SendToProgram("force\n", &second);
11906 if (second.usePing) {
11908 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11909 SendToProgram(buf, &second);
11912 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11913 /* Kill off second chess program */
11914 if (second.isr != NULL)
11915 RemoveInputSource(second.isr);
11918 if (second.pr != NoProc) {
11919 DoSleep( appData.delayBeforeQuit );
11920 SendToProgram("quit\n", &second);
11921 DestroyChildProcess(second.pr, 4 + second.useSigterm);
11922 second.reload = TRUE;
11924 second.pr = NoProc;
11927 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11928 char resChar = '=';
11932 if (first.twoMachinesColor[0] == 'w') {
11935 second.matchWins++;
11940 if (first.twoMachinesColor[0] == 'b') {
11943 second.matchWins++;
11946 case GameUnfinished:
11952 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11953 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11954 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11955 ReserveGame(nextGame, resChar); // sets nextGame
11956 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11957 else ranking = strdup("busy"); //suppress popup when aborted but not finished
11958 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11960 if (nextGame <= appData.matchGames && !abortMatch) {
11961 gameMode = nextGameMode;
11962 matchGame = nextGame; // this will be overruled in tourney mode!
11963 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11964 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11965 endingGame = 0; /* [HGM] crash */
11968 gameMode = nextGameMode;
11970 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
11971 OutputKibitz(2, buf);
11972 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
11973 OutputKibitz(2, buf);
11974 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
11975 if(second.matchWins) OutputKibitz(2, buf);
11976 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
11977 OutputKibitz(2, buf);
11979 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11980 first.tidy, second.tidy,
11981 first.matchWins, second.matchWins,
11982 appData.matchGames - (first.matchWins + second.matchWins));
11983 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11984 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11985 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11986 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11987 first.twoMachinesColor = "black\n";
11988 second.twoMachinesColor = "white\n";
11990 first.twoMachinesColor = "white\n";
11991 second.twoMachinesColor = "black\n";
11995 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11996 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11998 gameMode = nextGameMode;
12000 endingGame = 0; /* [HGM] crash */
12001 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
12002 if(matchMode == TRUE) { // match through command line: exit with or without popup
12004 ToNrEvent(forwardMostMove);
12005 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
12007 } else DisplayFatalError(buf, 0, 0);
12008 } else { // match through menu; just stop, with or without popup
12009 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
12012 if(strcmp(ranking, "busy")) DisplayNote(ranking);
12013 } else DisplayNote(buf);
12015 if(ranking) free(ranking);
12019 /* Assumes program was just initialized (initString sent).
12020 Leaves program in force mode. */
12022 FeedMovesToProgram (ChessProgramState *cps, int upto)
12026 if (appData.debugMode)
12027 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
12028 startedFromSetupPosition ? "position and " : "",
12029 backwardMostMove, upto, cps->which);
12030 if(currentlyInitializedVariant != gameInfo.variant) {
12032 // [HGM] variantswitch: make engine aware of new variant
12033 if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
12034 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
12035 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
12036 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
12037 SendToProgram(buf, cps);
12038 currentlyInitializedVariant = gameInfo.variant;
12040 SendToProgram("force\n", cps);
12041 if (startedFromSetupPosition) {
12042 SendBoard(cps, backwardMostMove);
12043 if (appData.debugMode) {
12044 fprintf(debugFP, "feedMoves\n");
12047 for (i = backwardMostMove; i < upto; i++) {
12048 SendMoveToProgram(i, cps);
12054 ResurrectChessProgram ()
12056 /* The chess program may have exited.
12057 If so, restart it and feed it all the moves made so far. */
12058 static int doInit = 0;
12060 if (appData.noChessProgram) return 1;
12062 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
12063 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
12064 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
12065 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
12067 if (first.pr != NoProc) return 1;
12068 StartChessProgram(&first);
12070 InitChessProgram(&first, FALSE);
12071 FeedMovesToProgram(&first, currentMove);
12073 if (!first.sendTime) {
12074 /* can't tell gnuchess what its clock should read,
12075 so we bow to its notion. */
12077 timeRemaining[0][currentMove] = whiteTimeRemaining;
12078 timeRemaining[1][currentMove] = blackTimeRemaining;
12081 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
12082 appData.icsEngineAnalyze) && first.analysisSupport) {
12083 SendToProgram("analyze\n", &first);
12084 first.analyzing = TRUE;
12090 * Button procedures
12093 Reset (int redraw, int init)
12097 if (appData.debugMode) {
12098 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12099 redraw, init, gameMode);
12101 pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12102 deadRanks = 0; // assume entire board is used
12103 for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12104 CleanupTail(); // [HGM] vari: delete any stored variations
12105 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12106 pausing = pauseExamInvalid = FALSE;
12107 startedFromSetupPosition = blackPlaysFirst = FALSE;
12109 whiteFlag = blackFlag = FALSE;
12110 userOfferedDraw = FALSE;
12111 hintRequested = bookRequested = FALSE;
12112 first.maybeThinking = FALSE;
12113 second.maybeThinking = FALSE;
12114 first.bookSuspend = FALSE; // [HGM] book
12115 second.bookSuspend = FALSE;
12116 thinkOutput[0] = NULLCHAR;
12117 lastHint[0] = NULLCHAR;
12118 ClearGameInfo(&gameInfo);
12119 gameInfo.variant = StringToVariant(appData.variant);
12120 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12121 gameInfo.variant = VariantUnknown;
12122 strncpy(engineVariant, appData.variant, MSG_SIZ);
12124 ics_user_moved = ics_clock_paused = FALSE;
12125 ics_getting_history = H_FALSE;
12127 white_holding[0] = black_holding[0] = NULLCHAR;
12128 ClearProgramStats();
12129 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12133 flipView = appData.flipView;
12134 ClearPremoveHighlights();
12135 gotPremove = FALSE;
12136 alarmSounded = FALSE;
12137 killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12139 GameEnds(EndOfFile, NULL, GE_PLAYER);
12140 if(appData.serverMovesName != NULL) {
12141 /* [HGM] prepare to make moves file for broadcasting */
12142 clock_t t = clock();
12143 if(serverMoves != NULL) fclose(serverMoves);
12144 serverMoves = fopen(appData.serverMovesName, "r");
12145 if(serverMoves != NULL) {
12146 fclose(serverMoves);
12147 /* delay 15 sec before overwriting, so all clients can see end */
12148 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12150 serverMoves = fopen(appData.serverMovesName, "w");
12154 gameMode = BeginningOfGame;
12156 if(appData.icsActive) gameInfo.variant = VariantNormal;
12157 currentMove = forwardMostMove = backwardMostMove = 0;
12158 MarkTargetSquares(1);
12159 InitPosition(redraw);
12160 for (i = 0; i < MAX_MOVES; i++) {
12161 if (commentList[i] != NULL) {
12162 free(commentList[i]);
12163 commentList[i] = NULL;
12167 timeRemaining[0][0] = whiteTimeRemaining;
12168 timeRemaining[1][0] = blackTimeRemaining;
12170 if (first.pr == NoProc) {
12171 StartChessProgram(&first);
12174 InitChessProgram(&first, startedFromSetupPosition);
12177 DisplayMessage("", "");
12178 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12179 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12180 ClearMap(); // [HGM] exclude: invalidate map
12184 AutoPlayGameLoop ()
12187 if (!AutoPlayOneMove())
12189 if (matchMode || appData.timeDelay == 0)
12191 if (appData.timeDelay < 0)
12193 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12201 ReloadGame(1); // next game
12207 int fromX, fromY, toX, toY;
12209 if (appData.debugMode) {
12210 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12213 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12216 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12217 pvInfoList[currentMove].depth = programStats.depth;
12218 pvInfoList[currentMove].score = programStats.score;
12219 pvInfoList[currentMove].time = 0;
12220 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12221 else { // append analysis of final position as comment
12223 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12224 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12226 programStats.depth = 0;
12229 if (currentMove >= forwardMostMove) {
12230 if(gameMode == AnalyzeFile) {
12231 if(appData.loadGameIndex == -1) {
12232 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12233 ScheduleDelayedEvent(AnalyzeNextGame, 10);
12235 ExitAnalyzeMode(); SendToProgram("force\n", &first);
12238 // gameMode = EndOfGame;
12239 // ModeHighlight();
12241 /* [AS] Clear current move marker at the end of a game */
12242 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12247 toX = moveList[currentMove][2] - AAA;
12248 toY = moveList[currentMove][3] - ONE;
12250 if (moveList[currentMove][1] == '@') {
12251 if (appData.highlightLastMove) {
12252 SetHighlights(-1, -1, toX, toY);
12255 fromX = moveList[currentMove][0] - AAA;
12256 fromY = moveList[currentMove][1] - ONE;
12258 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12260 if(moveList[currentMove][4] == ';') { // multi-leg
12261 killX = moveList[currentMove][5] - AAA;
12262 killY = moveList[currentMove][6] - ONE;
12264 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12265 killX = killY = -1;
12267 if (appData.highlightLastMove) {
12268 SetHighlights(fromX, fromY, toX, toY);
12271 DisplayMove(currentMove);
12272 SendMoveToProgram(currentMove++, &first);
12273 DisplayBothClocks();
12274 DrawPosition(FALSE, boards[currentMove]);
12275 // [HGM] PV info: always display, routine tests if empty
12276 DisplayComment(currentMove - 1, commentList[currentMove]);
12282 LoadGameOneMove (ChessMove readAhead)
12284 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12285 char promoChar = NULLCHAR;
12286 ChessMove moveType;
12287 char move[MSG_SIZ];
12290 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12291 gameMode != AnalyzeMode && gameMode != Training) {
12296 yyboardindex = forwardMostMove;
12297 if (readAhead != EndOfFile) {
12298 moveType = readAhead;
12300 if (gameFileFP == NULL)
12302 moveType = (ChessMove) Myylex();
12306 switch (moveType) {
12308 if (appData.debugMode)
12309 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12312 /* append the comment but don't display it */
12313 AppendComment(currentMove, p, FALSE);
12316 case WhiteCapturesEnPassant:
12317 case BlackCapturesEnPassant:
12318 case WhitePromotion:
12319 case BlackPromotion:
12320 case WhiteNonPromotion:
12321 case BlackNonPromotion:
12324 case WhiteKingSideCastle:
12325 case WhiteQueenSideCastle:
12326 case BlackKingSideCastle:
12327 case BlackQueenSideCastle:
12328 case WhiteKingSideCastleWild:
12329 case WhiteQueenSideCastleWild:
12330 case BlackKingSideCastleWild:
12331 case BlackQueenSideCastleWild:
12333 case WhiteHSideCastleFR:
12334 case WhiteASideCastleFR:
12335 case BlackHSideCastleFR:
12336 case BlackASideCastleFR:
12338 if (appData.debugMode)
12339 fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12340 fromX = currentMoveString[0] - AAA;
12341 fromY = currentMoveString[1] - ONE;
12342 toX = currentMoveString[2] - AAA;
12343 toY = currentMoveString[3] - ONE;
12344 promoChar = currentMoveString[4];
12345 if(promoChar == ';') promoChar = currentMoveString[7];
12350 if (appData.debugMode)
12351 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12352 fromX = moveType == WhiteDrop ?
12353 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12354 (int) CharToPiece(ToLower(currentMoveString[0]));
12356 toX = currentMoveString[2] - AAA;
12357 toY = currentMoveString[3] - ONE;
12363 case GameUnfinished:
12364 if (appData.debugMode)
12365 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12366 p = strchr(yy_text, '{');
12367 if (p == NULL) p = strchr(yy_text, '(');
12370 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12372 q = strchr(p, *p == '{' ? '}' : ')');
12373 if (q != NULL) *q = NULLCHAR;
12376 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12377 GameEnds(moveType, p, GE_FILE);
12379 if (cmailMsgLoaded) {
12381 flipView = WhiteOnMove(currentMove);
12382 if (moveType == GameUnfinished) flipView = !flipView;
12383 if (appData.debugMode)
12384 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12389 if (appData.debugMode)
12390 fprintf(debugFP, "Parser hit end of file\n");
12391 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12397 if (WhiteOnMove(currentMove)) {
12398 GameEnds(BlackWins, "Black mates", GE_FILE);
12400 GameEnds(WhiteWins, "White mates", GE_FILE);
12404 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12410 case MoveNumberOne:
12411 if (lastLoadGameStart == GNUChessGame) {
12412 /* GNUChessGames have numbers, but they aren't move numbers */
12413 if (appData.debugMode)
12414 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12415 yy_text, (int) moveType);
12416 return LoadGameOneMove(EndOfFile); /* tail recursion */
12418 /* else fall thru */
12423 /* Reached start of next game in file */
12424 if (appData.debugMode)
12425 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12426 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12432 if (WhiteOnMove(currentMove)) {
12433 GameEnds(BlackWins, "Black mates", GE_FILE);
12435 GameEnds(WhiteWins, "White mates", GE_FILE);
12439 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12445 case PositionDiagram: /* should not happen; ignore */
12446 case ElapsedTime: /* ignore */
12447 case NAG: /* ignore */
12448 if (appData.debugMode)
12449 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12450 yy_text, (int) moveType);
12451 return LoadGameOneMove(EndOfFile); /* tail recursion */
12454 if (appData.testLegality) {
12455 if (appData.debugMode)
12456 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12457 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12458 (forwardMostMove / 2) + 1,
12459 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12460 DisplayError(move, 0);
12463 if (appData.debugMode)
12464 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12465 yy_text, currentMoveString);
12466 if(currentMoveString[1] == '@') {
12467 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12470 fromX = currentMoveString[0] - AAA;
12471 fromY = currentMoveString[1] - ONE;
12473 toX = currentMoveString[2] - AAA;
12474 toY = currentMoveString[3] - ONE;
12475 promoChar = currentMoveString[4];
12479 case AmbiguousMove:
12480 if (appData.debugMode)
12481 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12482 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12483 (forwardMostMove / 2) + 1,
12484 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12485 DisplayError(move, 0);
12490 case ImpossibleMove:
12491 if (appData.debugMode)
12492 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12493 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12494 (forwardMostMove / 2) + 1,
12495 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12496 DisplayError(move, 0);
12502 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12503 DrawPosition(FALSE, boards[currentMove]);
12504 DisplayBothClocks();
12505 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12506 DisplayComment(currentMove - 1, commentList[currentMove]);
12508 (void) StopLoadGameTimer();
12510 cmailOldMove = forwardMostMove;
12513 /* currentMoveString is set as a side-effect of yylex */
12515 thinkOutput[0] = NULLCHAR;
12516 MakeMove(fromX, fromY, toX, toY, promoChar);
12517 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12518 currentMove = forwardMostMove;
12523 /* Load the nth game from the given file */
12525 LoadGameFromFile (char *filename, int n, char *title, int useList)
12530 if (strcmp(filename, "-") == 0) {
12534 f = fopen(filename, "rb");
12536 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12537 DisplayError(buf, errno);
12541 if (fseek(f, 0, 0) == -1) {
12542 /* f is not seekable; probably a pipe */
12545 if (useList && n == 0) {
12546 int error = GameListBuild(f);
12548 DisplayError(_("Cannot build game list"), error);
12549 } else if (!ListEmpty(&gameList) &&
12550 ((ListGame *) gameList.tailPred)->number > 1) {
12551 GameListPopUp(f, title);
12558 return LoadGame(f, n, title, FALSE);
12563 MakeRegisteredMove ()
12565 int fromX, fromY, toX, toY;
12567 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12568 switch (cmailMoveType[lastLoadGameNumber - 1]) {
12571 if (appData.debugMode)
12572 fprintf(debugFP, "Restoring %s for game %d\n",
12573 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12575 thinkOutput[0] = NULLCHAR;
12576 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12577 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12578 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12579 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12580 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12581 promoChar = cmailMove[lastLoadGameNumber - 1][4];
12582 MakeMove(fromX, fromY, toX, toY, promoChar);
12583 ShowMove(fromX, fromY, toX, toY);
12585 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12592 if (WhiteOnMove(currentMove)) {
12593 GameEnds(BlackWins, "Black mates", GE_PLAYER);
12595 GameEnds(WhiteWins, "White mates", GE_PLAYER);
12600 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12607 if (WhiteOnMove(currentMove)) {
12608 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12610 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12615 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12626 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12628 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12632 if (gameNumber > nCmailGames) {
12633 DisplayError(_("No more games in this message"), 0);
12636 if (f == lastLoadGameFP) {
12637 int offset = gameNumber - lastLoadGameNumber;
12639 cmailMsg[0] = NULLCHAR;
12640 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12641 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12642 nCmailMovesRegistered--;
12644 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12645 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12646 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12649 if (! RegisterMove()) return FALSE;
12653 retVal = LoadGame(f, gameNumber, title, useList);
12655 /* Make move registered during previous look at this game, if any */
12656 MakeRegisteredMove();
12658 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12659 commentList[currentMove]
12660 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12661 DisplayComment(currentMove - 1, commentList[currentMove]);
12667 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12669 ReloadGame (int offset)
12671 int gameNumber = lastLoadGameNumber + offset;
12672 if (lastLoadGameFP == NULL) {
12673 DisplayError(_("No game has been loaded yet"), 0);
12676 if (gameNumber <= 0) {
12677 DisplayError(_("Can't back up any further"), 0);
12680 if (cmailMsgLoaded) {
12681 return CmailLoadGame(lastLoadGameFP, gameNumber,
12682 lastLoadGameTitle, lastLoadGameUseList);
12684 return LoadGame(lastLoadGameFP, gameNumber,
12685 lastLoadGameTitle, lastLoadGameUseList);
12689 int keys[EmptySquare+1];
12692 PositionMatches (Board b1, Board b2)
12695 switch(appData.searchMode) {
12696 case 1: return CompareWithRights(b1, b2);
12698 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12699 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12703 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12704 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12705 sum += keys[b1[r][f]] - keys[b2[r][f]];
12709 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12710 sum += keys[b1[r][f]] - keys[b2[r][f]];
12722 int pieceList[256], quickBoard[256];
12723 ChessSquare pieceType[256] = { EmptySquare };
12724 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12725 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12726 int soughtTotal, turn;
12727 Boolean epOK, flipSearch;
12730 unsigned char piece, to;
12733 #define DSIZE (250000)
12735 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12736 Move *moveDatabase = initialSpace;
12737 unsigned int movePtr, dataSize = DSIZE;
12740 MakePieceList (Board board, int *counts)
12742 int r, f, n=Q_PROMO, total=0;
12743 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12744 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12745 int sq = f + (r<<4);
12746 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12747 quickBoard[sq] = ++n;
12749 pieceType[n] = board[r][f];
12750 counts[board[r][f]]++;
12751 if(board[r][f] == WhiteKing) pieceList[1] = n; else
12752 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12756 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12761 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12763 int sq = fromX + (fromY<<4);
12764 int piece = quickBoard[sq], rook;
12765 quickBoard[sq] = 0;
12766 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12767 if(piece == pieceList[1] && fromY == toY) {
12768 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12769 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12770 moveDatabase[movePtr++].piece = Q_WCASTL;
12771 quickBoard[sq] = piece;
12772 piece = quickBoard[from]; quickBoard[from] = 0;
12773 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12774 } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12775 quickBoard[sq] = 0; // remove Rook
12776 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12777 moveDatabase[movePtr++].piece = Q_WCASTL;
12778 quickBoard[sq] = pieceList[1]; // put King
12780 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12783 if(piece == pieceList[2] && fromY == toY) {
12784 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12785 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12786 moveDatabase[movePtr++].piece = Q_BCASTL;
12787 quickBoard[sq] = piece;
12788 piece = quickBoard[from]; quickBoard[from] = 0;
12789 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12790 } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12791 quickBoard[sq] = 0; // remove Rook
12792 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12793 moveDatabase[movePtr++].piece = Q_BCASTL;
12794 quickBoard[sq] = pieceList[2]; // put King
12796 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12799 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12800 quickBoard[(fromY<<4)+toX] = 0;
12801 moveDatabase[movePtr].piece = Q_EP;
12802 moveDatabase[movePtr++].to = (fromY<<4)+toX;
12803 moveDatabase[movePtr].to = sq;
12805 if(promoPiece != pieceType[piece]) {
12806 moveDatabase[movePtr++].piece = Q_PROMO;
12807 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12809 moveDatabase[movePtr].piece = piece;
12810 quickBoard[sq] = piece;
12815 PackGame (Board board)
12817 Move *newSpace = NULL;
12818 moveDatabase[movePtr].piece = 0; // terminate previous game
12819 if(movePtr > dataSize) {
12820 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12821 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12822 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12825 Move *p = moveDatabase, *q = newSpace;
12826 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
12827 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12828 moveDatabase = newSpace;
12829 } else { // calloc failed, we must be out of memory. Too bad...
12830 dataSize = 0; // prevent calloc events for all subsequent games
12831 return 0; // and signal this one isn't cached
12835 MakePieceList(board, counts);
12840 QuickCompare (Board board, int *minCounts, int *maxCounts)
12841 { // compare according to search mode
12843 switch(appData.searchMode)
12845 case 1: // exact position match
12846 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12847 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12848 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12851 case 2: // can have extra material on empty squares
12852 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12853 if(board[r][f] == EmptySquare) continue;
12854 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12857 case 3: // material with exact Pawn structure
12858 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12859 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12860 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12861 } // fall through to material comparison
12862 case 4: // exact material
12863 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12865 case 6: // material range with given imbalance
12866 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12867 // fall through to range comparison
12868 case 5: // material range
12869 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12875 QuickScan (Board board, Move *move)
12876 { // reconstruct game,and compare all positions in it
12877 int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12879 int piece = move->piece;
12880 int to = move->to, from = pieceList[piece];
12881 if(found < 0) { // if already found just scan to game end for final piece count
12882 if(QuickCompare(soughtBoard, minSought, maxSought) ||
12883 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12884 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12885 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12887 static int lastCounts[EmptySquare+1];
12889 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12890 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12891 } else stretch = 0;
12892 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12893 if(found >= 0 && !appData.minPieces) return found;
12895 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12896 if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12897 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12898 piece = (++move)->piece;
12899 from = pieceList[piece];
12900 counts[pieceType[piece]]--;
12901 pieceType[piece] = (ChessSquare) move->to;
12902 counts[move->to]++;
12903 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12904 counts[pieceType[quickBoard[to]]]--;
12905 quickBoard[to] = 0; total--;
12908 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12909 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12910 from = pieceList[piece]; // so this must be King
12911 quickBoard[from] = 0;
12912 pieceList[piece] = to;
12913 from = pieceList[(++move)->piece]; // for FRC this has to be done here
12914 quickBoard[from] = 0; // rook
12915 quickBoard[to] = piece;
12916 to = move->to; piece = move->piece;
12920 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12921 if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12922 quickBoard[from] = 0;
12924 quickBoard[to] = piece;
12925 pieceList[piece] = to;
12935 flipSearch = FALSE;
12936 CopyBoard(soughtBoard, boards[currentMove]);
12937 soughtTotal = MakePieceList(soughtBoard, maxSought);
12938 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12939 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12940 CopyBoard(reverseBoard, boards[currentMove]);
12941 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12942 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12943 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12944 reverseBoard[r][f] = piece;
12946 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12947 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12948 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12949 || (boards[currentMove][CASTLING][2] == NoRights ||
12950 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12951 && (boards[currentMove][CASTLING][5] == NoRights ||
12952 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12955 CopyBoard(flipBoard, soughtBoard);
12956 CopyBoard(rotateBoard, reverseBoard);
12957 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12958 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
12959 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12962 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12963 if(appData.searchMode >= 5) {
12964 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12965 MakePieceList(soughtBoard, minSought);
12966 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12968 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12969 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12972 GameInfo dummyInfo;
12973 static int creatingBook;
12976 GameContainsPosition (FILE *f, ListGame *lg)
12978 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12979 int fromX, fromY, toX, toY;
12981 static int initDone=FALSE;
12983 // weed out games based on numerical tag comparison
12984 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12985 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12986 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12987 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12989 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12992 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12993 else CopyBoard(boards[scratch], initialPosition); // default start position
12996 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12997 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
13000 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13001 fseek(f, lg->offset, 0);
13004 yyboardindex = scratch;
13005 quickFlag = plyNr+1;
13010 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
13016 if(plyNr) return -1; // after we have seen moves, this is for new game
13019 case AmbiguousMove: // we cannot reconstruct the game beyond these two
13020 case ImpossibleMove:
13021 case WhiteWins: // game ends here with these four
13024 case GameUnfinished:
13028 if(appData.testLegality) return -1;
13029 case WhiteCapturesEnPassant:
13030 case BlackCapturesEnPassant:
13031 case WhitePromotion:
13032 case BlackPromotion:
13033 case WhiteNonPromotion:
13034 case BlackNonPromotion:
13037 case WhiteKingSideCastle:
13038 case WhiteQueenSideCastle:
13039 case BlackKingSideCastle:
13040 case BlackQueenSideCastle:
13041 case WhiteKingSideCastleWild:
13042 case WhiteQueenSideCastleWild:
13043 case BlackKingSideCastleWild:
13044 case BlackQueenSideCastleWild:
13045 case WhiteHSideCastleFR:
13046 case WhiteASideCastleFR:
13047 case BlackHSideCastleFR:
13048 case BlackASideCastleFR:
13049 fromX = currentMoveString[0] - AAA;
13050 fromY = currentMoveString[1] - ONE;
13051 toX = currentMoveString[2] - AAA;
13052 toY = currentMoveString[3] - ONE;
13053 promoChar = currentMoveString[4];
13057 fromX = next == WhiteDrop ?
13058 (int) CharToPiece(ToUpper(currentMoveString[0])) :
13059 (int) CharToPiece(ToLower(currentMoveString[0]));
13061 toX = currentMoveString[2] - AAA;
13062 toY = currentMoveString[3] - ONE;
13066 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
13068 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
13069 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13070 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
13071 if(appData.findMirror) {
13072 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
13073 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
13078 /* Load the nth game from open file f */
13080 LoadGame (FILE *f, int gameNumber, char *title, int useList)
13084 int gn = gameNumber;
13085 ListGame *lg = NULL;
13086 int numPGNTags = 0, i;
13088 GameMode oldGameMode;
13089 VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13090 char oldName[MSG_SIZ];
13092 safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13094 if (appData.debugMode)
13095 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13097 if (gameMode == Training )
13098 SetTrainingModeOff();
13100 oldGameMode = gameMode;
13101 if (gameMode != BeginningOfGame) {
13102 Reset(FALSE, TRUE);
13104 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13107 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13108 fclose(lastLoadGameFP);
13112 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13115 fseek(f, lg->offset, 0);
13116 GameListHighlight(gameNumber);
13117 pos = lg->position;
13121 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13122 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13124 DisplayError(_("Game number out of range"), 0);
13129 if (fseek(f, 0, 0) == -1) {
13130 if (f == lastLoadGameFP ?
13131 gameNumber == lastLoadGameNumber + 1 :
13135 DisplayError(_("Can't seek on game file"), 0);
13140 lastLoadGameFP = f;
13141 lastLoadGameNumber = gameNumber;
13142 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13143 lastLoadGameUseList = useList;
13147 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13148 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13149 lg->gameInfo.black);
13151 } else if (*title != NULLCHAR) {
13152 if (gameNumber > 1) {
13153 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13156 DisplayTitle(title);
13160 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13161 gameMode = PlayFromGameFile;
13165 currentMove = forwardMostMove = backwardMostMove = 0;
13166 CopyBoard(boards[0], initialPosition);
13170 * Skip the first gn-1 games in the file.
13171 * Also skip over anything that precedes an identifiable
13172 * start of game marker, to avoid being confused by
13173 * garbage at the start of the file. Currently
13174 * recognized start of game markers are the move number "1",
13175 * the pattern "gnuchess .* game", the pattern
13176 * "^[#;%] [^ ]* game file", and a PGN tag block.
13177 * A game that starts with one of the latter two patterns
13178 * will also have a move number 1, possibly
13179 * following a position diagram.
13180 * 5-4-02: Let's try being more lenient and allowing a game to
13181 * start with an unnumbered move. Does that break anything?
13183 cm = lastLoadGameStart = EndOfFile;
13185 yyboardindex = forwardMostMove;
13186 cm = (ChessMove) Myylex();
13189 if (cmailMsgLoaded) {
13190 nCmailGames = CMAIL_MAX_GAMES - gn;
13193 DisplayError(_("Game not found in file"), 0);
13200 lastLoadGameStart = cm;
13203 case MoveNumberOne:
13204 switch (lastLoadGameStart) {
13209 case MoveNumberOne:
13211 gn--; /* count this game */
13212 lastLoadGameStart = cm;
13221 switch (lastLoadGameStart) {
13224 case MoveNumberOne:
13226 gn--; /* count this game */
13227 lastLoadGameStart = cm;
13230 lastLoadGameStart = cm; /* game counted already */
13238 yyboardindex = forwardMostMove;
13239 cm = (ChessMove) Myylex();
13240 } while (cm == PGNTag || cm == Comment);
13247 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13248 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
13249 != CMAIL_OLD_RESULT) {
13251 cmailResult[ CMAIL_MAX_GAMES
13252 - gn - 1] = CMAIL_OLD_RESULT;
13259 /* Only a NormalMove can be at the start of a game
13260 * without a position diagram. */
13261 if (lastLoadGameStart == EndOfFile ) {
13263 lastLoadGameStart = MoveNumberOne;
13272 if (appData.debugMode)
13273 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13275 for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13277 if (cm == XBoardGame) {
13278 /* Skip any header junk before position diagram and/or move 1 */
13280 yyboardindex = forwardMostMove;
13281 cm = (ChessMove) Myylex();
13283 if (cm == EndOfFile ||
13284 cm == GNUChessGame || cm == XBoardGame) {
13285 /* Empty game; pretend end-of-file and handle later */
13290 if (cm == MoveNumberOne || cm == PositionDiagram ||
13291 cm == PGNTag || cm == Comment)
13294 } else if (cm == GNUChessGame) {
13295 if (gameInfo.event != NULL) {
13296 free(gameInfo.event);
13298 gameInfo.event = StrSave(yy_text);
13301 startedFromSetupPosition = startedFromPositionFile; // [HGM]
13302 while (cm == PGNTag) {
13303 if (appData.debugMode)
13304 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13305 err = ParsePGNTag(yy_text, &gameInfo);
13306 if (!err) numPGNTags++;
13308 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13309 if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13310 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13311 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13312 InitPosition(TRUE);
13313 oldVariant = gameInfo.variant;
13314 if (appData.debugMode)
13315 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13319 if (gameInfo.fen != NULL) {
13320 Board initial_position;
13321 startedFromSetupPosition = TRUE;
13322 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13324 DisplayError(_("Bad FEN position in file"), 0);
13327 CopyBoard(boards[0], initial_position);
13328 if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13329 CopyBoard(initialPosition, initial_position);
13330 if (blackPlaysFirst) {
13331 currentMove = forwardMostMove = backwardMostMove = 1;
13332 CopyBoard(boards[1], initial_position);
13333 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13334 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13335 timeRemaining[0][1] = whiteTimeRemaining;
13336 timeRemaining[1][1] = blackTimeRemaining;
13337 if (commentList[0] != NULL) {
13338 commentList[1] = commentList[0];
13339 commentList[0] = NULL;
13342 currentMove = forwardMostMove = backwardMostMove = 0;
13344 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13346 initialRulePlies = FENrulePlies;
13347 for( i=0; i< nrCastlingRights; i++ )
13348 initialRights[i] = initial_position[CASTLING][i];
13350 yyboardindex = forwardMostMove;
13351 free(gameInfo.fen);
13352 gameInfo.fen = NULL;
13355 yyboardindex = forwardMostMove;
13356 cm = (ChessMove) Myylex();
13358 /* Handle comments interspersed among the tags */
13359 while (cm == Comment) {
13361 if (appData.debugMode)
13362 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13364 AppendComment(currentMove, p, FALSE);
13365 yyboardindex = forwardMostMove;
13366 cm = (ChessMove) Myylex();
13370 /* don't rely on existence of Event tag since if game was
13371 * pasted from clipboard the Event tag may not exist
13373 if (numPGNTags > 0){
13375 if (gameInfo.variant == VariantNormal) {
13376 VariantClass v = StringToVariant(gameInfo.event);
13377 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13378 if(v < VariantShogi) gameInfo.variant = v;
13381 if( appData.autoDisplayTags ) {
13382 tags = PGNTags(&gameInfo);
13383 TagsPopUp(tags, CmailMsg());
13388 /* Make something up, but don't display it now */
13393 if (cm == PositionDiagram) {
13396 Board initial_position;
13398 if (appData.debugMode)
13399 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13401 if (!startedFromSetupPosition) {
13403 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13404 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13415 initial_position[i][j++] = CharToPiece(*p);
13418 while (*p == ' ' || *p == '\t' ||
13419 *p == '\n' || *p == '\r') p++;
13421 if (strncmp(p, "black", strlen("black"))==0)
13422 blackPlaysFirst = TRUE;
13424 blackPlaysFirst = FALSE;
13425 startedFromSetupPosition = TRUE;
13427 CopyBoard(boards[0], initial_position);
13428 if (blackPlaysFirst) {
13429 currentMove = forwardMostMove = backwardMostMove = 1;
13430 CopyBoard(boards[1], initial_position);
13431 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13432 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13433 timeRemaining[0][1] = whiteTimeRemaining;
13434 timeRemaining[1][1] = blackTimeRemaining;
13435 if (commentList[0] != NULL) {
13436 commentList[1] = commentList[0];
13437 commentList[0] = NULL;
13440 currentMove = forwardMostMove = backwardMostMove = 0;
13443 yyboardindex = forwardMostMove;
13444 cm = (ChessMove) Myylex();
13447 if(!creatingBook) {
13448 if (first.pr == NoProc) {
13449 StartChessProgram(&first);
13451 InitChessProgram(&first, FALSE);
13452 if(gameInfo.variant == VariantUnknown && *oldName) {
13453 safeStrCpy(engineVariant, oldName, MSG_SIZ);
13454 gameInfo.variant = v;
13456 SendToProgram("force\n", &first);
13457 if (startedFromSetupPosition) {
13458 SendBoard(&first, forwardMostMove);
13459 if (appData.debugMode) {
13460 fprintf(debugFP, "Load Game\n");
13462 DisplayBothClocks();
13466 /* [HGM] server: flag to write setup moves in broadcast file as one */
13467 loadFlag = appData.suppressLoadMoves;
13469 while (cm == Comment) {
13471 if (appData.debugMode)
13472 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13474 AppendComment(currentMove, p, FALSE);
13475 yyboardindex = forwardMostMove;
13476 cm = (ChessMove) Myylex();
13479 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13480 cm == WhiteWins || cm == BlackWins ||
13481 cm == GameIsDrawn || cm == GameUnfinished) {
13482 DisplayMessage("", _("No moves in game"));
13483 if (cmailMsgLoaded) {
13484 if (appData.debugMode)
13485 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13489 DrawPosition(FALSE, boards[currentMove]);
13490 DisplayBothClocks();
13491 gameMode = EditGame;
13498 // [HGM] PV info: routine tests if comment empty
13499 if (!matchMode && (pausing || appData.timeDelay != 0)) {
13500 DisplayComment(currentMove - 1, commentList[currentMove]);
13502 if (!matchMode && appData.timeDelay != 0)
13503 DrawPosition(FALSE, boards[currentMove]);
13505 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13506 programStats.ok_to_send = 1;
13509 /* if the first token after the PGN tags is a move
13510 * and not move number 1, retrieve it from the parser
13512 if (cm != MoveNumberOne)
13513 LoadGameOneMove(cm);
13515 /* load the remaining moves from the file */
13516 while (LoadGameOneMove(EndOfFile)) {
13517 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13518 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13521 /* rewind to the start of the game */
13522 currentMove = backwardMostMove;
13524 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13526 if (oldGameMode == AnalyzeFile) {
13527 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13528 AnalyzeFileEvent();
13530 if (oldGameMode == AnalyzeMode) {
13531 AnalyzeFileEvent();
13534 if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13535 long int w, b; // [HGM] adjourn: restore saved clock times
13536 char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13537 if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13538 timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13539 timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13543 if(creatingBook) return TRUE;
13544 if (!matchMode && pos > 0) {
13545 ToNrEvent(pos); // [HGM] no autoplay if selected on position
13547 if (matchMode || appData.timeDelay == 0) {
13549 } else if (appData.timeDelay > 0) {
13550 AutoPlayGameLoop();
13553 if (appData.debugMode)
13554 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13556 loadFlag = 0; /* [HGM] true game starts */
13560 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13562 ReloadPosition (int offset)
13564 int positionNumber = lastLoadPositionNumber + offset;
13565 if (lastLoadPositionFP == NULL) {
13566 DisplayError(_("No position has been loaded yet"), 0);
13569 if (positionNumber <= 0) {
13570 DisplayError(_("Can't back up any further"), 0);
13573 return LoadPosition(lastLoadPositionFP, positionNumber,
13574 lastLoadPositionTitle);
13577 /* Load the nth position from the given file */
13579 LoadPositionFromFile (char *filename, int n, char *title)
13584 if (strcmp(filename, "-") == 0) {
13585 return LoadPosition(stdin, n, "stdin");
13587 f = fopen(filename, "rb");
13589 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13590 DisplayError(buf, errno);
13593 return LoadPosition(f, n, title);
13598 /* Load the nth position from the given open file, and close it */
13600 LoadPosition (FILE *f, int positionNumber, char *title)
13602 char *p, line[MSG_SIZ];
13603 Board initial_position;
13604 int i, j, fenMode, pn;
13606 if (gameMode == Training )
13607 SetTrainingModeOff();
13609 if (gameMode != BeginningOfGame) {
13610 Reset(FALSE, TRUE);
13612 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13613 fclose(lastLoadPositionFP);
13615 if (positionNumber == 0) positionNumber = 1;
13616 lastLoadPositionFP = f;
13617 lastLoadPositionNumber = positionNumber;
13618 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13619 if (first.pr == NoProc && !appData.noChessProgram) {
13620 StartChessProgram(&first);
13621 InitChessProgram(&first, FALSE);
13623 pn = positionNumber;
13624 if (positionNumber < 0) {
13625 /* Negative position number means to seek to that byte offset */
13626 if (fseek(f, -positionNumber, 0) == -1) {
13627 DisplayError(_("Can't seek on position file"), 0);
13632 if (fseek(f, 0, 0) == -1) {
13633 if (f == lastLoadPositionFP ?
13634 positionNumber == lastLoadPositionNumber + 1 :
13635 positionNumber == 1) {
13638 DisplayError(_("Can't seek on position file"), 0);
13643 /* See if this file is FEN or old-style xboard */
13644 if (fgets(line, MSG_SIZ, f) == NULL) {
13645 DisplayError(_("Position not found in file"), 0);
13648 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13649 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13652 if (fenMode || line[0] == '#') pn--;
13654 /* skip positions before number pn */
13655 if (fgets(line, MSG_SIZ, f) == NULL) {
13657 DisplayError(_("Position not found in file"), 0);
13660 if (fenMode || line[0] == '#') pn--;
13666 if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13667 DisplayError(_("Bad FEN position in file"), 0);
13670 if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13671 sscanf(p+4, "%[^;]", bestMove);
13672 } else *bestMove = NULLCHAR;
13673 if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13674 sscanf(p+4, "%[^;]", avoidMove);
13675 } else *avoidMove = NULLCHAR;
13677 (void) fgets(line, MSG_SIZ, f);
13678 (void) fgets(line, MSG_SIZ, f);
13680 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13681 (void) fgets(line, MSG_SIZ, f);
13682 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13685 initial_position[i][j++] = CharToPiece(*p);
13689 blackPlaysFirst = FALSE;
13691 (void) fgets(line, MSG_SIZ, f);
13692 if (strncmp(line, "black", strlen("black"))==0)
13693 blackPlaysFirst = TRUE;
13696 startedFromSetupPosition = TRUE;
13698 CopyBoard(boards[0], initial_position);
13699 if (blackPlaysFirst) {
13700 currentMove = forwardMostMove = backwardMostMove = 1;
13701 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13702 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13703 CopyBoard(boards[1], initial_position);
13704 DisplayMessage("", _("Black to play"));
13706 currentMove = forwardMostMove = backwardMostMove = 0;
13707 DisplayMessage("", _("White to play"));
13709 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13710 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13711 SendToProgram("force\n", &first);
13712 SendBoard(&first, forwardMostMove);
13714 if (appData.debugMode) {
13716 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13717 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13718 fprintf(debugFP, "Load Position\n");
13721 if (positionNumber > 1) {
13722 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13723 DisplayTitle(line);
13725 DisplayTitle(title);
13727 gameMode = EditGame;
13730 timeRemaining[0][1] = whiteTimeRemaining;
13731 timeRemaining[1][1] = blackTimeRemaining;
13732 DrawPosition(FALSE, boards[currentMove]);
13739 CopyPlayerNameIntoFileName (char **dest, char *src)
13741 while (*src != NULLCHAR && *src != ',') {
13746 *(*dest)++ = *src++;
13752 DefaultFileName (char *ext)
13754 static char def[MSG_SIZ];
13757 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13759 CopyPlayerNameIntoFileName(&p, gameInfo.white);
13761 CopyPlayerNameIntoFileName(&p, gameInfo.black);
13763 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13770 /* Save the current game to the given file */
13772 SaveGameToFile (char *filename, int append)
13776 int result, i, t,tot=0;
13778 if (strcmp(filename, "-") == 0) {
13779 return SaveGame(stdout, 0, NULL);
13781 for(i=0; i<10; i++) { // upto 10 tries
13782 f = fopen(filename, append ? "a" : "w");
13783 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13784 if(f || errno != 13) break;
13785 DoSleep(t = 5 + random()%11); // wait 5-15 msec
13789 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13790 DisplayError(buf, errno);
13793 safeStrCpy(buf, lastMsg, MSG_SIZ);
13794 DisplayMessage(_("Waiting for access to save file"), "");
13795 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13796 DisplayMessage(_("Saving game"), "");
13797 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
13798 result = SaveGame(f, 0, NULL);
13799 DisplayMessage(buf, "");
13806 SavePart (char *str)
13808 static char buf[MSG_SIZ];
13811 p = strchr(str, ' ');
13812 if (p == NULL) return str;
13813 strncpy(buf, str, p - str);
13814 buf[p - str] = NULLCHAR;
13818 #define PGN_MAX_LINE 75
13820 #define PGN_SIDE_WHITE 0
13821 #define PGN_SIDE_BLACK 1
13824 FindFirstMoveOutOfBook (int side)
13828 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13829 int index = backwardMostMove;
13830 int has_book_hit = 0;
13832 if( (index % 2) != side ) {
13836 while( index < forwardMostMove ) {
13837 /* Check to see if engine is in book */
13838 int depth = pvInfoList[index].depth;
13839 int score = pvInfoList[index].score;
13845 else if( score == 0 && depth == 63 ) {
13846 in_book = 1; /* Zappa */
13848 else if( score == 2 && depth == 99 ) {
13849 in_book = 1; /* Abrok */
13852 has_book_hit += in_book;
13868 GetOutOfBookInfo (char * buf)
13872 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13874 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13875 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13879 if( oob[0] >= 0 || oob[1] >= 0 ) {
13880 for( i=0; i<2; i++ ) {
13884 if( i > 0 && oob[0] >= 0 ) {
13885 strcat( buf, " " );
13888 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13889 sprintf( buf+strlen(buf), "%s%.2f",
13890 pvInfoList[idx].score >= 0 ? "+" : "",
13891 pvInfoList[idx].score / 100.0 );
13897 /* Save game in PGN style */
13899 SaveGamePGN2 (FILE *f)
13901 int i, offset, linelen, newblock;
13904 int movelen, numlen, blank;
13905 char move_buffer[100]; /* [AS] Buffer for move+PV info */
13907 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13909 PrintPGNTags(f, &gameInfo);
13911 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13913 if (backwardMostMove > 0 || startedFromSetupPosition) {
13914 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13915 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13916 fprintf(f, "\n{--------------\n");
13917 PrintPosition(f, backwardMostMove);
13918 fprintf(f, "--------------}\n");
13922 /* [AS] Out of book annotation */
13923 if( appData.saveOutOfBookInfo ) {
13926 GetOutOfBookInfo( buf );
13928 if( buf[0] != '\0' ) {
13929 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13936 i = backwardMostMove;
13940 while (i < forwardMostMove) {
13941 /* Print comments preceding this move */
13942 if (commentList[i] != NULL) {
13943 if (linelen > 0) fprintf(f, "\n");
13944 fprintf(f, "%s", commentList[i]);
13949 /* Format move number */
13951 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13954 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13956 numtext[0] = NULLCHAR;
13958 numlen = strlen(numtext);
13961 /* Print move number */
13962 blank = linelen > 0 && numlen > 0;
13963 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13972 fprintf(f, "%s", numtext);
13976 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13977 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13980 blank = linelen > 0 && movelen > 0;
13981 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13990 fprintf(f, "%s", move_buffer);
13991 linelen += movelen;
13993 /* [AS] Add PV info if present */
13994 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13995 /* [HGM] add time */
13996 char buf[MSG_SIZ]; int seconds;
13998 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
14004 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
14007 seconds = (seconds + 4)/10; // round to full seconds
14009 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
14011 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
14014 if(appData.cumulativeTimePGN) {
14015 snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
14018 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
14019 pvInfoList[i].score >= 0 ? "+" : "",
14020 pvInfoList[i].score / 100.0,
14021 pvInfoList[i].depth,
14024 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
14026 /* Print score/depth */
14027 blank = linelen > 0 && movelen > 0;
14028 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14037 fprintf(f, "%s", move_buffer);
14038 linelen += movelen;
14044 /* Start a new line */
14045 if (linelen > 0) fprintf(f, "\n");
14047 /* Print comments after last move */
14048 if (commentList[i] != NULL) {
14049 fprintf(f, "%s\n", commentList[i]);
14053 if (gameInfo.resultDetails != NULL &&
14054 gameInfo.resultDetails[0] != NULLCHAR) {
14055 char buf[MSG_SIZ], *p = gameInfo.resultDetails;
14056 if(gameInfo.result == GameUnfinished && appData.clockMode &&
14057 (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
14058 snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
14059 fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
14061 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14065 /* Save game in PGN style and close the file */
14067 SaveGamePGN (FILE *f)
14071 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14075 /* Save game in old style and close the file */
14077 SaveGameOldStyle (FILE *f)
14082 tm = time((time_t *) NULL);
14084 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
14087 if (backwardMostMove > 0 || startedFromSetupPosition) {
14088 fprintf(f, "\n[--------------\n");
14089 PrintPosition(f, backwardMostMove);
14090 fprintf(f, "--------------]\n");
14095 i = backwardMostMove;
14096 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14098 while (i < forwardMostMove) {
14099 if (commentList[i] != NULL) {
14100 fprintf(f, "[%s]\n", commentList[i]);
14103 if ((i % 2) == 1) {
14104 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
14107 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
14109 if (commentList[i] != NULL) {
14113 if (i >= forwardMostMove) {
14117 fprintf(f, "%s\n", parseList[i]);
14122 if (commentList[i] != NULL) {
14123 fprintf(f, "[%s]\n", commentList[i]);
14126 /* This isn't really the old style, but it's close enough */
14127 if (gameInfo.resultDetails != NULL &&
14128 gameInfo.resultDetails[0] != NULLCHAR) {
14129 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14130 gameInfo.resultDetails);
14132 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14139 /* Save the current game to open file f and close the file */
14141 SaveGame (FILE *f, int dummy, char *dummy2)
14143 if (gameMode == EditPosition) EditPositionDone(TRUE);
14144 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14145 if (appData.oldSaveStyle)
14146 return SaveGameOldStyle(f);
14148 return SaveGamePGN(f);
14151 /* Save the current position to the given file */
14153 SavePositionToFile (char *filename)
14158 if (strcmp(filename, "-") == 0) {
14159 return SavePosition(stdout, 0, NULL);
14161 f = fopen(filename, "a");
14163 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14164 DisplayError(buf, errno);
14167 safeStrCpy(buf, lastMsg, MSG_SIZ);
14168 DisplayMessage(_("Waiting for access to save file"), "");
14169 flock(fileno(f), LOCK_EX); // [HGM] lock
14170 DisplayMessage(_("Saving position"), "");
14171 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
14172 SavePosition(f, 0, NULL);
14173 DisplayMessage(buf, "");
14179 /* Save the current position to the given open file and close the file */
14181 SavePosition (FILE *f, int dummy, char *dummy2)
14186 if (gameMode == EditPosition) EditPositionDone(TRUE);
14187 if (appData.oldSaveStyle) {
14188 tm = time((time_t *) NULL);
14190 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14192 fprintf(f, "[--------------\n");
14193 PrintPosition(f, currentMove);
14194 fprintf(f, "--------------]\n");
14196 fen = PositionToFEN(currentMove, NULL, 1);
14197 fprintf(f, "%s\n", fen);
14205 ReloadCmailMsgEvent (int unregister)
14208 static char *inFilename = NULL;
14209 static char *outFilename;
14211 struct stat inbuf, outbuf;
14214 /* Any registered moves are unregistered if unregister is set, */
14215 /* i.e. invoked by the signal handler */
14217 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14218 cmailMoveRegistered[i] = FALSE;
14219 if (cmailCommentList[i] != NULL) {
14220 free(cmailCommentList[i]);
14221 cmailCommentList[i] = NULL;
14224 nCmailMovesRegistered = 0;
14227 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14228 cmailResult[i] = CMAIL_NOT_RESULT;
14232 if (inFilename == NULL) {
14233 /* Because the filenames are static they only get malloced once */
14234 /* and they never get freed */
14235 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14236 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14238 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14239 sprintf(outFilename, "%s.out", appData.cmailGameName);
14242 status = stat(outFilename, &outbuf);
14244 cmailMailedMove = FALSE;
14246 status = stat(inFilename, &inbuf);
14247 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14250 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14251 counts the games, notes how each one terminated, etc.
14253 It would be nice to remove this kludge and instead gather all
14254 the information while building the game list. (And to keep it
14255 in the game list nodes instead of having a bunch of fixed-size
14256 parallel arrays.) Note this will require getting each game's
14257 termination from the PGN tags, as the game list builder does
14258 not process the game moves. --mann
14260 cmailMsgLoaded = TRUE;
14261 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14263 /* Load first game in the file or popup game menu */
14264 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14266 #endif /* !WIN32 */
14274 char string[MSG_SIZ];
14276 if ( cmailMailedMove
14277 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14278 return TRUE; /* Allow free viewing */
14281 /* Unregister move to ensure that we don't leave RegisterMove */
14282 /* with the move registered when the conditions for registering no */
14284 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14285 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14286 nCmailMovesRegistered --;
14288 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14290 free(cmailCommentList[lastLoadGameNumber - 1]);
14291 cmailCommentList[lastLoadGameNumber - 1] = NULL;
14295 if (cmailOldMove == -1) {
14296 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14300 if (currentMove > cmailOldMove + 1) {
14301 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14305 if (currentMove < cmailOldMove) {
14306 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14310 if (forwardMostMove > currentMove) {
14311 /* Silently truncate extra moves */
14315 if ( (currentMove == cmailOldMove + 1)
14316 || ( (currentMove == cmailOldMove)
14317 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14318 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14319 if (gameInfo.result != GameUnfinished) {
14320 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14323 if (commentList[currentMove] != NULL) {
14324 cmailCommentList[lastLoadGameNumber - 1]
14325 = StrSave(commentList[currentMove]);
14327 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14329 if (appData.debugMode)
14330 fprintf(debugFP, "Saving %s for game %d\n",
14331 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14333 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14335 f = fopen(string, "w");
14336 if (appData.oldSaveStyle) {
14337 SaveGameOldStyle(f); /* also closes the file */
14339 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14340 f = fopen(string, "w");
14341 SavePosition(f, 0, NULL); /* also closes the file */
14343 fprintf(f, "{--------------\n");
14344 PrintPosition(f, currentMove);
14345 fprintf(f, "--------------}\n\n");
14347 SaveGame(f, 0, NULL); /* also closes the file*/
14350 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14351 nCmailMovesRegistered ++;
14352 } else if (nCmailGames == 1) {
14353 DisplayError(_("You have not made a move yet"), 0);
14364 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14365 FILE *commandOutput;
14366 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14367 int nBytes = 0; /* Suppress warnings on uninitialized variables */
14373 if (! cmailMsgLoaded) {
14374 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14378 if (nCmailGames == nCmailResults) {
14379 DisplayError(_("No unfinished games"), 0);
14383 #if CMAIL_PROHIBIT_REMAIL
14384 if (cmailMailedMove) {
14385 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);
14386 DisplayError(msg, 0);
14391 if (! (cmailMailedMove || RegisterMove())) return;
14393 if ( cmailMailedMove
14394 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14395 snprintf(string, MSG_SIZ, partCommandString,
14396 appData.debugMode ? " -v" : "", appData.cmailGameName);
14397 commandOutput = popen(string, "r");
14399 if (commandOutput == NULL) {
14400 DisplayError(_("Failed to invoke cmail"), 0);
14402 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14403 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14405 if (nBuffers > 1) {
14406 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14407 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14408 nBytes = MSG_SIZ - 1;
14410 (void) memcpy(msg, buffer, nBytes);
14412 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14414 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14415 cmailMailedMove = TRUE; /* Prevent >1 moves */
14418 for (i = 0; i < nCmailGames; i ++) {
14419 if (cmailResult[i] == CMAIL_NOT_RESULT) {
14424 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14426 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14428 appData.cmailGameName,
14430 LoadGameFromFile(buffer, 1, buffer, FALSE);
14431 cmailMsgLoaded = FALSE;
14435 DisplayInformation(msg);
14436 pclose(commandOutput);
14439 if ((*cmailMsg) != '\0') {
14440 DisplayInformation(cmailMsg);
14445 #endif /* !WIN32 */
14454 int prependComma = 0;
14456 char string[MSG_SIZ]; /* Space for game-list */
14459 if (!cmailMsgLoaded) return "";
14461 if (cmailMailedMove) {
14462 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14464 /* Create a list of games left */
14465 snprintf(string, MSG_SIZ, "[");
14466 for (i = 0; i < nCmailGames; i ++) {
14467 if (! ( cmailMoveRegistered[i]
14468 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14469 if (prependComma) {
14470 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14472 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14476 strcat(string, number);
14479 strcat(string, "]");
14481 if (nCmailMovesRegistered + nCmailResults == 0) {
14482 switch (nCmailGames) {
14484 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14488 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14492 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14497 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14499 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14504 if (nCmailResults == nCmailGames) {
14505 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14507 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14512 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14524 if (gameMode == Training)
14525 SetTrainingModeOff();
14528 cmailMsgLoaded = FALSE;
14529 if (appData.icsActive) {
14530 SendToICS(ics_prefix);
14531 SendToICS("refresh\n");
14536 ExitEvent (int status)
14540 /* Give up on clean exit */
14544 /* Keep trying for clean exit */
14548 if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14549 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14551 if (telnetISR != NULL) {
14552 RemoveInputSource(telnetISR);
14554 if (icsPR != NoProc) {
14555 DestroyChildProcess(icsPR, TRUE);
14558 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14559 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14561 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14562 /* make sure this other one finishes before killing it! */
14563 if(endingGame) { int count = 0;
14564 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14565 while(endingGame && count++ < 10) DoSleep(1);
14566 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14569 /* Kill off chess programs */
14570 if (first.pr != NoProc) {
14573 DoSleep( appData.delayBeforeQuit );
14574 SendToProgram("quit\n", &first);
14575 DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14577 if (second.pr != NoProc) {
14578 DoSleep( appData.delayBeforeQuit );
14579 SendToProgram("quit\n", &second);
14580 DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14582 if (first.isr != NULL) {
14583 RemoveInputSource(first.isr);
14585 if (second.isr != NULL) {
14586 RemoveInputSource(second.isr);
14589 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14590 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14592 ShutDownFrontEnd();
14597 PauseEngine (ChessProgramState *cps)
14599 SendToProgram("pause\n", cps);
14604 UnPauseEngine (ChessProgramState *cps)
14606 SendToProgram("resume\n", cps);
14613 if (appData.debugMode)
14614 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14618 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14620 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14621 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14622 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14624 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14625 HandleMachineMove(stashedInputMove, stalledEngine);
14626 stalledEngine = NULL;
14629 if (gameMode == MachinePlaysWhite ||
14630 gameMode == TwoMachinesPlay ||
14631 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14632 if(first.pause) UnPauseEngine(&first);
14633 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14634 if(second.pause) UnPauseEngine(&second);
14635 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14638 DisplayBothClocks();
14640 if (gameMode == PlayFromGameFile) {
14641 if (appData.timeDelay >= 0)
14642 AutoPlayGameLoop();
14643 } else if (gameMode == IcsExamining && pauseExamInvalid) {
14644 Reset(FALSE, TRUE);
14645 SendToICS(ics_prefix);
14646 SendToICS("refresh\n");
14647 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14648 ForwardInner(forwardMostMove);
14650 pauseExamInvalid = FALSE;
14652 switch (gameMode) {
14656 pauseExamForwardMostMove = forwardMostMove;
14657 pauseExamInvalid = FALSE;
14660 case IcsPlayingWhite:
14661 case IcsPlayingBlack:
14665 case PlayFromGameFile:
14666 (void) StopLoadGameTimer();
14670 case BeginningOfGame:
14671 if (appData.icsActive) return;
14672 /* else fall through */
14673 case MachinePlaysWhite:
14674 case MachinePlaysBlack:
14675 case TwoMachinesPlay:
14676 if (forwardMostMove == 0)
14677 return; /* don't pause if no one has moved */
14678 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14679 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14680 if(onMove->pause) { // thinking engine can be paused
14681 PauseEngine(onMove); // do it
14682 if(onMove->other->pause) // pondering opponent can always be paused immediately
14683 PauseEngine(onMove->other);
14685 SendToProgram("easy\n", onMove->other);
14687 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14688 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14690 PauseEngine(&first);
14692 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14693 } else { // human on move, pause pondering by either method
14695 PauseEngine(&first);
14696 else if(appData.ponderNextMove)
14697 SendToProgram("easy\n", &first);
14700 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14710 EditCommentEvent ()
14712 char title[MSG_SIZ];
14714 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14715 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14717 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14718 WhiteOnMove(currentMove - 1) ? " " : ".. ",
14719 parseList[currentMove - 1]);
14722 EditCommentPopUp(currentMove, title, commentList[currentMove]);
14729 char *tags = PGNTags(&gameInfo);
14731 EditTagsPopUp(tags, NULL);
14738 if(second.analyzing) {
14739 SendToProgram("exit\n", &second);
14740 second.analyzing = FALSE;
14742 if (second.pr == NoProc) StartChessProgram(&second);
14743 InitChessProgram(&second, FALSE);
14744 FeedMovesToProgram(&second, currentMove);
14746 SendToProgram("analyze\n", &second);
14747 second.analyzing = TRUE;
14751 /* Toggle ShowThinking */
14753 ToggleShowThinking()
14755 appData.showThinking = !appData.showThinking;
14756 ShowThinkingEvent();
14760 AnalyzeModeEvent ()
14764 if (!first.analysisSupport) {
14765 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14766 DisplayError(buf, 0);
14769 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14770 if (appData.icsActive) {
14771 if (gameMode != IcsObserving) {
14772 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14773 DisplayError(buf, 0);
14775 if (appData.icsEngineAnalyze) {
14776 if (appData.debugMode)
14777 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14783 /* if enable, user wants to disable icsEngineAnalyze */
14784 if (appData.icsEngineAnalyze) {
14789 appData.icsEngineAnalyze = TRUE;
14790 if (appData.debugMode)
14791 fprintf(debugFP, "ICS engine analyze starting... \n");
14794 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14795 if (appData.noChessProgram || gameMode == AnalyzeMode)
14798 if (gameMode != AnalyzeFile) {
14799 if (!appData.icsEngineAnalyze) {
14801 if (gameMode != EditGame) return 0;
14803 if (!appData.showThinking) ToggleShowThinking();
14804 ResurrectChessProgram();
14805 SendToProgram("analyze\n", &first);
14806 first.analyzing = TRUE;
14807 /*first.maybeThinking = TRUE;*/
14808 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14809 EngineOutputPopUp();
14811 if (!appData.icsEngineAnalyze) {
14812 gameMode = AnalyzeMode;
14813 ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14819 StartAnalysisClock();
14820 GetTimeMark(&lastNodeCountTime);
14826 AnalyzeFileEvent ()
14828 if (appData.noChessProgram || gameMode == AnalyzeFile)
14831 if (!first.analysisSupport) {
14833 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14834 DisplayError(buf, 0);
14838 if (gameMode != AnalyzeMode) {
14839 keepInfo = 1; // mere annotating should not alter PGN tags
14842 if (gameMode != EditGame) return;
14843 if (!appData.showThinking) ToggleShowThinking();
14844 ResurrectChessProgram();
14845 SendToProgram("analyze\n", &first);
14846 first.analyzing = TRUE;
14847 /*first.maybeThinking = TRUE;*/
14848 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14849 EngineOutputPopUp();
14851 gameMode = AnalyzeFile;
14855 StartAnalysisClock();
14856 GetTimeMark(&lastNodeCountTime);
14858 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14859 AnalysisPeriodicEvent(1);
14863 MachineWhiteEvent ()
14866 char *bookHit = NULL;
14868 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14872 if (gameMode == PlayFromGameFile ||
14873 gameMode == TwoMachinesPlay ||
14874 gameMode == Training ||
14875 gameMode == AnalyzeMode ||
14876 gameMode == EndOfGame)
14879 if (gameMode == EditPosition)
14880 EditPositionDone(TRUE);
14882 if (!WhiteOnMove(currentMove)) {
14883 DisplayError(_("It is not White's turn"), 0);
14887 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14890 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14891 gameMode == AnalyzeFile)
14894 ResurrectChessProgram(); /* in case it isn't running */
14895 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14896 gameMode = MachinePlaysWhite;
14899 gameMode = MachinePlaysWhite;
14903 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14905 if (first.sendName) {
14906 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14907 SendToProgram(buf, &first);
14909 if (first.sendTime) {
14910 if (first.useColors) {
14911 SendToProgram("black\n", &first); /*gnu kludge*/
14913 SendTimeRemaining(&first, TRUE);
14915 if (first.useColors) {
14916 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14918 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14919 SetMachineThinkingEnables();
14920 first.maybeThinking = TRUE;
14924 if (appData.autoFlipView && !flipView) {
14925 flipView = !flipView;
14926 DrawPosition(FALSE, NULL);
14927 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14930 if(bookHit) { // [HGM] book: simulate book reply
14931 static char bookMove[MSG_SIZ]; // a bit generous?
14933 programStats.nodes = programStats.depth = programStats.time =
14934 programStats.score = programStats.got_only_move = 0;
14935 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14937 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14938 strcat(bookMove, bookHit);
14939 savedMessage = bookMove; // args for deferred call
14940 savedState = &first;
14941 ScheduleDelayedEvent(DeferredBookMove, 1);
14946 MachineBlackEvent ()
14949 char *bookHit = NULL;
14951 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14955 if (gameMode == PlayFromGameFile ||
14956 gameMode == TwoMachinesPlay ||
14957 gameMode == Training ||
14958 gameMode == AnalyzeMode ||
14959 gameMode == EndOfGame)
14962 if (gameMode == EditPosition)
14963 EditPositionDone(TRUE);
14965 if (WhiteOnMove(currentMove)) {
14966 DisplayError(_("It is not Black's turn"), 0);
14970 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14973 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14974 gameMode == AnalyzeFile)
14977 ResurrectChessProgram(); /* in case it isn't running */
14978 gameMode = MachinePlaysBlack;
14982 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14984 if (first.sendName) {
14985 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14986 SendToProgram(buf, &first);
14988 if (first.sendTime) {
14989 if (first.useColors) {
14990 SendToProgram("white\n", &first); /*gnu kludge*/
14992 SendTimeRemaining(&first, FALSE);
14994 if (first.useColors) {
14995 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14997 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14998 SetMachineThinkingEnables();
14999 first.maybeThinking = TRUE;
15002 if (appData.autoFlipView && flipView) {
15003 flipView = !flipView;
15004 DrawPosition(FALSE, NULL);
15005 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
15007 if(bookHit) { // [HGM] book: simulate book reply
15008 static char bookMove[MSG_SIZ]; // a bit generous?
15010 programStats.nodes = programStats.depth = programStats.time =
15011 programStats.score = programStats.got_only_move = 0;
15012 sprintf(programStats.movelist, "%s (xbook)", bookHit);
15014 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15015 strcat(bookMove, bookHit);
15016 savedMessage = bookMove; // args for deferred call
15017 savedState = &first;
15018 ScheduleDelayedEvent(DeferredBookMove, 1);
15024 DisplayTwoMachinesTitle ()
15027 if (appData.matchGames > 0) {
15028 if(appData.tourneyFile[0]) {
15029 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
15030 gameInfo.white, _("vs."), gameInfo.black,
15031 nextGame+1, appData.matchGames+1,
15032 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
15034 if (first.twoMachinesColor[0] == 'w') {
15035 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15036 gameInfo.white, _("vs."), gameInfo.black,
15037 first.matchWins, second.matchWins,
15038 matchGame - 1 - (first.matchWins + second.matchWins));
15040 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15041 gameInfo.white, _("vs."), gameInfo.black,
15042 second.matchWins, first.matchWins,
15043 matchGame - 1 - (first.matchWins + second.matchWins));
15046 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15052 SettingsMenuIfReady ()
15054 if (second.lastPing != second.lastPong) {
15055 DisplayMessage("", _("Waiting for second chess program"));
15056 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
15060 DisplayMessage("", "");
15061 SettingsPopUp(&second);
15065 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
15068 if (cps->pr == NoProc) {
15069 StartChessProgram(cps);
15070 if (cps->protocolVersion == 1) {
15072 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
15074 /* kludge: allow timeout for initial "feature" command */
15075 if(retry != TwoMachinesEventIfReady) FreezeUI();
15076 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
15077 DisplayMessage("", buf);
15078 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
15086 TwoMachinesEvent P((void))
15088 int i, move = forwardMostMove;
15090 ChessProgramState *onmove;
15091 char *bookHit = NULL;
15092 static int stalling = 0;
15096 if (appData.noChessProgram) return;
15098 switch (gameMode) {
15099 case TwoMachinesPlay:
15101 case MachinePlaysWhite:
15102 case MachinePlaysBlack:
15103 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15104 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15108 case BeginningOfGame:
15109 case PlayFromGameFile:
15112 if (gameMode != EditGame) return;
15115 EditPositionDone(TRUE);
15126 // forwardMostMove = currentMove;
15127 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15128 startingEngine = TRUE;
15130 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15132 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15133 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15134 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15138 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15140 if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15141 gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15142 startingEngine = matchMode = FALSE;
15143 DisplayError("second engine does not play this", 0);
15144 gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15145 EditGameEvent(); // switch back to EditGame mode
15150 InitChessProgram(&second, FALSE); // unbalances ping of second engine
15151 SendToProgram("force\n", &second);
15153 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15157 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15158 if(appData.matchPause>10000 || appData.matchPause<10)
15159 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15160 wait = SubtractTimeMarks(&now, &pauseStart);
15161 if(wait < appData.matchPause) {
15162 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15165 // we are now committed to starting the game
15167 DisplayMessage("", "");
15169 if (startedFromSetupPosition) {
15170 SendBoard(&second, backwardMostMove);
15171 if (appData.debugMode) {
15172 fprintf(debugFP, "Two Machines\n");
15175 for (i = backwardMostMove; i < forwardMostMove; i++) {
15176 SendMoveToProgram(i, &second);
15180 gameMode = TwoMachinesPlay;
15181 pausing = startingEngine = FALSE;
15182 ModeHighlight(); // [HGM] logo: this triggers display update of logos
15184 DisplayTwoMachinesTitle();
15186 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15191 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15192 SendToProgram(first.computerString, &first);
15193 if (first.sendName) {
15194 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15195 SendToProgram(buf, &first);
15198 SendToProgram(second.computerString, &second);
15199 if (second.sendName) {
15200 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15201 SendToProgram(buf, &second);
15205 if (!first.sendTime || !second.sendTime || move == 0) { // [HGM] first engine changed sides from Reset, so recalc time odds
15207 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15208 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15210 if (onmove->sendTime) {
15211 if (onmove->useColors) {
15212 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15214 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15216 if (onmove->useColors) {
15217 SendToProgram(onmove->twoMachinesColor, onmove);
15219 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15220 // SendToProgram("go\n", onmove);
15221 onmove->maybeThinking = TRUE;
15222 SetMachineThinkingEnables();
15226 if(bookHit) { // [HGM] book: simulate book reply
15227 static char bookMove[MSG_SIZ]; // a bit generous?
15229 programStats.nodes = programStats.depth = programStats.time =
15230 programStats.score = programStats.got_only_move = 0;
15231 sprintf(programStats.movelist, "%s (xbook)", bookHit);
15233 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15234 strcat(bookMove, bookHit);
15235 savedMessage = bookMove; // args for deferred call
15236 savedState = onmove;
15237 ScheduleDelayedEvent(DeferredBookMove, 1);
15244 if (gameMode == Training) {
15245 SetTrainingModeOff();
15246 gameMode = PlayFromGameFile;
15247 DisplayMessage("", _("Training mode off"));
15249 gameMode = Training;
15250 animateTraining = appData.animate;
15252 /* make sure we are not already at the end of the game */
15253 if (currentMove < forwardMostMove) {
15254 SetTrainingModeOn();
15255 DisplayMessage("", _("Training mode on"));
15257 gameMode = PlayFromGameFile;
15258 DisplayError(_("Already at end of game"), 0);
15267 if (!appData.icsActive) return;
15268 switch (gameMode) {
15269 case IcsPlayingWhite:
15270 case IcsPlayingBlack:
15273 case BeginningOfGame:
15281 EditPositionDone(TRUE);
15294 gameMode = IcsIdle;
15304 switch (gameMode) {
15306 SetTrainingModeOff();
15308 case MachinePlaysWhite:
15309 case MachinePlaysBlack:
15310 case BeginningOfGame:
15311 SendToProgram("force\n", &first);
15312 if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15313 if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15315 abortEngineThink = TRUE;
15316 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15317 SendToProgram(buf, &first);
15318 DisplayMessage("Aborting engine think", "");
15322 SetUserThinkingEnables();
15324 case PlayFromGameFile:
15325 (void) StopLoadGameTimer();
15326 if (gameFileFP != NULL) {
15331 EditPositionDone(TRUE);
15336 SendToProgram("force\n", &first);
15338 case TwoMachinesPlay:
15339 GameEnds(EndOfFile, NULL, GE_PLAYER);
15340 ResurrectChessProgram();
15341 SetUserThinkingEnables();
15344 ResurrectChessProgram();
15346 case IcsPlayingBlack:
15347 case IcsPlayingWhite:
15348 DisplayError(_("Warning: You are still playing a game"), 0);
15351 DisplayError(_("Warning: You are still observing a game"), 0);
15354 DisplayError(_("Warning: You are still examining a game"), 0);
15365 first.offeredDraw = second.offeredDraw = 0;
15367 if (gameMode == PlayFromGameFile) {
15368 whiteTimeRemaining = timeRemaining[0][currentMove];
15369 blackTimeRemaining = timeRemaining[1][currentMove];
15373 if (gameMode == MachinePlaysWhite ||
15374 gameMode == MachinePlaysBlack ||
15375 gameMode == TwoMachinesPlay ||
15376 gameMode == EndOfGame) {
15377 i = forwardMostMove;
15378 while (i > currentMove) {
15379 SendToProgram("undo\n", &first);
15382 if(!adjustedClock) {
15383 whiteTimeRemaining = timeRemaining[0][currentMove];
15384 blackTimeRemaining = timeRemaining[1][currentMove];
15385 DisplayBothClocks();
15387 if (whiteFlag || blackFlag) {
15388 whiteFlag = blackFlag = 0;
15393 gameMode = EditGame;
15399 EditPositionEvent ()
15402 if (gameMode == EditPosition) {
15408 if (gameMode != EditGame) return;
15410 gameMode = EditPosition;
15413 CopyBoard(rightsBoard, nullBoard);
15414 if (currentMove > 0)
15415 CopyBoard(boards[0], boards[currentMove]);
15416 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15417 rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15419 blackPlaysFirst = !WhiteOnMove(currentMove);
15421 currentMove = forwardMostMove = backwardMostMove = 0;
15422 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15424 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15430 /* [DM] icsEngineAnalyze - possible call from other functions */
15431 if (appData.icsEngineAnalyze) {
15432 appData.icsEngineAnalyze = FALSE;
15434 DisplayMessage("",_("Close ICS engine analyze..."));
15436 if (first.analysisSupport && first.analyzing) {
15437 SendToBoth("exit\n");
15438 first.analyzing = second.analyzing = FALSE;
15440 thinkOutput[0] = NULLCHAR;
15444 EditPositionDone (Boolean fakeRights)
15446 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15448 startedFromSetupPosition = TRUE;
15449 InitChessProgram(&first, FALSE);
15450 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15452 boards[0][EP_STATUS] = EP_NONE;
15453 for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
15454 for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
15455 if(rightsBoard[r][f]) {
15456 ChessSquare p = boards[0][r][f];
15457 if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
15458 else if(p == king) boards[0][CASTLING][2] = f;
15459 else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
15460 else rightsBoard[r][f] = 2; // mark for second pass
15463 for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
15464 if(rightsBoard[r][f] == 2) {
15465 ChessSquare p = boards[0][r][f];
15466 if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
15467 if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
15471 SendToProgram("force\n", &first);
15472 if (blackPlaysFirst) {
15473 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15474 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15475 currentMove = forwardMostMove = backwardMostMove = 1;
15476 CopyBoard(boards[1], boards[0]);
15478 currentMove = forwardMostMove = backwardMostMove = 0;
15480 SendBoard(&first, forwardMostMove);
15481 if (appData.debugMode) {
15482 fprintf(debugFP, "EditPosDone\n");
15485 DisplayMessage("", "");
15486 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15487 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15488 gameMode = EditGame;
15490 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15491 ClearHighlights(); /* [AS] */
15494 /* Pause for `ms' milliseconds */
15495 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15497 TimeDelay (long ms)
15504 } while (SubtractTimeMarks(&m2, &m1) < ms);
15507 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15509 SendMultiLineToICS (char *buf)
15511 char temp[MSG_SIZ+1], *p;
15518 strncpy(temp, buf, len);
15523 if (*p == '\n' || *p == '\r')
15528 strcat(temp, "\n");
15530 SendToPlayer(temp, strlen(temp));
15534 SetWhiteToPlayEvent ()
15536 if (gameMode == EditPosition) {
15537 blackPlaysFirst = FALSE;
15538 DisplayBothClocks(); /* works because currentMove is 0 */
15539 } else if (gameMode == IcsExamining) {
15540 SendToICS(ics_prefix);
15541 SendToICS("tomove white\n");
15546 SetBlackToPlayEvent ()
15548 if (gameMode == EditPosition) {
15549 blackPlaysFirst = TRUE;
15550 currentMove = 1; /* kludge */
15551 DisplayBothClocks();
15553 } else if (gameMode == IcsExamining) {
15554 SendToICS(ics_prefix);
15555 SendToICS("tomove black\n");
15560 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15563 ChessSquare piece = boards[0][y][x];
15564 static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15565 static int lastVariant;
15566 int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15568 if (gameMode != EditPosition && gameMode != IcsExamining) return;
15570 switch (selection) {
15572 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15573 MarkTargetSquares(1);
15574 CopyBoard(currentBoard, boards[0]);
15575 CopyBoard(menuBoard, initialPosition);
15576 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15577 SendToICS(ics_prefix);
15578 SendToICS("bsetup clear\n");
15579 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15580 SendToICS(ics_prefix);
15581 SendToICS("clearboard\n");
15584 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15585 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15586 for (y = 0; y < BOARD_HEIGHT; y++) {
15587 if (gameMode == IcsExamining) {
15588 if (boards[currentMove][y][x] != EmptySquare) {
15589 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15593 } else if(boards[0][y][x] != DarkSquare) {
15594 if(boards[0][y][x] != p) nonEmpty++;
15595 boards[0][y][x] = p;
15599 CopyBoard(rightsBoard, nullBoard);
15600 if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15602 for(r = 0; r < BOARD_HEIGHT; r++) {
15603 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
15604 ChessSquare p = menuBoard[r][x];
15605 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15608 menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
15609 DisplayMessage("Clicking clock again restores position", "");
15610 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15611 if(!nonEmpty) { // asked to clear an empty board
15612 CopyBoard(boards[0], menuBoard);
15614 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15615 CopyBoard(boards[0], initialPosition);
15617 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15618 && !CompareBoards(nullBoard, erasedBoard)) {
15619 CopyBoard(boards[0], erasedBoard);
15621 CopyBoard(erasedBoard, currentBoard);
15623 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15624 rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15627 if (gameMode == EditPosition) {
15628 DrawPosition(FALSE, boards[0]);
15633 SetWhiteToPlayEvent();
15637 SetBlackToPlayEvent();
15641 if (gameMode == IcsExamining) {
15642 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15643 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15646 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15647 if(x == BOARD_LEFT-2) {
15648 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15649 boards[0][y][1] = 0;
15651 if(x == BOARD_RGHT+1) {
15652 if(y >= gameInfo.holdingsSize) break;
15653 boards[0][y][BOARD_WIDTH-2] = 0;
15656 boards[0][y][x] = EmptySquare;
15657 DrawPosition(FALSE, boards[0]);
15662 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15663 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
15664 selection = (ChessSquare) (PROMOTED(piece));
15665 } else if(piece == EmptySquare) selection = WhiteSilver;
15666 else selection = (ChessSquare)((int)piece - 1);
15670 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15671 piece > (int)BlackMan && piece <= (int)BlackKing ) {
15672 selection = (ChessSquare) (DEMOTED(piece));
15673 } else if(piece == EmptySquare) selection = BlackSilver;
15674 else selection = (ChessSquare)((int)piece + 1);
15679 if(gameInfo.variant == VariantShatranj ||
15680 gameInfo.variant == VariantXiangqi ||
15681 gameInfo.variant == VariantCourier ||
15682 gameInfo.variant == VariantASEAN ||
15683 gameInfo.variant == VariantMakruk )
15684 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15690 if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15691 if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15697 if(gameInfo.variant == VariantXiangqi)
15698 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15699 if(gameInfo.variant == VariantKnightmate)
15700 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15701 if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15704 if (gameMode == IcsExamining) {
15705 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15706 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15707 PieceToChar(selection), AAA + x, ONE + y);
15710 rightsBoard[y][x] = hasRights;
15711 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15713 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15714 n = PieceToNumber(selection - BlackPawn);
15715 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15716 boards[0][BOARD_HEIGHT-1-n][0] = selection;
15717 boards[0][BOARD_HEIGHT-1-n][1]++;
15719 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15720 n = PieceToNumber(selection);
15721 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15722 boards[0][n][BOARD_WIDTH-1] = selection;
15723 boards[0][n][BOARD_WIDTH-2]++;
15726 boards[0][y][x] = selection;
15727 DrawPosition(TRUE, boards[0]);
15729 fromX = fromY = -1;
15737 DropMenuEvent (ChessSquare selection, int x, int y)
15739 ChessMove moveType;
15741 switch (gameMode) {
15742 case IcsPlayingWhite:
15743 case MachinePlaysBlack:
15744 if (!WhiteOnMove(currentMove)) {
15745 DisplayMoveError(_("It is Black's turn"));
15748 moveType = WhiteDrop;
15750 case IcsPlayingBlack:
15751 case MachinePlaysWhite:
15752 if (WhiteOnMove(currentMove)) {
15753 DisplayMoveError(_("It is White's turn"));
15756 moveType = BlackDrop;
15759 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15765 if (moveType == BlackDrop && selection < BlackPawn) {
15766 selection = (ChessSquare) ((int) selection
15767 + (int) BlackPawn - (int) WhitePawn);
15769 if (boards[currentMove][y][x] != EmptySquare) {
15770 DisplayMoveError(_("That square is occupied"));
15774 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15780 /* Accept a pending offer of any kind from opponent */
15782 if (appData.icsActive) {
15783 SendToICS(ics_prefix);
15784 SendToICS("accept\n");
15785 } else if (cmailMsgLoaded) {
15786 if (currentMove == cmailOldMove &&
15787 commentList[cmailOldMove] != NULL &&
15788 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15789 "Black offers a draw" : "White offers a draw")) {
15791 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15792 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15794 DisplayError(_("There is no pending offer on this move"), 0);
15795 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15798 /* Not used for offers from chess program */
15805 /* Decline a pending offer of any kind from opponent */
15807 if (appData.icsActive) {
15808 SendToICS(ics_prefix);
15809 SendToICS("decline\n");
15810 } else if (cmailMsgLoaded) {
15811 if (currentMove == cmailOldMove &&
15812 commentList[cmailOldMove] != NULL &&
15813 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15814 "Black offers a draw" : "White offers a draw")) {
15816 AppendComment(cmailOldMove, "Draw declined", TRUE);
15817 DisplayComment(cmailOldMove - 1, "Draw declined");
15820 DisplayError(_("There is no pending offer on this move"), 0);
15823 /* Not used for offers from chess program */
15830 /* Issue ICS rematch command */
15831 if (appData.icsActive) {
15832 SendToICS(ics_prefix);
15833 SendToICS("rematch\n");
15840 /* Call your opponent's flag (claim a win on time) */
15841 if (appData.icsActive) {
15842 SendToICS(ics_prefix);
15843 SendToICS("flag\n");
15845 switch (gameMode) {
15848 case MachinePlaysWhite:
15851 GameEnds(GameIsDrawn, "Both players ran out of time",
15854 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15856 DisplayError(_("Your opponent is not out of time"), 0);
15859 case MachinePlaysBlack:
15862 GameEnds(GameIsDrawn, "Both players ran out of time",
15865 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15867 DisplayError(_("Your opponent is not out of time"), 0);
15875 ClockClick (int which)
15876 { // [HGM] code moved to back-end from winboard.c
15877 if(which) { // black clock
15878 if (gameMode == EditPosition || gameMode == IcsExamining) {
15879 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15880 SetBlackToPlayEvent();
15881 } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15882 gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15883 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15884 } else if (shiftKey) {
15885 AdjustClock(which, -1);
15886 } else if (gameMode == IcsPlayingWhite ||
15887 gameMode == MachinePlaysBlack) {
15890 } else { // white clock
15891 if (gameMode == EditPosition || gameMode == IcsExamining) {
15892 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15893 SetWhiteToPlayEvent();
15894 } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15895 gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15896 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15897 } else if (shiftKey) {
15898 AdjustClock(which, -1);
15899 } else if (gameMode == IcsPlayingBlack ||
15900 gameMode == MachinePlaysWhite) {
15909 /* Offer draw or accept pending draw offer from opponent */
15911 if (appData.icsActive) {
15912 /* Note: tournament rules require draw offers to be
15913 made after you make your move but before you punch
15914 your clock. Currently ICS doesn't let you do that;
15915 instead, you immediately punch your clock after making
15916 a move, but you can offer a draw at any time. */
15918 SendToICS(ics_prefix);
15919 SendToICS("draw\n");
15920 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15921 } else if (cmailMsgLoaded) {
15922 if (currentMove == cmailOldMove &&
15923 commentList[cmailOldMove] != NULL &&
15924 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15925 "Black offers a draw" : "White offers a draw")) {
15926 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15927 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15928 } else if (currentMove == cmailOldMove + 1) {
15929 char *offer = WhiteOnMove(cmailOldMove) ?
15930 "White offers a draw" : "Black offers a draw";
15931 AppendComment(currentMove, offer, TRUE);
15932 DisplayComment(currentMove - 1, offer);
15933 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15935 DisplayError(_("You must make your move before offering a draw"), 0);
15936 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15938 } else if (first.offeredDraw) {
15939 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15941 if (first.sendDrawOffers) {
15942 SendToProgram("draw\n", &first);
15943 userOfferedDraw = TRUE;
15951 /* Offer Adjourn or accept pending Adjourn offer from opponent */
15953 if (appData.icsActive) {
15954 SendToICS(ics_prefix);
15955 SendToICS("adjourn\n");
15957 /* Currently GNU Chess doesn't offer or accept Adjourns */
15965 /* Offer Abort or accept pending Abort offer from opponent */
15967 if (appData.icsActive) {
15968 SendToICS(ics_prefix);
15969 SendToICS("abort\n");
15971 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15978 /* Resign. You can do this even if it's not your turn. */
15980 if (appData.icsActive) {
15981 SendToICS(ics_prefix);
15982 SendToICS("resign\n");
15984 switch (gameMode) {
15985 case MachinePlaysWhite:
15986 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15988 case MachinePlaysBlack:
15989 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15992 if (cmailMsgLoaded) {
15994 if (WhiteOnMove(cmailOldMove)) {
15995 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15997 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15999 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
16010 StopObservingEvent ()
16012 /* Stop observing current games */
16013 SendToICS(ics_prefix);
16014 SendToICS("unobserve\n");
16018 StopExaminingEvent ()
16020 /* Stop observing current game */
16021 SendToICS(ics_prefix);
16022 SendToICS("unexamine\n");
16026 ForwardInner (int target)
16028 int limit; int oldSeekGraphUp = seekGraphUp;
16030 if (appData.debugMode)
16031 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
16032 target, currentMove, forwardMostMove);
16034 if (gameMode == EditPosition)
16037 seekGraphUp = FALSE;
16038 MarkTargetSquares(1);
16039 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16041 if (gameMode == PlayFromGameFile && !pausing)
16044 if (gameMode == IcsExamining && pausing)
16045 limit = pauseExamForwardMostMove;
16047 limit = forwardMostMove;
16049 if (target > limit) target = limit;
16051 if (target > 0 && moveList[target - 1][0]) {
16052 int fromX, fromY, toX, toY;
16053 toX = moveList[target - 1][2] - AAA;
16054 toY = moveList[target - 1][3] - ONE;
16055 if (moveList[target - 1][1] == '@') {
16056 if (appData.highlightLastMove) {
16057 SetHighlights(-1, -1, toX, toY);
16060 fromX = moveList[target - 1][0] - AAA;
16061 fromY = moveList[target - 1][1] - ONE;
16062 if (target == currentMove + 1) {
16063 if(moveList[target - 1][4] == ';') { // multi-leg
16064 killX = moveList[target - 1][5] - AAA;
16065 killY = moveList[target - 1][6] - ONE;
16067 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
16068 killX = killY = -1;
16070 if (appData.highlightLastMove) {
16071 SetHighlights(fromX, fromY, toX, toY);
16075 if (gameMode == EditGame || gameMode == AnalyzeMode ||
16076 gameMode == Training || gameMode == PlayFromGameFile ||
16077 gameMode == AnalyzeFile) {
16078 while (currentMove < target) {
16079 if(second.analyzing) SendMoveToProgram(currentMove, &second);
16080 SendMoveToProgram(currentMove++, &first);
16083 currentMove = target;
16086 if (gameMode == EditGame || gameMode == EndOfGame) {
16087 whiteTimeRemaining = timeRemaining[0][currentMove];
16088 blackTimeRemaining = timeRemaining[1][currentMove];
16090 DisplayBothClocks();
16091 DisplayMove(currentMove - 1);
16092 DrawPosition(oldSeekGraphUp, boards[currentMove]);
16093 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16094 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
16095 DisplayComment(currentMove - 1, commentList[currentMove]);
16097 ClearMap(); // [HGM] exclude: invalidate map
16104 if (gameMode == IcsExamining && !pausing) {
16105 SendToICS(ics_prefix);
16106 SendToICS("forward\n");
16108 ForwardInner(currentMove + 1);
16115 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16116 /* to optimze, we temporarily turn off analysis mode while we feed
16117 * the remaining moves to the engine. Otherwise we get analysis output
16120 if (first.analysisSupport) {
16121 SendToProgram("exit\nforce\n", &first);
16122 first.analyzing = FALSE;
16126 if (gameMode == IcsExamining && !pausing) {
16127 SendToICS(ics_prefix);
16128 SendToICS("forward 999999\n");
16130 ForwardInner(forwardMostMove);
16133 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16134 /* we have fed all the moves, so reactivate analysis mode */
16135 SendToProgram("analyze\n", &first);
16136 first.analyzing = TRUE;
16137 /*first.maybeThinking = TRUE;*/
16138 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16143 BackwardInner (int target)
16145 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16147 if (appData.debugMode)
16148 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16149 target, currentMove, forwardMostMove);
16151 if (gameMode == EditPosition) return;
16152 seekGraphUp = FALSE;
16153 MarkTargetSquares(1);
16154 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16155 if (currentMove <= backwardMostMove) {
16157 DrawPosition(full_redraw, boards[currentMove]);
16160 if (gameMode == PlayFromGameFile && !pausing)
16163 if (moveList[target][0]) {
16164 int fromX, fromY, toX, toY;
16165 toX = moveList[target][2] - AAA;
16166 toY = moveList[target][3] - ONE;
16167 if (moveList[target][1] == '@') {
16168 if (appData.highlightLastMove) {
16169 SetHighlights(-1, -1, toX, toY);
16172 fromX = moveList[target][0] - AAA;
16173 fromY = moveList[target][1] - ONE;
16174 if (target == currentMove - 1) {
16175 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16177 if (appData.highlightLastMove) {
16178 SetHighlights(fromX, fromY, toX, toY);
16182 if (gameMode == EditGame || gameMode==AnalyzeMode ||
16183 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16184 while (currentMove > target) {
16185 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16186 // null move cannot be undone. Reload program with move history before it.
16188 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16189 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16191 SendBoard(&first, i);
16192 if(second.analyzing) SendBoard(&second, i);
16193 for(currentMove=i; currentMove<target; currentMove++) {
16194 SendMoveToProgram(currentMove, &first);
16195 if(second.analyzing) SendMoveToProgram(currentMove, &second);
16199 SendToBoth("undo\n");
16203 currentMove = target;
16206 if (gameMode == EditGame || gameMode == EndOfGame) {
16207 whiteTimeRemaining = timeRemaining[0][currentMove];
16208 blackTimeRemaining = timeRemaining[1][currentMove];
16210 DisplayBothClocks();
16211 DisplayMove(currentMove - 1);
16212 DrawPosition(full_redraw, boards[currentMove]);
16213 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16214 // [HGM] PV info: routine tests if comment empty
16215 DisplayComment(currentMove - 1, commentList[currentMove]);
16216 ClearMap(); // [HGM] exclude: invalidate map
16222 if (gameMode == IcsExamining && !pausing) {
16223 SendToICS(ics_prefix);
16224 SendToICS("backward\n");
16226 BackwardInner(currentMove - 1);
16233 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16234 /* to optimize, we temporarily turn off analysis mode while we undo
16235 * all the moves. Otherwise we get analysis output after each undo.
16237 if (first.analysisSupport) {
16238 SendToProgram("exit\nforce\n", &first);
16239 first.analyzing = FALSE;
16243 if (gameMode == IcsExamining && !pausing) {
16244 SendToICS(ics_prefix);
16245 SendToICS("backward 999999\n");
16247 BackwardInner(backwardMostMove);
16250 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16251 /* we have fed all the moves, so reactivate analysis mode */
16252 SendToProgram("analyze\n", &first);
16253 first.analyzing = TRUE;
16254 /*first.maybeThinking = TRUE;*/
16255 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16262 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16263 if (to >= forwardMostMove) to = forwardMostMove;
16264 if (to <= backwardMostMove) to = backwardMostMove;
16265 if (to < currentMove) {
16273 RevertEvent (Boolean annotate)
16275 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16278 if (gameMode != IcsExamining) {
16279 DisplayError(_("You are not examining a game"), 0);
16283 DisplayError(_("You can't revert while pausing"), 0);
16286 SendToICS(ics_prefix);
16287 SendToICS("revert\n");
16291 RetractMoveEvent ()
16293 switch (gameMode) {
16294 case MachinePlaysWhite:
16295 case MachinePlaysBlack:
16296 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16297 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16300 if (forwardMostMove < 2) return;
16301 currentMove = forwardMostMove = forwardMostMove - 2;
16302 whiteTimeRemaining = timeRemaining[0][currentMove];
16303 blackTimeRemaining = timeRemaining[1][currentMove];
16304 DisplayBothClocks();
16305 DisplayMove(currentMove - 1);
16306 ClearHighlights();/*!! could figure this out*/
16307 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16308 SendToProgram("remove\n", &first);
16309 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16312 case BeginningOfGame:
16316 case IcsPlayingWhite:
16317 case IcsPlayingBlack:
16318 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16319 SendToICS(ics_prefix);
16320 SendToICS("takeback 2\n");
16322 SendToICS(ics_prefix);
16323 SendToICS("takeback 1\n");
16332 ChessProgramState *cps;
16334 switch (gameMode) {
16335 case MachinePlaysWhite:
16336 if (!WhiteOnMove(forwardMostMove)) {
16337 DisplayError(_("It is your turn"), 0);
16342 case MachinePlaysBlack:
16343 if (WhiteOnMove(forwardMostMove)) {
16344 DisplayError(_("It is your turn"), 0);
16349 case TwoMachinesPlay:
16350 if (WhiteOnMove(forwardMostMove) ==
16351 (first.twoMachinesColor[0] == 'w')) {
16357 case BeginningOfGame:
16361 SendToProgram("?\n", cps);
16365 TruncateGameEvent ()
16368 if (gameMode != EditGame) return;
16375 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16376 if (forwardMostMove > currentMove) {
16377 if (gameInfo.resultDetails != NULL) {
16378 free(gameInfo.resultDetails);
16379 gameInfo.resultDetails = NULL;
16380 gameInfo.result = GameUnfinished;
16382 forwardMostMove = currentMove;
16383 HistorySet(parseList, backwardMostMove, forwardMostMove,
16391 if (appData.noChessProgram) return;
16392 switch (gameMode) {
16393 case MachinePlaysWhite:
16394 if (WhiteOnMove(forwardMostMove)) {
16395 DisplayError(_("Wait until your turn."), 0);
16399 case BeginningOfGame:
16400 case MachinePlaysBlack:
16401 if (!WhiteOnMove(forwardMostMove)) {
16402 DisplayError(_("Wait until your turn."), 0);
16407 DisplayError(_("No hint available"), 0);
16410 SendToProgram("hint\n", &first);
16411 hintRequested = TRUE;
16415 SaveSelected (FILE *g, int dummy, char *dummy2)
16417 ListGame * lg = (ListGame *) gameList.head;
16421 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16422 DisplayError(_("Game list not loaded or empty"), 0);
16426 creatingBook = TRUE; // suppresses stuff during load game
16428 /* Get list size */
16429 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16430 if(lg->position >= 0) { // selected?
16431 LoadGame(f, nItem, "", TRUE);
16432 SaveGamePGN2(g); // leaves g open
16435 lg = (ListGame *) lg->node.succ;
16439 creatingBook = FALSE;
16447 ListGame * lg = (ListGame *) gameList.head;
16450 static int secondTime = FALSE;
16452 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16453 DisplayError(_("Game list not loaded or empty"), 0);
16457 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16460 DisplayNote(_("Book file exists! Try again for overwrite."));
16464 creatingBook = TRUE;
16465 secondTime = FALSE;
16467 /* Get list size */
16468 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16469 if(lg->position >= 0) {
16470 LoadGame(f, nItem, "", TRUE);
16471 AddGameToBook(TRUE);
16474 lg = (ListGame *) lg->node.succ;
16477 creatingBook = FALSE;
16484 if (appData.noChessProgram) return;
16485 switch (gameMode) {
16486 case MachinePlaysWhite:
16487 if (WhiteOnMove(forwardMostMove)) {
16488 DisplayError(_("Wait until your turn."), 0);
16492 case BeginningOfGame:
16493 case MachinePlaysBlack:
16494 if (!WhiteOnMove(forwardMostMove)) {
16495 DisplayError(_("Wait until your turn."), 0);
16500 EditPositionDone(TRUE);
16502 case TwoMachinesPlay:
16507 SendToProgram("bk\n", &first);
16508 bookOutput[0] = NULLCHAR;
16509 bookRequested = TRUE;
16515 char *tags = PGNTags(&gameInfo);
16516 TagsPopUp(tags, CmailMsg());
16520 /* end button procedures */
16523 PrintPosition (FILE *fp, int move)
16527 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16528 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16529 char c = PieceToChar(boards[move][i][j]);
16530 fputc(c == '?' ? '.' : c, fp);
16531 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16534 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16535 fprintf(fp, "white to play\n");
16537 fprintf(fp, "black to play\n");
16541 PrintOpponents (FILE *fp)
16543 if (gameInfo.white != NULL) {
16544 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16550 /* Find last component of program's own name, using some heuristics */
16552 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16555 int local = (strcmp(host, "localhost") == 0);
16556 while (!local && (p = strchr(prog, ';')) != NULL) {
16558 while (*p == ' ') p++;
16561 if (*prog == '"' || *prog == '\'') {
16562 q = strchr(prog + 1, *prog);
16564 q = strchr(prog, ' ');
16566 if (q == NULL) q = prog + strlen(prog);
16568 while (p >= prog && *p != '/' && *p != '\\') p--;
16570 if(p == prog && *p == '"') p++;
16572 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16573 memcpy(buf, p, q - p);
16574 buf[q - p] = NULLCHAR;
16582 TimeControlTagValue ()
16585 if (!appData.clockMode) {
16586 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16587 } else if (movesPerSession > 0) {
16588 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16589 } else if (timeIncrement == 0) {
16590 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16592 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16594 return StrSave(buf);
16600 /* This routine is used only for certain modes */
16601 VariantClass v = gameInfo.variant;
16602 ChessMove r = GameUnfinished;
16605 if(keepInfo) return;
16607 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16608 r = gameInfo.result;
16609 p = gameInfo.resultDetails;
16610 gameInfo.resultDetails = NULL;
16612 ClearGameInfo(&gameInfo);
16613 gameInfo.variant = v;
16615 switch (gameMode) {
16616 case MachinePlaysWhite:
16617 gameInfo.event = StrSave( appData.pgnEventHeader );
16618 gameInfo.site = StrSave(HostName());
16619 gameInfo.date = PGNDate();
16620 gameInfo.round = StrSave("-");
16621 gameInfo.white = StrSave(first.tidy);
16622 gameInfo.black = StrSave(UserName());
16623 gameInfo.timeControl = TimeControlTagValue();
16626 case MachinePlaysBlack:
16627 gameInfo.event = StrSave( appData.pgnEventHeader );
16628 gameInfo.site = StrSave(HostName());
16629 gameInfo.date = PGNDate();
16630 gameInfo.round = StrSave("-");
16631 gameInfo.white = StrSave(UserName());
16632 gameInfo.black = StrSave(first.tidy);
16633 gameInfo.timeControl = TimeControlTagValue();
16636 case TwoMachinesPlay:
16637 gameInfo.event = StrSave( appData.pgnEventHeader );
16638 gameInfo.site = StrSave(HostName());
16639 gameInfo.date = PGNDate();
16642 snprintf(buf, MSG_SIZ, "%d", roundNr);
16643 gameInfo.round = StrSave(buf);
16645 gameInfo.round = StrSave("-");
16647 if (first.twoMachinesColor[0] == 'w') {
16648 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16649 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16651 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16652 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16654 gameInfo.timeControl = TimeControlTagValue();
16658 gameInfo.event = StrSave("Edited game");
16659 gameInfo.site = StrSave(HostName());
16660 gameInfo.date = PGNDate();
16661 gameInfo.round = StrSave("-");
16662 gameInfo.white = StrSave("-");
16663 gameInfo.black = StrSave("-");
16664 gameInfo.result = r;
16665 gameInfo.resultDetails = p;
16669 gameInfo.event = StrSave("Edited position");
16670 gameInfo.site = StrSave(HostName());
16671 gameInfo.date = PGNDate();
16672 gameInfo.round = StrSave("-");
16673 gameInfo.white = StrSave("-");
16674 gameInfo.black = StrSave("-");
16677 case IcsPlayingWhite:
16678 case IcsPlayingBlack:
16683 case PlayFromGameFile:
16684 gameInfo.event = StrSave("Game from non-PGN file");
16685 gameInfo.site = StrSave(HostName());
16686 gameInfo.date = PGNDate();
16687 gameInfo.round = StrSave("-");
16688 gameInfo.white = StrSave("?");
16689 gameInfo.black = StrSave("?");
16698 ReplaceComment (int index, char *text)
16704 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16705 pvInfoList[index-1].depth == len &&
16706 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16707 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16708 while (*text == '\n') text++;
16709 len = strlen(text);
16710 while (len > 0 && text[len - 1] == '\n') len--;
16712 if (commentList[index] != NULL)
16713 free(commentList[index]);
16716 commentList[index] = NULL;
16719 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16720 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16721 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16722 commentList[index] = (char *) malloc(len + 2);
16723 strncpy(commentList[index], text, len);
16724 commentList[index][len] = '\n';
16725 commentList[index][len + 1] = NULLCHAR;
16727 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16729 commentList[index] = (char *) malloc(len + 7);
16730 safeStrCpy(commentList[index], "{\n", 3);
16731 safeStrCpy(commentList[index]+2, text, len+1);
16732 commentList[index][len+2] = NULLCHAR;
16733 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16734 strcat(commentList[index], "\n}\n");
16739 CrushCRs (char *text)
16747 if (ch == '\r') continue;
16749 } while (ch != '\0');
16753 AppendComment (int index, char *text, Boolean addBraces)
16754 /* addBraces tells if we should add {} */
16759 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16760 if(addBraces == 3) addBraces = 0; else // force appending literally
16761 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16764 while (*text == '\n') text++;
16765 len = strlen(text);
16766 while (len > 0 && text[len - 1] == '\n') len--;
16767 text[len] = NULLCHAR;
16769 if (len == 0) return;
16771 if (commentList[index] != NULL) {
16772 Boolean addClosingBrace = addBraces;
16773 old = commentList[index];
16774 oldlen = strlen(old);
16775 while(commentList[index][oldlen-1] == '\n')
16776 commentList[index][--oldlen] = NULLCHAR;
16777 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16778 safeStrCpy(commentList[index], old, oldlen + len + 6);
16780 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16781 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16782 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16783 while (*text == '\n') { text++; len--; }
16784 commentList[index][--oldlen] = NULLCHAR;
16786 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16787 else strcat(commentList[index], "\n");
16788 strcat(commentList[index], text);
16789 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16790 else strcat(commentList[index], "\n");
16792 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16794 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16795 else commentList[index][0] = NULLCHAR;
16796 strcat(commentList[index], text);
16797 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16798 if(addBraces == TRUE) strcat(commentList[index], "}\n");
16803 FindStr (char * text, char * sub_text)
16805 char * result = strstr( text, sub_text );
16807 if( result != NULL ) {
16808 result += strlen( sub_text );
16814 /* [AS] Try to extract PV info from PGN comment */
16815 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16817 GetInfoFromComment (int index, char * text)
16819 char * sep = text, *p;
16821 if( text != NULL && index > 0 ) {
16824 int time = -1, sec = 0, deci;
16825 char * s_eval = FindStr( text, "[%eval " );
16826 char * s_emt = FindStr( text, "[%emt " );
16828 if( s_eval != NULL || s_emt != NULL ) {
16830 if(0) { // [HGM] this code is not finished, and could actually be detrimental
16835 if( s_eval != NULL ) {
16836 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16840 if( delim != ']' ) {
16845 if( s_emt != NULL ) {
16850 /* We expect something like: [+|-]nnn.nn/dd */
16853 if(*text != '{') return text; // [HGM] braces: must be normal comment
16855 sep = strchr( text, '/' );
16856 if( sep == NULL || sep < (text+4) ) {
16861 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16862 if(p[1] == '(') { // comment starts with PV
16863 p = strchr(p, ')'); // locate end of PV
16864 if(p == NULL || sep < p+5) return text;
16865 // at this point we have something like "{(.*) +0.23/6 ..."
16866 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16867 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16868 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16870 time = -1; sec = -1; deci = -1;
16871 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16872 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16873 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16874 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
16878 if( score_lo < 0 || score_lo >= 100 ) {
16882 if(sec >= 0) time = 600*time + 10*sec; else
16883 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16885 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16887 /* [HGM] PV time: now locate end of PV info */
16888 while( *++sep >= '0' && *sep <= '9'); // strip depth
16890 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16892 while( *++sep >= '0' && *sep <= '9'); // strip seconds
16894 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16895 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16906 pvInfoList[index-1].depth = depth;
16907 pvInfoList[index-1].score = score;
16908 pvInfoList[index-1].time = 10*time; // centi-sec
16909 if(*sep == '}') *sep = 0; else *--sep = '{';
16911 while(*p++ = *sep++)
16914 } // squeeze out space between PV and comment, and return both
16920 SendToProgram (char *message, ChessProgramState *cps)
16922 int count, outCount, error;
16925 if (cps->pr == NoProc) return;
16928 if (appData.debugMode) {
16931 fprintf(debugFP, "%ld >%-6s: %s",
16932 SubtractTimeMarks(&now, &programStartTime),
16933 cps->which, message);
16935 fprintf(serverFP, "%ld >%-6s: %s",
16936 SubtractTimeMarks(&now, &programStartTime),
16937 cps->which, message), fflush(serverFP);
16940 count = strlen(message);
16941 outCount = OutputToProcess(cps->pr, message, count, &error);
16942 if (outCount < count && !exiting
16943 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16944 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16945 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16946 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16947 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16948 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16949 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16950 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16952 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16953 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16954 gameInfo.result = res;
16956 gameInfo.resultDetails = StrSave(buf);
16958 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16959 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16964 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16968 ChessProgramState *cps = (ChessProgramState *)closure;
16970 if (isr != cps->isr) return; /* Killed intentionally */
16973 RemoveInputSource(cps->isr);
16974 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16975 _(cps->which), cps->program);
16976 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16977 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16978 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16979 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16980 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16981 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16983 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16984 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16985 gameInfo.result = res;
16987 gameInfo.resultDetails = StrSave(buf);
16989 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16990 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16992 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16993 _(cps->which), cps->program);
16994 RemoveInputSource(cps->isr);
16996 /* [AS] Program is misbehaving badly... kill it */
16997 if( count == -2 ) {
16998 DestroyChildProcess( cps->pr, 9 );
17002 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
17007 if ((end_str = strchr(message, '\r')) != NULL)
17008 *end_str = NULLCHAR;
17009 if ((end_str = strchr(message, '\n')) != NULL)
17010 *end_str = NULLCHAR;
17012 if (appData.debugMode) {
17013 TimeMark now; int print = 1;
17014 char *quote = ""; char c; int i;
17016 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
17017 char start = message[0];
17018 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
17019 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
17020 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
17021 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
17022 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
17023 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
17024 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
17025 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
17026 sscanf(message, "hint: %c", &c)!=1 &&
17027 sscanf(message, "pong %c", &c)!=1 && start != '#') {
17028 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
17029 print = (appData.engineComments >= 2);
17031 message[0] = start; // restore original message
17035 fprintf(debugFP, "%ld <%-6s: %s%s\n",
17036 SubtractTimeMarks(&now, &programStartTime), cps->which,
17040 fprintf(serverFP, "%ld <%-6s: %s%s\n",
17041 SubtractTimeMarks(&now, &programStartTime), cps->which,
17043 message), fflush(serverFP);
17047 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
17048 if (appData.icsEngineAnalyze) {
17049 if (strstr(message, "whisper") != NULL ||
17050 strstr(message, "kibitz") != NULL ||
17051 strstr(message, "tellics") != NULL) return;
17054 HandleMachineMove(message, cps);
17059 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
17064 if( timeControl_2 > 0 ) {
17065 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
17066 tc = timeControl_2;
17069 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
17070 inc /= cps->timeOdds;
17071 st /= cps->timeOdds;
17073 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
17076 /* Set exact time per move, normally using st command */
17077 if (cps->stKludge) {
17078 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
17080 if (seconds == 0) {
17081 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
17083 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
17086 snprintf(buf, MSG_SIZ, "st %d\n", st);
17089 /* Set conventional or incremental time control, using level command */
17090 if (seconds == 0) {
17091 /* Note old gnuchess bug -- minutes:seconds used to not work.
17092 Fixed in later versions, but still avoid :seconds
17093 when seconds is 0. */
17094 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
17096 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
17097 seconds, inc/1000.);
17100 SendToProgram(buf, cps);
17102 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17103 /* Orthogonally, limit search to given depth */
17105 if (cps->sdKludge) {
17106 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17108 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17110 SendToProgram(buf, cps);
17113 if(cps->nps >= 0) { /* [HGM] nps */
17114 if(cps->supportsNPS == FALSE)
17115 cps->nps = -1; // don't use if engine explicitly says not supported!
17117 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17118 SendToProgram(buf, cps);
17123 ChessProgramState *
17125 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17127 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17128 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17134 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17136 char message[MSG_SIZ];
17139 /* Note: this routine must be called when the clocks are stopped
17140 or when they have *just* been set or switched; otherwise
17141 it will be off by the time since the current tick started.
17143 if (machineWhite) {
17144 time = whiteTimeRemaining / 10;
17145 otime = blackTimeRemaining / 10;
17147 time = blackTimeRemaining / 10;
17148 otime = whiteTimeRemaining / 10;
17150 /* [HGM] translate opponent's time by time-odds factor */
17151 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17153 if (time <= 0) time = 1;
17154 if (otime <= 0) otime = 1;
17156 snprintf(message, MSG_SIZ, "time %ld\n", time);
17157 SendToProgram(message, cps);
17159 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17160 SendToProgram(message, cps);
17164 EngineDefinedVariant (ChessProgramState *cps, int n)
17165 { // return name of n-th unknown variant that engine supports
17166 static char buf[MSG_SIZ];
17167 char *p, *s = cps->variants;
17168 if(!s) return NULL;
17169 do { // parse string from variants feature
17171 p = strchr(s, ',');
17172 if(p) *p = NULLCHAR;
17173 v = StringToVariant(s);
17174 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17175 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17176 if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17177 !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17178 !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17179 !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17180 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17183 if(n < 0) return buf;
17189 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17192 int len = strlen(name);
17195 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17197 sscanf(*p, "%d", &val);
17199 while (**p && **p != ' ')
17201 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17202 SendToProgram(buf, cps);
17209 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17212 int len = strlen(name);
17213 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17215 sscanf(*p, "%d", loc);
17216 while (**p && **p != ' ') (*p)++;
17217 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17218 SendToProgram(buf, cps);
17225 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17228 int len = strlen(name);
17229 if (strncmp((*p), name, len) == 0
17230 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17232 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
17233 FREE(*loc); *loc = malloc(len);
17234 strncpy(*loc, *p, len);
17235 sscanf(*p, "%[^\"]", *loc); // should always fit, because we allocated at least strlen(*p)
17236 while (**p && **p != '\"') (*p)++;
17237 if (**p == '\"') (*p)++;
17238 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17239 SendToProgram(buf, cps);
17246 ParseOption (Option *opt, ChessProgramState *cps)
17247 // [HGM] options: process the string that defines an engine option, and determine
17248 // name, type, default value, and allowed value range
17250 char *p, *q, buf[MSG_SIZ];
17251 int n, min = (-1)<<31, max = 1<<31, def;
17253 opt->target = &opt->value; // OK for spin/slider and checkbox
17254 if(p = strstr(opt->name, " -spin ")) {
17255 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17256 if(max < min) max = min; // enforce consistency
17257 if(def < min) def = min;
17258 if(def > max) def = max;
17263 } else if((p = strstr(opt->name, " -slider "))) {
17264 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17265 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17266 if(max < min) max = min; // enforce consistency
17267 if(def < min) def = min;
17268 if(def > max) def = max;
17272 opt->type = Spin; // Slider;
17273 } else if((p = strstr(opt->name, " -string "))) {
17274 opt->textValue = p+9;
17275 opt->type = TextBox;
17276 opt->target = &opt->textValue;
17277 } else if((p = strstr(opt->name, " -file "))) {
17278 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17279 opt->target = opt->textValue = p+7;
17280 opt->type = FileName; // FileName;
17281 opt->target = &opt->textValue;
17282 } else if((p = strstr(opt->name, " -path "))) {
17283 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17284 opt->target = opt->textValue = p+7;
17285 opt->type = PathName; // PathName;
17286 opt->target = &opt->textValue;
17287 } else if(p = strstr(opt->name, " -check ")) {
17288 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17289 opt->value = (def != 0);
17290 opt->type = CheckBox;
17291 } else if(p = strstr(opt->name, " -combo ")) {
17292 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17293 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17294 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17295 opt->value = n = 0;
17296 while(q = StrStr(q, " /// ")) {
17297 n++; *q = 0; // count choices, and null-terminate each of them
17299 if(*q == '*') { // remember default, which is marked with * prefix
17303 cps->comboList[cps->comboCnt++] = q;
17305 cps->comboList[cps->comboCnt++] = NULL;
17307 opt->type = ComboBox;
17308 } else if(p = strstr(opt->name, " -button")) {
17309 opt->type = Button;
17310 } else if(p = strstr(opt->name, " -save")) {
17311 opt->type = SaveButton;
17312 } else return FALSE;
17313 *p = 0; // terminate option name
17314 // now look if the command-line options define a setting for this engine option.
17315 if(cps->optionSettings && cps->optionSettings[0])
17316 p = strstr(cps->optionSettings, opt->name); else p = NULL;
17317 if(p && (p == cps->optionSettings || p[-1] == ',')) {
17318 snprintf(buf, MSG_SIZ, "option %s", p);
17319 if(p = strstr(buf, ",")) *p = 0;
17320 if(q = strchr(buf, '=')) switch(opt->type) {
17322 for(n=0; n<opt->max; n++)
17323 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17326 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17330 opt->value = atoi(q+1);
17335 SendToProgram(buf, cps);
17337 *(int*) (opt->name + MSG_SIZ - 104) = opt->value; // hide default values somewhere
17338 if(opt->target == &opt->textValue) strncpy(opt->name + MSG_SIZ - 100, opt->textValue, 99);
17343 FeatureDone (ChessProgramState *cps, int val)
17345 DelayedEventCallback cb = GetDelayedEvent();
17346 if ((cb == InitBackEnd3 && cps == &first) ||
17347 (cb == SettingsMenuIfReady && cps == &second) ||
17348 (cb == LoadEngine) ||
17349 (cb == TwoMachinesEventIfReady)) {
17350 CancelDelayedEvent();
17351 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17352 } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17353 cps->initDone = val;
17354 if(val) cps->reload = FALSE, RefreshSettingsDialog(cps, val);
17357 /* Parse feature command from engine */
17359 ParseFeatures (char *args, ChessProgramState *cps)
17367 while (*p == ' ') p++;
17368 if (*p == NULLCHAR) return;
17370 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17371 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17372 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17373 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17374 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17375 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17376 if (BoolFeature(&p, "reuse", &val, cps)) {
17377 /* Engine can disable reuse, but can't enable it if user said no */
17378 if (!val) cps->reuse = FALSE;
17381 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17382 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17383 if (gameMode == TwoMachinesPlay) {
17384 DisplayTwoMachinesTitle();
17390 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17391 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17392 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17393 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17394 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17395 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17396 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17397 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17398 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17399 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17400 if (IntFeature(&p, "done", &val, cps)) {
17401 FeatureDone(cps, val);
17404 /* Added by Tord: */
17405 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17406 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17407 /* End of additions by Tord */
17409 /* [HGM] added features: */
17410 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17411 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17412 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17413 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17414 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17415 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17416 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17417 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17418 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17419 if(cps->nrOptions == 0) { ASSIGN(cps->option[0].name, _("Make Persistent -save")); ParseOption(&(cps->option[cps->nrOptions++]), cps); }
17420 FREE(cps->option[cps->nrOptions].name);
17421 cps->option[cps->nrOptions].name = q; q = NULL;
17422 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17423 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17424 SendToProgram(buf, cps);
17427 if(cps->nrOptions >= MAX_OPTIONS) {
17429 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17430 DisplayError(buf, 0);
17434 /* End of additions by HGM */
17436 /* unknown feature: complain and skip */
17438 while (*q && *q != '=') q++;
17439 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17440 SendToProgram(buf, cps);
17446 while (*p && *p != '\"') p++;
17447 if (*p == '\"') p++;
17449 while (*p && *p != ' ') p++;
17457 PeriodicUpdatesEvent (int newState)
17459 if (newState == appData.periodicUpdates)
17462 appData.periodicUpdates=newState;
17464 /* Display type changes, so update it now */
17465 // DisplayAnalysis();
17467 /* Get the ball rolling again... */
17469 AnalysisPeriodicEvent(1);
17470 StartAnalysisClock();
17475 PonderNextMoveEvent (int newState)
17477 if (newState == appData.ponderNextMove) return;
17478 if (gameMode == EditPosition) EditPositionDone(TRUE);
17480 SendToProgram("hard\n", &first);
17481 if (gameMode == TwoMachinesPlay) {
17482 SendToProgram("hard\n", &second);
17485 SendToProgram("easy\n", &first);
17486 thinkOutput[0] = NULLCHAR;
17487 if (gameMode == TwoMachinesPlay) {
17488 SendToProgram("easy\n", &second);
17491 appData.ponderNextMove = newState;
17495 NewSettingEvent (int option, int *feature, char *command, int value)
17499 if (gameMode == EditPosition) EditPositionDone(TRUE);
17500 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17501 if(feature == NULL || *feature) SendToProgram(buf, &first);
17502 if (gameMode == TwoMachinesPlay) {
17503 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17508 ShowThinkingEvent ()
17509 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17511 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17512 int newState = appData.showThinking
17513 // [HGM] thinking: other features now need thinking output as well
17514 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17516 if (oldState == newState) return;
17517 oldState = newState;
17518 if (gameMode == EditPosition) EditPositionDone(TRUE);
17520 SendToProgram("post\n", &first);
17521 if (gameMode == TwoMachinesPlay) {
17522 SendToProgram("post\n", &second);
17525 SendToProgram("nopost\n", &first);
17526 thinkOutput[0] = NULLCHAR;
17527 if (gameMode == TwoMachinesPlay) {
17528 SendToProgram("nopost\n", &second);
17531 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17535 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17537 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17538 if (pr == NoProc) return;
17539 AskQuestion(title, question, replyPrefix, pr);
17543 TypeInEvent (char firstChar)
17545 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17546 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17547 gameMode == AnalyzeMode || gameMode == EditGame ||
17548 gameMode == EditPosition || gameMode == IcsExamining ||
17549 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17550 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17551 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17552 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
17553 gameMode == Training) PopUpMoveDialog(firstChar);
17557 TypeInDoneEvent (char *move)
17560 int n, fromX, fromY, toX, toY;
17562 ChessMove moveType;
17565 if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17566 EditPositionPasteFEN(move);
17569 // [HGM] movenum: allow move number to be typed in any mode
17570 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17574 // undocumented kludge: allow command-line option to be typed in!
17575 // (potentially fatal, and does not implement the effect of the option.)
17576 // should only be used for options that are values on which future decisions will be made,
17577 // and definitely not on options that would be used during initialization.
17578 if(strstr(move, "!!! -") == move) {
17579 ParseArgsFromString(move+4);
17583 if (gameMode != EditGame && currentMove != forwardMostMove &&
17584 gameMode != Training) {
17585 DisplayMoveError(_("Displayed move is not current"));
17587 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17588 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17589 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17590 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17591 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17592 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17594 DisplayMoveError(_("Could not parse move"));
17600 DisplayMove (int moveNumber)
17602 char message[MSG_SIZ];
17604 char cpThinkOutput[MSG_SIZ];
17606 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17608 if (moveNumber == forwardMostMove - 1 ||
17609 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17611 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17613 if (strchr(cpThinkOutput, '\n')) {
17614 *strchr(cpThinkOutput, '\n') = NULLCHAR;
17617 *cpThinkOutput = NULLCHAR;
17620 /* [AS] Hide thinking from human user */
17621 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17622 *cpThinkOutput = NULLCHAR;
17623 if( thinkOutput[0] != NULLCHAR ) {
17626 for( i=0; i<=hiddenThinkOutputState; i++ ) {
17627 cpThinkOutput[i] = '.';
17629 cpThinkOutput[i] = NULLCHAR;
17630 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17634 if (moveNumber == forwardMostMove - 1 &&
17635 gameInfo.resultDetails != NULL) {
17636 if (gameInfo.resultDetails[0] == NULLCHAR) {
17637 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17639 snprintf(res, MSG_SIZ, " {%s} %s",
17640 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17646 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17647 DisplayMessage(res, cpThinkOutput);
17649 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17650 WhiteOnMove(moveNumber) ? " " : ".. ",
17651 parseList[moveNumber], res);
17652 DisplayMessage(message, cpThinkOutput);
17657 DisplayComment (int moveNumber, char *text)
17659 char title[MSG_SIZ];
17661 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17662 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17664 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17665 WhiteOnMove(moveNumber) ? " " : ".. ",
17666 parseList[moveNumber]);
17668 if (text != NULL && (appData.autoDisplayComment || commentUp))
17669 CommentPopUp(title, text);
17672 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17673 * might be busy thinking or pondering. It can be omitted if your
17674 * gnuchess is configured to stop thinking immediately on any user
17675 * input. However, that gnuchess feature depends on the FIONREAD
17676 * ioctl, which does not work properly on some flavors of Unix.
17679 Attention (ChessProgramState *cps)
17682 if (!cps->useSigint) return;
17683 if (appData.noChessProgram || (cps->pr == NoProc)) return;
17684 switch (gameMode) {
17685 case MachinePlaysWhite:
17686 case MachinePlaysBlack:
17687 case TwoMachinesPlay:
17688 case IcsPlayingWhite:
17689 case IcsPlayingBlack:
17692 /* Skip if we know it isn't thinking */
17693 if (!cps->maybeThinking) return;
17694 if (appData.debugMode)
17695 fprintf(debugFP, "Interrupting %s\n", cps->which);
17696 InterruptChildProcess(cps->pr);
17697 cps->maybeThinking = FALSE;
17702 #endif /*ATTENTION*/
17708 if (whiteTimeRemaining <= 0) {
17711 if (appData.icsActive) {
17712 if (appData.autoCallFlag &&
17713 gameMode == IcsPlayingBlack && !blackFlag) {
17714 SendToICS(ics_prefix);
17715 SendToICS("flag\n");
17719 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17721 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17722 if (appData.autoCallFlag) {
17723 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17730 if (blackTimeRemaining <= 0) {
17733 if (appData.icsActive) {
17734 if (appData.autoCallFlag &&
17735 gameMode == IcsPlayingWhite && !whiteFlag) {
17736 SendToICS(ics_prefix);
17737 SendToICS("flag\n");
17741 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17743 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17744 if (appData.autoCallFlag) {
17745 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17756 CheckTimeControl ()
17758 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17759 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17762 * add time to clocks when time control is achieved ([HGM] now also used for increment)
17764 if ( !WhiteOnMove(forwardMostMove) ) {
17765 /* White made time control */
17766 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17767 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17768 /* [HGM] time odds: correct new time quota for time odds! */
17769 / WhitePlayer()->timeOdds;
17770 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17772 lastBlack -= blackTimeRemaining;
17773 /* Black made time control */
17774 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17775 / WhitePlayer()->other->timeOdds;
17776 lastWhite = whiteTimeRemaining;
17781 DisplayBothClocks ()
17783 int wom = gameMode == EditPosition ?
17784 !blackPlaysFirst : WhiteOnMove(currentMove);
17785 DisplayWhiteClock(whiteTimeRemaining, wom);
17786 DisplayBlackClock(blackTimeRemaining, !wom);
17790 /* Timekeeping seems to be a portability nightmare. I think everyone
17791 has ftime(), but I'm really not sure, so I'm including some ifdefs
17792 to use other calls if you don't. Clocks will be less accurate if
17793 you have neither ftime nor gettimeofday.
17796 /* VS 2008 requires the #include outside of the function */
17797 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17798 #include <sys/timeb.h>
17801 /* Get the current time as a TimeMark */
17803 GetTimeMark (TimeMark *tm)
17805 #if HAVE_GETTIMEOFDAY
17807 struct timeval timeVal;
17808 struct timezone timeZone;
17810 gettimeofday(&timeVal, &timeZone);
17811 tm->sec = (long) timeVal.tv_sec;
17812 tm->ms = (int) (timeVal.tv_usec / 1000L);
17814 #else /*!HAVE_GETTIMEOFDAY*/
17817 // include <sys/timeb.h> / moved to just above start of function
17818 struct timeb timeB;
17821 tm->sec = (long) timeB.time;
17822 tm->ms = (int) timeB.millitm;
17824 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17825 tm->sec = (long) time(NULL);
17831 /* Return the difference in milliseconds between two
17832 time marks. We assume the difference will fit in a long!
17835 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17837 return 1000L*(tm2->sec - tm1->sec) +
17838 (long) (tm2->ms - tm1->ms);
17843 * Code to manage the game clocks.
17845 * In tournament play, black starts the clock and then white makes a move.
17846 * We give the human user a slight advantage if he is playing white---the
17847 * clocks don't run until he makes his first move, so it takes zero time.
17848 * Also, we don't account for network lag, so we could get out of sync
17849 * with GNU Chess's clock -- but then, referees are always right.
17852 static TimeMark tickStartTM;
17853 static long intendedTickLength;
17856 NextTickLength (long timeRemaining)
17858 long nominalTickLength, nextTickLength;
17860 if (timeRemaining > 0L && timeRemaining <= 10000L)
17861 nominalTickLength = 100L;
17863 nominalTickLength = 1000L;
17864 nextTickLength = timeRemaining % nominalTickLength;
17865 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17867 return nextTickLength;
17870 /* Adjust clock one minute up or down */
17872 AdjustClock (Boolean which, int dir)
17874 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17875 if(which) blackTimeRemaining += 60000*dir;
17876 else whiteTimeRemaining += 60000*dir;
17877 DisplayBothClocks();
17878 adjustedClock = TRUE;
17881 /* Stop clocks and reset to a fresh time control */
17885 (void) StopClockTimer();
17886 if (appData.icsActive) {
17887 whiteTimeRemaining = blackTimeRemaining = 0;
17888 } else if (searchTime) {
17889 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17890 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17891 } else { /* [HGM] correct new time quote for time odds */
17892 whiteTC = blackTC = fullTimeControlString;
17893 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17894 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17896 if (whiteFlag || blackFlag) {
17898 whiteFlag = blackFlag = FALSE;
17900 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17901 DisplayBothClocks();
17902 adjustedClock = FALSE;
17905 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17907 static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness...
17909 /* Decrement running clock by amount of time that has passed */
17914 long lastTickLength, fudge;
17917 if (!appData.clockMode) return;
17918 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17922 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17924 /* Fudge if we woke up a little too soon */
17925 fudge = intendedTickLength - lastTickLength;
17926 if (fudge < 0 || fudge > FUDGE) fudge = 0;
17928 if (WhiteOnMove(forwardMostMove)) {
17929 if(whiteNPS >= 0) lastTickLength = 0;
17930 tRemaining = whiteTimeRemaining -= lastTickLength;
17931 if( tRemaining < 0 && !appData.icsActive) {
17932 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17933 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17934 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17935 lastWhite= tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17938 if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining;
17939 DisplayWhiteClock(whiteTimeRemaining - fudge,
17940 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17943 if(blackNPS >= 0) lastTickLength = 0;
17944 tRemaining = blackTimeRemaining -= lastTickLength;
17945 if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17946 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17948 blackStartMove = forwardMostMove;
17949 lastBlack = tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17952 if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining;
17953 DisplayBlackClock(blackTimeRemaining - fudge,
17954 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17957 if (CheckFlags()) return;
17959 if(twoBoards) { // count down secondary board's clocks as well
17960 activePartnerTime -= lastTickLength;
17962 if(activePartner == 'W')
17963 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17965 DisplayBlackClock(activePartnerTime, TRUE);
17970 intendedTickLength = NextTickLength( tRemaining - fudge) + fudge;
17971 StartClockTimer(intendedTickLength);
17973 /* if the time remaining has fallen below the alarm threshold, sound the
17974 * alarm. if the alarm has sounded and (due to a takeback or time control
17975 * with increment) the time remaining has increased to a level above the
17976 * threshold, reset the alarm so it can sound again.
17979 if (appData.icsActive && appData.icsAlarm) {
17981 /* make sure we are dealing with the user's clock */
17982 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17983 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17986 if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) {
17987 alarmSounded = FALSE;
17988 } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) {
17990 alarmSounded = TRUE;
17996 /* A player has just moved, so stop the previously running
17997 clock and (if in clock mode) start the other one.
17998 We redisplay both clocks in case we're in ICS mode, because
17999 ICS gives us an update to both clocks after every move.
18000 Note that this routine is called *after* forwardMostMove
18001 is updated, so the last fractional tick must be subtracted
18002 from the color that is *not* on move now.
18005 SwitchClocks (int newMoveNr)
18007 long lastTickLength;
18009 int flagged = FALSE;
18013 if (StopClockTimer() && appData.clockMode) {
18014 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18015 if (!WhiteOnMove(forwardMostMove)) {
18016 if(blackNPS >= 0) lastTickLength = 0;
18017 blackTimeRemaining -= lastTickLength;
18018 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18019 // if(pvInfoList[forwardMostMove].time == -1)
18020 pvInfoList[forwardMostMove].time = // use GUI time
18021 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
18023 if(whiteNPS >= 0) lastTickLength = 0;
18024 whiteTimeRemaining -= lastTickLength;
18025 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18026 // if(pvInfoList[forwardMostMove].time == -1)
18027 pvInfoList[forwardMostMove].time =
18028 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
18030 flagged = CheckFlags();
18032 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
18033 CheckTimeControl();
18035 if (flagged || !appData.clockMode) return;
18037 switch (gameMode) {
18038 case MachinePlaysBlack:
18039 case MachinePlaysWhite:
18040 case BeginningOfGame:
18041 if (pausing) return;
18045 case PlayFromGameFile:
18053 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
18054 if(WhiteOnMove(forwardMostMove))
18055 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
18056 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
18060 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18061 whiteTimeRemaining : blackTimeRemaining);
18062 StartClockTimer(intendedTickLength);
18066 /* Stop both clocks */
18070 long lastTickLength;
18073 if (!StopClockTimer()) return;
18074 if (!appData.clockMode) return;
18078 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18079 if (WhiteOnMove(forwardMostMove)) {
18080 if(whiteNPS >= 0) lastTickLength = 0;
18081 whiteTimeRemaining -= lastTickLength;
18082 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
18084 if(blackNPS >= 0) lastTickLength = 0;
18085 blackTimeRemaining -= lastTickLength;
18086 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
18091 /* Start clock of player on move. Time may have been reset, so
18092 if clock is already running, stop and restart it. */
18096 (void) StopClockTimer(); /* in case it was running already */
18097 DisplayBothClocks();
18098 if (CheckFlags()) return;
18100 if (!appData.clockMode) return;
18101 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
18103 GetTimeMark(&tickStartTM);
18104 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18105 whiteTimeRemaining : blackTimeRemaining);
18107 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
18108 whiteNPS = blackNPS = -1;
18109 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
18110 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
18111 whiteNPS = first.nps;
18112 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18113 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18114 blackNPS = first.nps;
18115 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18116 whiteNPS = second.nps;
18117 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18118 blackNPS = second.nps;
18119 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18121 StartClockTimer(intendedTickLength);
18125 TimeString (long ms)
18127 long second, minute, hour, day;
18129 static char buf[40], moveTime[8];
18131 if (ms > 0 && ms <= 9900) {
18132 /* convert milliseconds to tenths, rounding up */
18133 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18135 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18139 /* convert milliseconds to seconds, rounding up */
18140 /* use floating point to avoid strangeness of integer division
18141 with negative dividends on many machines */
18142 second = (long) floor(((double) (ms + 999L)) / 1000.0);
18149 day = second / (60 * 60 * 24);
18150 second = second % (60 * 60 * 24);
18151 hour = second / (60 * 60);
18152 second = second % (60 * 60);
18153 minute = second / 60;
18154 second = second % 60;
18156 if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time
18157 else *moveTime = NULLCHAR;
18160 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ",
18161 sign, day, hour, minute, second, moveTime);
18163 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime);
18165 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime);
18172 * This is necessary because some C libraries aren't ANSI C compliant yet.
18175 StrStr (char *string, char *match)
18179 length = strlen(match);
18181 for (i = strlen(string) - length; i >= 0; i--, string++)
18182 if (!strncmp(match, string, length))
18189 StrCaseStr (char *string, char *match)
18193 length = strlen(match);
18195 for (i = strlen(string) - length; i >= 0; i--, string++) {
18196 for (j = 0; j < length; j++) {
18197 if (ToLower(match[j]) != ToLower(string[j]))
18200 if (j == length) return string;
18208 StrCaseCmp (char *s1, char *s2)
18213 c1 = ToLower(*s1++);
18214 c2 = ToLower(*s2++);
18215 if (c1 > c2) return 1;
18216 if (c1 < c2) return -1;
18217 if (c1 == NULLCHAR) return 0;
18225 return isupper(c) ? tolower(c) : c;
18232 return islower(c) ? toupper(c) : c;
18234 #endif /* !_amigados */
18241 if ((ret = (char *) malloc(strlen(s) + 1)))
18243 safeStrCpy(ret, s, strlen(s)+1);
18249 StrSavePtr (char *s, char **savePtr)
18254 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18255 safeStrCpy(*savePtr, s, strlen(s)+1);
18267 clock = time((time_t *)NULL);
18268 tm = localtime(&clock);
18269 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18270 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18271 return StrSave(buf);
18276 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18278 int i, j, fromX, fromY, toX, toY;
18279 int whiteToPlay, haveRights = nrCastlingRights;
18285 whiteToPlay = (gameMode == EditPosition) ?
18286 !blackPlaysFirst : (move % 2 == 0);
18289 /* Piece placement data */
18290 for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18291 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18293 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18294 if (boards[move][i][j] == EmptySquare) {
18296 } else { ChessSquare piece = boards[move][i][j];
18297 if (emptycount > 0) {
18298 if(emptycount<10) /* [HGM] can be >= 10 */
18299 *p++ = '0' + emptycount;
18300 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18303 if(PieceToChar(piece) == '+') {
18304 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18306 piece = (ChessSquare)(CHUDEMOTED(piece));
18308 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18309 if(*p = PieceSuffix(piece)) p++;
18311 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18312 p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18317 if (emptycount > 0) {
18318 if(emptycount<10) /* [HGM] can be >= 10 */
18319 *p++ = '0' + emptycount;
18320 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18327 /* [HGM] print Crazyhouse or Shogi holdings */
18328 if( gameInfo.holdingsWidth ) {
18329 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18331 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18332 piece = boards[move][i][BOARD_WIDTH-1];
18333 if( piece != EmptySquare )
18334 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18335 *p++ = PieceToChar(piece);
18337 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18338 piece = boards[move][BOARD_HEIGHT-i-1][0];
18339 if( piece != EmptySquare )
18340 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18341 *p++ = PieceToChar(piece);
18344 if( q == p ) *p++ = '-';
18350 *p++ = whiteToPlay ? 'w' : 'b';
18353 if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18354 haveRights = 0; q = p;
18355 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18356 piece = boards[move][0][i];
18357 if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18358 if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18361 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18362 piece = boards[move][BOARD_HEIGHT-1][i];
18363 if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18364 if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18367 if(p == q) *p++ = '-';
18371 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18374 if(q != overrideCastling+1) p[-1] = ' '; else --p;
18377 int handW=0, handB=0;
18378 if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18379 for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18380 for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18383 if(appData.fischerCastling) {
18384 if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18385 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18386 if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18388 /* [HGM] write directly from rights */
18389 if(boards[move][CASTLING][2] != NoRights &&
18390 boards[move][CASTLING][0] != NoRights )
18391 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18392 if(boards[move][CASTLING][2] != NoRights &&
18393 boards[move][CASTLING][1] != NoRights )
18394 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18397 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18398 if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18400 if(boards[move][CASTLING][5] != NoRights &&
18401 boards[move][CASTLING][3] != NoRights )
18402 *p++ = boards[move][CASTLING][3] + AAA;
18403 if(boards[move][CASTLING][5] != NoRights &&
18404 boards[move][CASTLING][4] != NoRights )
18405 *p++ = boards[move][CASTLING][4] + AAA;
18409 /* [HGM] write true castling rights */
18410 if( nrCastlingRights == 6 ) {
18412 if(boards[move][CASTLING][0] != NoRights &&
18413 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
18414 q = (boards[move][CASTLING][1] != NoRights &&
18415 boards[move][CASTLING][2] != NoRights );
18416 if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18417 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18418 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18419 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18423 if(boards[move][CASTLING][3] != NoRights &&
18424 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
18425 q = (boards[move][CASTLING][4] != NoRights &&
18426 boards[move][CASTLING][5] != NoRights );
18428 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18429 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18430 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18435 if (q == p) *p++ = '-'; /* No castling rights */
18439 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
18440 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18441 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18442 /* En passant target square */
18443 if (move > backwardMostMove) {
18444 fromX = moveList[move - 1][0] - AAA;
18445 fromY = moveList[move - 1][1] - ONE;
18446 toX = moveList[move - 1][2] - AAA;
18447 toY = moveList[move - 1][3] - ONE;
18448 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18449 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18450 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18452 /* 2-square pawn move just happened */
18454 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18458 } else if(move == backwardMostMove) {
18459 // [HGM] perhaps we should always do it like this, and forget the above?
18460 if((signed char)boards[move][EP_STATUS] >= 0) {
18461 *p++ = boards[move][EP_STATUS] + AAA;
18462 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18473 i = boards[move][CHECK_COUNT];
18475 sprintf(p, "%d+%d ", i&255, i>>8);
18480 { int i = 0, j=move;
18482 /* [HGM] find reversible plies */
18483 if (appData.debugMode) { int k;
18484 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18485 for(k=backwardMostMove; k<=forwardMostMove; k++)
18486 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18490 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18491 if( j == backwardMostMove ) i += initialRulePlies;
18492 sprintf(p, "%d ", i);
18493 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18495 /* Fullmove number */
18496 sprintf(p, "%d", (move / 2) + 1);
18497 } else *--p = NULLCHAR;
18499 return StrSave(buf);
18503 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18505 int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18507 int emptycount, virgin[BOARD_FILES];
18508 ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18512 for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) board[BOARD_HEIGHT-i][j] = DarkSquare;
18514 /* Piece placement data */
18515 for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18518 if (*p == '/' || *p == ' ' || *p == '[' ) {
18520 emptycount = gameInfo.boardWidth - j;
18521 while (emptycount--)
18522 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18523 if (*p == '/') p++;
18524 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18525 for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18526 for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18528 appData.NrRanks = gameInfo.boardHeight - i; i=0;
18531 #if(BOARD_FILES >= 10)*0
18532 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18533 p++; emptycount=10;
18534 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18535 while (emptycount--)
18536 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18538 } else if (*p == '*') {
18539 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18540 } else if (isdigit(*p)) {
18541 emptycount = *p++ - '0';
18542 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18543 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18544 while (emptycount--)
18545 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18546 } else if (*p == '<') {
18547 if(i == BOARD_HEIGHT-1) shuffle = 1;
18548 else if (i != 0 || !shuffle) return FALSE;
18550 } else if (shuffle && *p == '>') {
18551 p++; // for now ignore closing shuffle range, and assume rank-end
18552 } else if (*p == '?') {
18553 if (j >= gameInfo.boardWidth) return FALSE;
18554 if (i != 0 && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18555 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18556 } else if (*p == '+' || isalpha(*p)) {
18557 char *q, *s = SUFFIXES;
18558 if (j >= gameInfo.boardWidth) return FALSE;
18561 if(q = strchr(s, p[1])) p++;
18562 piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18563 if(piece == EmptySquare) return FALSE; /* unknown piece */
18564 piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18565 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18568 if(q = strchr(s, *p)) p++;
18569 piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18572 if(piece==EmptySquare) return FALSE; /* unknown piece */
18573 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18574 piece = (ChessSquare) (PROMOTED(piece));
18575 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18578 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18579 if(piece == king) wKingRank = i;
18580 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18586 while (*p == '/' || *p == ' ') p++;
18588 if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18590 /* [HGM] by default clear Crazyhouse holdings, if present */
18591 if(gameInfo.holdingsWidth) {
18592 for(i=0; i<BOARD_HEIGHT; i++) {
18593 board[i][0] = EmptySquare; /* black holdings */
18594 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18595 board[i][1] = (ChessSquare) 0; /* black counts */
18596 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18600 /* [HGM] look for Crazyhouse holdings here */
18601 while(*p==' ') p++;
18602 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18603 int swap=0, wcnt=0, bcnt=0;
18605 if(*p == '<') swap++, p++;
18606 if(*p == '-' ) p++; /* empty holdings */ else {
18607 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18608 /* if we would allow FEN reading to set board size, we would */
18609 /* have to add holdings and shift the board read so far here */
18610 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18612 if((int) piece >= (int) BlackPawn ) {
18613 i = (int)piece - (int)BlackPawn;
18614 i = PieceToNumber((ChessSquare)i);
18615 if( i >= gameInfo.holdingsSize ) return FALSE;
18616 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18617 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
18620 i = (int)piece - (int)WhitePawn;
18621 i = PieceToNumber((ChessSquare)i);
18622 if( i >= gameInfo.holdingsSize ) return FALSE;
18623 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
18624 board[i][BOARD_WIDTH-2]++; /* black holdings */
18628 if(subst) { // substitute back-rank question marks by holdings pieces
18629 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18630 int k, m, n = bcnt + 1;
18631 if(board[0][j] == ClearBoard) {
18632 if(!wcnt) return FALSE;
18634 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18635 board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18636 if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18640 if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18641 if(!bcnt) return FALSE;
18642 if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18643 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18644 board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18645 if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18656 if(subst) return FALSE; // substitution requested, but no holdings
18658 while(*p == ' ') p++;
18662 if(appData.colorNickNames) {
18663 if( c == appData.colorNickNames[0] ) c = 'w'; else
18664 if( c == appData.colorNickNames[1] ) c = 'b';
18668 *blackPlaysFirst = FALSE;
18671 *blackPlaysFirst = TRUE;
18677 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18678 /* return the extra info in global variiables */
18680 while(*p==' ') p++;
18682 if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18683 if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18684 if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18687 /* set defaults in case FEN is incomplete */
18688 board[EP_STATUS] = EP_UNKNOWN;
18689 board[TOUCHED_W] = board[TOUCHED_B] = 0;
18690 for(i=0; i<nrCastlingRights; i++ ) {
18691 board[CASTLING][i] =
18692 appData.fischerCastling ? NoRights : initialRights[i];
18693 } /* assume possible unless obviously impossible */
18694 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18695 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18696 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18697 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18698 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18699 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18700 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18701 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18704 if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18707 while(isalpha(*p)) {
18708 if(isupper(*p)) w |= 1 << (*p++ - 'A');
18709 if(islower(*p)) b |= 1 << (*p++ - 'a');
18713 board[TOUCHED_W] = ~w;
18714 board[TOUCHED_B] = ~b;
18715 while(*p == ' ') p++;
18719 if(nrCastlingRights) {
18721 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18722 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18723 /* castling indicator present, so default becomes no castlings */
18724 for(i=0; i<nrCastlingRights; i++ ) {
18725 board[CASTLING][i] = NoRights;
18728 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18729 (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18730 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18731 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
18732 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18734 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18735 if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18736 if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18738 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18739 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18740 if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18741 && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18742 if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18743 && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18746 for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18747 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18748 board[CASTLING][2] = whiteKingFile;
18749 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18750 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18751 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18754 for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18755 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18756 board[CASTLING][2] = whiteKingFile;
18757 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18758 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18759 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18762 for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18763 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18764 board[CASTLING][5] = blackKingFile;
18765 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18766 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18767 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18770 for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18771 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18772 board[CASTLING][5] = blackKingFile;
18773 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18774 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18775 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18778 default: /* FRC castlings */
18779 if(c >= 'a') { /* black rights */
18780 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18781 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18782 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18783 if(i == BOARD_RGHT) break;
18784 board[CASTLING][5] = i;
18786 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
18787 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
18789 board[CASTLING][3] = c;
18791 board[CASTLING][4] = c;
18792 } else { /* white rights */
18793 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18794 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18795 if(board[0][i] == WhiteKing) break;
18796 if(i == BOARD_RGHT) break;
18797 board[CASTLING][2] = i;
18798 c -= AAA - 'a' + 'A';
18799 if(board[0][c] >= WhiteKing) break;
18801 board[CASTLING][0] = c;
18803 board[CASTLING][1] = c;
18807 for(i=0; i<nrCastlingRights; i++)
18808 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18809 if(gameInfo.variant == VariantSChess)
18810 for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18811 if(fischer && shuffle) appData.fischerCastling = TRUE;
18812 if (appData.debugMode) {
18813 fprintf(debugFP, "FEN castling rights:");
18814 for(i=0; i<nrCastlingRights; i++)
18815 fprintf(debugFP, " %d", board[CASTLING][i]);
18816 fprintf(debugFP, "\n");
18819 while(*p==' ') p++;
18822 if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18824 /* read e.p. field in games that know e.p. capture */
18825 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
18826 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18827 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18829 p++; board[EP_STATUS] = EP_NONE;
18831 char c = *p++ - AAA;
18833 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18834 if(*p >= '0' && *p <='9') p++;
18835 board[EP_STATUS] = c;
18839 while(*p == ' ') p++;
18841 board[CHECK_COUNT] = 0; // [HGM] 3check: check-count field
18842 if(sscanf(p, "%d+%d", &i, &j) == 2) {
18843 board[CHECK_COUNT] = i + 256*j;
18844 while(*p && *p != ' ') p++;
18847 c = sscanf(p, "%d%*d +%d+%d", &i, &j, &k);
18849 FENrulePlies = i; /* 50-move ply counter */
18850 /* (The move number is still ignored) */
18851 if(c == 3 && !board[CHECK_COUNT]) board[CHECK_COUNT] = (3 - j) + 256*(3 - k); // SCIDB-style check count
18858 EditPositionPasteFEN (char *fen)
18861 Board initial_position;
18863 if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18864 DisplayError(_("Bad FEN position in clipboard"), 0);
18867 int savedBlackPlaysFirst = blackPlaysFirst;
18868 EditPositionEvent();
18869 blackPlaysFirst = savedBlackPlaysFirst;
18870 CopyBoard(boards[0], initial_position);
18871 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18872 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18873 DisplayBothClocks();
18874 DrawPosition(FALSE, boards[currentMove]);
18879 static char cseq[12] = "\\ ";
18882 set_cont_sequence (char *new_seq)
18887 // handle bad attempts to set the sequence
18889 return 0; // acceptable error - no debug
18891 len = strlen(new_seq);
18892 ret = (len > 0) && (len < sizeof(cseq));
18894 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18895 else if (appData.debugMode)
18896 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18901 reformat a source message so words don't cross the width boundary. internal
18902 newlines are not removed. returns the wrapped size (no null character unless
18903 included in source message). If dest is NULL, only calculate the size required
18904 for the dest buffer. lp argument indicats line position upon entry, and it's
18905 passed back upon exit.
18908 wrap (char *dest, char *src, int count, int width, int *lp)
18910 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18912 cseq_len = strlen(cseq);
18913 old_line = line = *lp;
18914 ansi = len = clen = 0;
18916 for (i=0; i < count; i++)
18918 if (src[i] == '\033')
18921 // if we hit the width, back up
18922 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18924 // store i & len in case the word is too long
18925 old_i = i, old_len = len;
18927 // find the end of the last word
18928 while (i && src[i] != ' ' && src[i] != '\n')
18934 // word too long? restore i & len before splitting it
18935 if ((old_i-i+clen) >= width)
18942 if (i && src[i-1] == ' ')
18945 if (src[i] != ' ' && src[i] != '\n')
18952 // now append the newline and continuation sequence
18957 strncpy(dest+len, cseq, cseq_len);
18965 dest[len] = src[i];
18969 if (src[i] == '\n')
18974 if (dest && appData.debugMode)
18976 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18977 count, width, line, len, *lp);
18978 show_bytes(debugFP, src, count);
18979 fprintf(debugFP, "\ndest: ");
18980 show_bytes(debugFP, dest, len);
18981 fprintf(debugFP, "\n");
18983 *lp = dest ? line : old_line;
18988 // [HGM] vari: routines for shelving variations
18989 Boolean modeRestore = FALSE;
18992 PushInner (int firstMove, int lastMove)
18994 int i, j, nrMoves = lastMove - firstMove;
18996 // push current tail of game on stack
18997 savedResult[storedGames] = gameInfo.result;
18998 savedDetails[storedGames] = gameInfo.resultDetails;
18999 gameInfo.resultDetails = NULL;
19000 savedFirst[storedGames] = firstMove;
19001 savedLast [storedGames] = lastMove;
19002 savedFramePtr[storedGames] = framePtr;
19003 framePtr -= nrMoves; // reserve space for the boards
19004 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
19005 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
19006 for(j=0; j<MOVE_LEN; j++)
19007 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
19008 for(j=0; j<2*MOVE_LEN; j++)
19009 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
19010 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
19011 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
19012 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
19013 pvInfoList[firstMove+i-1].depth = 0;
19014 commentList[framePtr+i] = commentList[firstMove+i];
19015 commentList[firstMove+i] = NULL;
19019 forwardMostMove = firstMove; // truncate game so we can start variation
19023 PushTail (int firstMove, int lastMove)
19025 if(appData.icsActive) { // only in local mode
19026 forwardMostMove = currentMove; // mimic old ICS behavior
19029 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
19031 PushInner(firstMove, lastMove);
19032 if(storedGames == 1) GreyRevert(FALSE);
19033 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
19037 PopInner (Boolean annotate)
19040 char buf[8000], moveBuf[20];
19042 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
19043 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
19044 nrMoves = savedLast[storedGames] - currentMove;
19047 if(!WhiteOnMove(currentMove))
19048 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
19049 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
19050 for(i=currentMove; i<forwardMostMove; i++) {
19052 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
19053 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
19054 strcat(buf, moveBuf);
19055 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
19056 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
19060 for(i=1; i<=nrMoves; i++) { // copy last variation back
19061 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
19062 for(j=0; j<MOVE_LEN; j++)
19063 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
19064 for(j=0; j<2*MOVE_LEN; j++)
19065 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
19066 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
19067 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
19068 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
19069 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
19070 commentList[currentMove+i] = commentList[framePtr+i];
19071 commentList[framePtr+i] = NULL;
19073 if(annotate) AppendComment(currentMove+1, buf, FALSE);
19074 framePtr = savedFramePtr[storedGames];
19075 gameInfo.result = savedResult[storedGames];
19076 if(gameInfo.resultDetails != NULL) {
19077 free(gameInfo.resultDetails);
19079 gameInfo.resultDetails = savedDetails[storedGames];
19080 forwardMostMove = currentMove + nrMoves;
19084 PopTail (Boolean annotate)
19086 if(appData.icsActive) return FALSE; // only in local mode
19087 if(!storedGames) return FALSE; // sanity
19088 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
19090 PopInner(annotate);
19091 if(currentMove < forwardMostMove) ForwardEvent(); else
19092 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
19094 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
19100 { // remove all shelved variations
19102 for(i=0; i<storedGames; i++) {
19103 if(savedDetails[i])
19104 free(savedDetails[i]);
19105 savedDetails[i] = NULL;
19107 for(i=framePtr; i<MAX_MOVES; i++) {
19108 if(commentList[i]) free(commentList[i]);
19109 commentList[i] = NULL;
19111 framePtr = MAX_MOVES-1;
19116 LoadVariation (int index, char *text)
19117 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
19118 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
19119 int level = 0, move;
19121 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
19122 // first find outermost bracketing variation
19123 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
19124 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
19125 if(*p == '{') wait = '}'; else
19126 if(*p == '[') wait = ']'; else
19127 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
19128 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
19130 if(*p == wait) wait = NULLCHAR; // closing ]} found
19133 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19134 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19135 end[1] = NULLCHAR; // clip off comment beyond variation
19136 ToNrEvent(currentMove-1);
19137 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19138 // kludge: use ParsePV() to append variation to game
19139 move = currentMove;
19140 ParsePV(start, TRUE, TRUE);
19141 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19142 ClearPremoveHighlights();
19144 ToNrEvent(currentMove+1);
19147 int transparency[2];
19152 #define BUF_SIZ (2*MSG_SIZ)
19153 char *p, *q, buf[BUF_SIZ];
19154 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19155 snprintf(buf, BUF_SIZ, "-theme %s", engineLine);
19156 ParseArgsFromString(buf);
19157 ActivateTheme(TRUE); // also redo colors
19161 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19164 q = appData.themeNames;
19165 snprintf(buf, BUF_SIZ, "\"%s\"", nickName);
19166 if(appData.useBitmaps) {
19167 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt true -lbtf \"%s\"",
19168 Shorten(appData.liteBackTextureFile));
19169 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dbtf \"%s\" -lbtm %d -dbtm %d",
19170 Shorten(appData.darkBackTextureFile),
19171 appData.liteBackTextureMode,
19172 appData.darkBackTextureMode );
19174 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt false");
19176 if(!appData.useBitmaps || transparency[0]) {
19177 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -lsc %s", Col2Text(2) ); // lightSquareColor
19179 if(!appData.useBitmaps || transparency[1]) {
19180 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dsc %s", Col2Text(3) ); // darkSquareColor
19182 if(appData.useBorder) {
19183 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub true -border \"%s\"",
19186 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub false");
19188 if(appData.useFont) {
19189 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19190 appData.renderPiecesWithFont,
19191 appData.fontToPieceTable,
19192 Col2Text(9), // appData.fontBackColorWhite
19193 Col2Text(10) ); // appData.fontForeColorBlack
19195 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf false");
19196 if(appData.pieceDirectory[0]) {
19197 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -pid \"%s\"", Shorten(appData.pieceDirectory));
19198 if(appData.trueColors != 2) // 2 is a kludge to suppress this in WinBoard
19199 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -trueColors %s", appData.trueColors ? "true" : "false");
19201 if(!appData.pieceDirectory[0] || !appData.trueColors)
19202 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -wpc %s -bpc %s",
19203 Col2Text(0), // whitePieceColor
19204 Col2Text(1) ); // blackPieceColor
19206 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19207 Col2Text(4), // highlightSquareColor
19208 Col2Text(5) ); // premoveHighlightColor
19209 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19210 if(insert != q) insert[-1] = NULLCHAR;
19211 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19214 ActivateTheme(FALSE);