2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free
9 * Software Foundation, Inc.
11 * Enhancements Copyright 2005 Alessandro Scotti
13 * The following terms apply to Digital Equipment Corporation's copyright
15 * ------------------------------------------------------------------------
18 * Permission to use, copy, modify, and distribute this software and its
19 * documentation for any purpose and without fee is hereby granted,
20 * provided that the above copyright notice appear in all copies and that
21 * both that copyright notice and this permission notice appear in
22 * supporting documentation, and that the name of Digital not be
23 * used in advertising or publicity pertaining to distribution of the
24 * software without specific, written prior permission.
26 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
27 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
28 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
29 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
30 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
31 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
33 * ------------------------------------------------------------------------
35 * The following terms apply to the enhanced version of XBoard
36 * distributed by the Free Software Foundation:
37 * ------------------------------------------------------------------------
39 * GNU XBoard is free software: you can redistribute it and/or modify
40 * it under the terms of the GNU General Public License as published by
41 * the Free Software Foundation, either version 3 of the License, or (at
42 * your option) any later version.
44 * GNU XBoard is distributed in the hope that it will be useful, but
45 * WITHOUT ANY WARRANTY; without even the implied warranty of
46 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
47 * General Public License for more details.
49 * You should have received a copy of the GNU General Public License
50 * along with this program. If not, see http://www.gnu.org/licenses/. *
52 *------------------------------------------------------------------------
53 ** See the file ChangeLog for a revision history. */
55 /* [AS] Also useful here for debugging */
59 int flock(int f, int code);
64 # define EGBB_NAME "egbbdll64.dll"
66 # define EGBB_NAME "egbbdll.dll"
71 # include <sys/file.h>
76 # define EGBB_NAME "egbbso64.so"
78 # define EGBB_NAME "egbbso.so"
80 // kludge to allow Windows code in back-end by converting it to corresponding Linux code
82 # define HMODULE void *
83 # define LoadLibrary(x) dlopen(x, RTLD_LAZY)
84 # define GetProcAddress dlsym
94 #include <sys/types.h>
103 #else /* not STDC_HEADERS */
106 # else /* not HAVE_STRING_H */
107 # include <strings.h>
108 # endif /* not HAVE_STRING_H */
109 #endif /* not STDC_HEADERS */
112 # include <sys/fcntl.h>
113 #else /* not HAVE_SYS_FCNTL_H */
116 # endif /* HAVE_FCNTL_H */
117 #endif /* not HAVE_SYS_FCNTL_H */
119 #if TIME_WITH_SYS_TIME
120 # include <sys/time.h>
124 # include <sys/time.h>
130 #if defined(_amigados) && !defined(__GNUC__)
135 extern int gettimeofday(struct timeval *, struct timezone *);
143 #include "frontend.h"
150 #include "backendz.h"
151 #include "evalgraph.h"
152 #include "engineoutput.h"
156 # define _(s) gettext (s)
157 # define N_(s) gettext_noop (s)
158 # define T_(s) gettext(s)
171 int establish P((void));
172 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
173 char *buf, int count, int error));
174 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
175 char *buf, int count, int error));
176 void SendToICS P((char *s));
177 void SendToICSDelayed P((char *s, long msdelay));
178 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
179 void HandleMachineMove P((char *message, ChessProgramState *cps));
180 int AutoPlayOneMove P((void));
181 int LoadGameOneMove P((ChessMove readAhead));
182 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
183 int LoadPositionFromFile P((char *filename, int n, char *title));
184 int SavePositionToFile P((char *filename));
185 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
186 void ShowMove P((int fromX, int fromY, int toX, int toY));
187 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
188 /*char*/int promoChar));
189 void BackwardInner P((int target));
190 void ForwardInner P((int target));
191 int Adjudicate P((ChessProgramState *cps));
192 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
193 void EditPositionDone P((Boolean fakeRights));
194 void PrintOpponents P((FILE *fp));
195 void PrintPosition P((FILE *fp, int move));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199 char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201 int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
250 extern void ConsoleCreate();
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
264 int deadRanks, handSize;
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 = handSize-1;
2491 holdingsColumn = BOARD_WIDTH-1;
2492 countsColumn = BOARD_WIDTH-2;
2493 holdingsStartRow = 0;
2497 for(i=0; i<BOARD_RANKS-1; 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[handSize-1-k][0] = WHITE_TO_BLACK p; board[handSize-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[handSize-1-k][0] = WHITE_TO_BLACK p; board[handSize-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[handSize-1-k][0] = WHITE_TO_BLACK p; board[handSize-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[handSize-1-k][0] = WHITE_TO_BLACK p; board[handSize-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 handSize = BOARD_HEIGHT;
6370 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6371 if(pawnRow < 1) pawnRow = 1;
6372 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6373 gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6374 if(gameInfo.variant == VariantChu) pawnRow = 3;
6376 /* User pieceToChar list overrules defaults */
6377 if(appData.pieceToCharTable != NULL)
6378 SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6380 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6382 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6383 s = (ChessSquare) 0; /* account holding counts in guard band */
6384 for( i=0; i<BOARD_HEIGHT; i++ )
6385 initialPosition[i][j] = s;
6387 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6388 initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6389 initialPosition[pawnRow][j] = WhitePawn;
6390 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6391 if(gameInfo.variant == VariantXiangqi) {
6393 initialPosition[pawnRow][j] =
6394 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6395 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6396 initialPosition[2][j] = WhiteCannon;
6397 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6401 if(gameInfo.variant == VariantChu) {
6402 if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6403 initialPosition[pawnRow+1][j] = WhiteCobra,
6404 initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6405 for(i=1; i<pieceRows; i++) {
6406 initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6407 initialPosition[BOARD_HEIGHT-1-i][j] = pieces[2*i+1][j-gameInfo.holdingsWidth];
6410 if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6411 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6412 initialPosition[0][j] = WhiteRook;
6413 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6416 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] = pieces[1][j-gameInfo.holdingsWidth];
6418 if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6419 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6422 initialPosition[1][j] = WhiteBishop;
6423 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6425 initialPosition[1][j] = WhiteRook;
6426 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6429 if( nrCastlingRights == -1) {
6430 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6431 /* This sets default castling rights from none to normal corners */
6432 /* Variants with other castling rights must set them themselves above */
6433 nrCastlingRights = 6;
6435 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6436 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6437 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6438 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6439 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6440 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6443 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6444 if(gameInfo.variant == VariantGreat) { // promotion commoners
6445 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6446 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6447 initialPosition[handSize-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6448 initialPosition[handSize-1-PieceToNumber(WhiteMan)][1] = 9;
6450 if( gameInfo.variant == VariantSChess ) {
6451 initialPosition[1][0] = BlackMarshall;
6452 initialPosition[2][0] = BlackAngel;
6453 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6454 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6455 initialPosition[1][1] = initialPosition[2][1] =
6456 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6458 initialPosition[CHECK_COUNT] = (gameInfo.variant == Variant3Check ? 0x303 : 0);
6459 if (appData.debugMode) {
6460 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6462 if(shuffleOpenings) {
6463 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6464 startedFromSetupPosition = TRUE;
6466 if(startedFromPositionFile) {
6467 /* [HGM] loadPos: use PositionFile for every new game */
6468 CopyBoard(initialPosition, filePosition);
6469 for(i=0; i<nrCastlingRights; i++)
6470 initialRights[i] = filePosition[CASTLING][i];
6471 startedFromSetupPosition = TRUE;
6473 if(*appData.men) LoadPieceDesc(appData.men);
6475 CopyBoard(boards[0], initialPosition);
6477 if(oldx != gameInfo.boardWidth ||
6478 oldy != gameInfo.boardHeight ||
6479 oldv != gameInfo.variant ||
6480 oldh != gameInfo.holdingsWidth
6482 InitDrawingSizes(-2 ,0);
6484 oldv = gameInfo.variant;
6486 DrawPosition(TRUE, boards[currentMove]);
6490 SendBoard (ChessProgramState *cps, int moveNum)
6492 char message[MSG_SIZ];
6494 if (cps->useSetboard) {
6495 char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6496 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6497 SendToProgram(message, cps);
6502 int i, j, left=0, right=BOARD_WIDTH;
6503 /* Kludge to set black to move, avoiding the troublesome and now
6504 * deprecated "black" command.
6506 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6507 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn && !pieceDesc[WhitePawn] ? "a2a3\n" : "black\nforce\n", cps);
6509 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6511 SendToProgram("edit\n", cps);
6512 SendToProgram("#\n", cps);
6513 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6514 bp = &boards[moveNum][i][left];
6515 for (j = left; j < right; j++, bp++) {
6516 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6517 if ((int) *bp < (int) BlackPawn) {
6518 if(j == BOARD_RGHT+1)
6519 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6520 else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6521 if(message[0] == '+' || message[0] == '~') {
6522 snprintf(message, MSG_SIZ,"%c%c%d+\n",
6523 PieceToChar((ChessSquare)(DEMOTED(*bp))),
6524 AAA + j, ONE + i - '0');
6526 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6527 message[1] = BOARD_RGHT - 1 - j + '1';
6528 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6530 SendToProgram(message, cps);
6535 SendToProgram("c\n", cps);
6536 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6537 bp = &boards[moveNum][i][left];
6538 for (j = left; j < right; j++, bp++) {
6539 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6540 if (((int) *bp != (int) EmptySquare)
6541 && ((int) *bp >= (int) BlackPawn)) {
6542 if(j == BOARD_LEFT-2)
6543 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6544 else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6545 AAA + j, ONE + i - '0');
6546 if(message[0] == '+' || message[0] == '~') {
6547 snprintf(message, MSG_SIZ,"%c%c%d+\n",
6548 PieceToChar((ChessSquare)(DEMOTED(*bp))),
6549 AAA + j, ONE + i - '0');
6551 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6552 message[1] = BOARD_RGHT - 1 - j + '1';
6553 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6555 SendToProgram(message, cps);
6560 SendToProgram(".\n", cps);
6562 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6565 char exclusionHeader[MSG_SIZ];
6566 int exCnt, excludePtr;
6567 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6568 static Exclusion excluTab[200];
6569 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6575 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6576 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6582 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6583 excludePtr = 24; exCnt = 0;
6588 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6589 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6590 char buf[2*MOVE_LEN], *p;
6591 Exclusion *e = excluTab;
6593 for(i=0; i<exCnt; i++)
6594 if(e[i].ff == fromX && e[i].fr == fromY &&
6595 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6596 if(i == exCnt) { // was not in exclude list; add it
6597 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6598 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6599 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6602 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6603 excludePtr++; e[i].mark = excludePtr++;
6604 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6607 exclusionHeader[e[i].mark] = state;
6611 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6612 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6616 if((signed char)promoChar == -1) { // kludge to indicate best move
6617 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6618 return 1; // if unparsable, abort
6620 // update exclusion map (resolving toggle by consulting existing state)
6621 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6623 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6624 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6625 excludeMap[k] |= 1<<j;
6626 else excludeMap[k] &= ~(1<<j);
6628 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6630 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6631 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6633 return (state == '+');
6637 ExcludeClick (int index)
6640 Exclusion *e = excluTab;
6641 if(index < 25) { // none, best or tail clicked
6642 if(index < 13) { // none: include all
6643 WriteMap(0); // clear map
6644 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6645 SendToBoth("include all\n"); // and inform engine
6646 } else if(index > 18) { // tail
6647 if(exclusionHeader[19] == '-') { // tail was excluded
6648 SendToBoth("include all\n");
6649 WriteMap(0); // clear map completely
6650 // now re-exclude selected moves
6651 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6652 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6653 } else { // tail was included or in mixed state
6654 SendToBoth("exclude all\n");
6655 WriteMap(0xFF); // fill map completely
6656 // now re-include selected moves
6657 j = 0; // count them
6658 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6659 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6660 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6663 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6666 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6667 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6668 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6675 DefaultPromoChoice (int white)
6678 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6679 gameInfo.variant == VariantMakruk)
6680 result = WhiteFerz; // no choice
6681 else if(gameInfo.variant == VariantASEAN)
6682 result = WhiteRook; // no choice
6683 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6684 result= WhiteKing; // in Suicide Q is the last thing we want
6685 else if(gameInfo.variant == VariantSpartan)
6686 result = white ? WhiteQueen : WhiteAngel;
6687 else result = WhiteQueen;
6688 if(!white) result = WHITE_TO_BLACK result;
6692 static int autoQueen; // [HGM] oneclick
6695 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6697 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6698 /* [HGM] add Shogi promotions */
6699 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6700 ChessSquare piece, partner;
6704 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6705 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6707 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6708 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6711 if(legal[toY][toX] == 4) return FALSE;
6713 piece = boards[currentMove][fromY][fromX];
6714 if(gameInfo.variant == VariantChu) {
6715 promotionZoneSize = BOARD_HEIGHT/3;
6716 if(legal[toY][toX] == 6) return FALSE; // no promotion if highlights deny it
6717 highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6718 } else if(gameInfo.variant == VariantShogi) {
6719 promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6720 highestPromotingPiece = (int)WhiteAlfil;
6721 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6722 promotionZoneSize = 3;
6725 // Treat Lance as Pawn when it is not representing Amazon or Lance
6726 if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6727 if(piece == WhiteLance) piece = WhitePawn; else
6728 if(piece == BlackLance) piece = BlackPawn;
6731 // next weed out all moves that do not touch the promotion zone at all
6732 if((int)piece >= BlackPawn) {
6733 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6735 if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6736 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6738 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6739 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6740 if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6744 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6746 // weed out mandatory Shogi promotions
6747 if(gameInfo.variant == VariantShogi) {
6748 if(piece >= BlackPawn) {
6749 if(toY == 0 && piece == BlackPawn ||
6750 toY == 0 && piece == BlackQueen ||
6751 toY <= 1 && piece == BlackKnight) {
6756 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6757 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6758 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6765 // weed out obviously illegal Pawn moves
6766 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6767 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6768 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6769 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6770 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6771 // note we are not allowed to test for valid (non-)capture, due to premove
6774 // we either have a choice what to promote to, or (in Shogi) whether to promote
6775 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6776 gameInfo.variant == VariantMakruk) {
6777 ChessSquare p=BlackFerz; // no choice
6778 while(p < EmptySquare) { //but make sure we use piece that exists
6779 *promoChoice = PieceToChar(p++);
6780 if(*promoChoice != '.') break;
6782 if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6784 // no sense asking what we must promote to if it is going to explode...
6785 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6786 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6789 // give caller the default choice even if we will not make it
6790 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6791 partner = piece; // pieces can promote if the pieceToCharTable says so
6792 if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6793 else if(Partner(&partner)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6794 if( sweepSelect && gameInfo.variant != VariantGreat
6795 && gameInfo.variant != VariantGrand
6796 && gameInfo.variant != VariantSuper) return FALSE;
6797 if(autoQueen) return FALSE; // predetermined
6799 // suppress promotion popup on illegal moves that are not premoves
6800 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6801 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6802 if(appData.testLegality && !premove) {
6803 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6804 fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6805 if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6806 if(moveType != WhitePromotion && moveType != BlackPromotion)
6814 InPalace (int row, int column)
6815 { /* [HGM] for Xiangqi */
6816 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6817 column < (BOARD_WIDTH + 4)/2 &&
6818 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6823 PieceForSquare (int x, int y)
6825 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6828 return boards[currentMove][y][x];
6832 OKToStartUserMove (int x, int y)
6834 ChessSquare from_piece;
6837 if (matchMode) return FALSE;
6838 if (gameMode == EditPosition) return TRUE;
6840 if (x >= 0 && y >= 0)
6841 from_piece = boards[currentMove][y][x];
6843 from_piece = EmptySquare;
6845 if (from_piece == EmptySquare) return FALSE;
6847 white_piece = (int)from_piece >= (int)WhitePawn &&
6848 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6852 case TwoMachinesPlay:
6860 case MachinePlaysWhite:
6861 case IcsPlayingBlack:
6862 if (appData.zippyPlay) return FALSE;
6864 DisplayMoveError(_("You are playing Black"));
6869 case MachinePlaysBlack:
6870 case IcsPlayingWhite:
6871 if (appData.zippyPlay) return FALSE;
6873 DisplayMoveError(_("You are playing White"));
6878 case PlayFromGameFile:
6879 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6882 if (!white_piece && WhiteOnMove(currentMove)) {
6883 DisplayMoveError(_("It is White's turn"));
6886 if (white_piece && !WhiteOnMove(currentMove)) {
6887 DisplayMoveError(_("It is Black's turn"));
6890 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6891 /* Editing correspondence game history */
6892 /* Could disallow this or prompt for confirmation */
6897 case BeginningOfGame:
6898 if (appData.icsActive) return FALSE;
6899 if (!appData.noChessProgram) {
6901 DisplayMoveError(_("You are playing White"));
6908 if (!white_piece && WhiteOnMove(currentMove)) {
6909 DisplayMoveError(_("It is White's turn"));
6912 if (white_piece && !WhiteOnMove(currentMove)) {
6913 DisplayMoveError(_("It is Black's turn"));
6922 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6923 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6924 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6925 && gameMode != AnalyzeFile && gameMode != Training) {
6926 DisplayMoveError(_("Displayed position is not current"));
6933 OnlyMove (int *x, int *y, Boolean captures)
6935 DisambiguateClosure cl;
6936 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6938 case MachinePlaysBlack:
6939 case IcsPlayingWhite:
6940 case BeginningOfGame:
6941 if(!WhiteOnMove(currentMove)) return FALSE;
6943 case MachinePlaysWhite:
6944 case IcsPlayingBlack:
6945 if(WhiteOnMove(currentMove)) return FALSE;
6952 cl.pieceIn = EmptySquare;
6957 cl.promoCharIn = NULLCHAR;
6958 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6959 if( cl.kind == NormalMove ||
6960 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6961 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6962 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6969 if(cl.kind != ImpossibleMove) return FALSE;
6970 cl.pieceIn = EmptySquare;
6975 cl.promoCharIn = NULLCHAR;
6976 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6977 if( cl.kind == NormalMove ||
6978 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6979 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6980 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6985 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6991 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6992 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6993 int lastLoadGameUseList = FALSE;
6994 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6995 ChessMove lastLoadGameStart = EndOfFile;
6997 Boolean addToBookFlag;
6998 static Board rightsBoard, nullBoard;
7001 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
7005 int ff=fromX, rf=fromY, ft=toX, rt=toY;
7007 /* Check if the user is playing in turn. This is complicated because we
7008 let the user "pick up" a piece before it is his turn. So the piece he
7009 tried to pick up may have been captured by the time he puts it down!
7010 Therefore we use the color the user is supposed to be playing in this
7011 test, not the color of the piece that is currently on the starting
7012 square---except in EditGame mode, where the user is playing both
7013 sides; fortunately there the capture race can't happen. (It can
7014 now happen in IcsExamining mode, but that's just too bad. The user
7015 will get a somewhat confusing message in that case.)
7020 case TwoMachinesPlay:
7024 /* We switched into a game mode where moves are not accepted,
7025 perhaps while the mouse button was down. */
7028 case MachinePlaysWhite:
7029 /* User is moving for Black */
7030 if (WhiteOnMove(currentMove)) {
7031 DisplayMoveError(_("It is White's turn"));
7036 case MachinePlaysBlack:
7037 /* User is moving for White */
7038 if (!WhiteOnMove(currentMove)) {
7039 DisplayMoveError(_("It is Black's turn"));
7044 case PlayFromGameFile:
7045 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7048 case BeginningOfGame:
7051 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7052 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7053 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7054 /* User is moving for Black */
7055 if (WhiteOnMove(currentMove)) {
7056 DisplayMoveError(_("It is White's turn"));
7060 /* User is moving for White */
7061 if (!WhiteOnMove(currentMove)) {
7062 DisplayMoveError(_("It is Black's turn"));
7068 case IcsPlayingBlack:
7069 /* User is moving for Black */
7070 if (WhiteOnMove(currentMove)) {
7071 if (!appData.premove) {
7072 DisplayMoveError(_("It is White's turn"));
7073 } else if (toX >= 0 && toY >= 0) {
7076 premoveFromX = fromX;
7077 premoveFromY = fromY;
7078 premovePromoChar = promoChar;
7080 if (appData.debugMode)
7081 fprintf(debugFP, "Got premove: fromX %d,"
7082 "fromY %d, toX %d, toY %d\n",
7083 fromX, fromY, toX, toY);
7085 DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7090 case IcsPlayingWhite:
7091 /* User is moving for White */
7092 if (!WhiteOnMove(currentMove)) {
7093 if (!appData.premove) {
7094 DisplayMoveError(_("It is Black's turn"));
7095 } else if (toX >= 0 && toY >= 0) {
7098 premoveFromX = fromX;
7099 premoveFromY = fromY;
7100 premovePromoChar = promoChar;
7102 if (appData.debugMode)
7103 fprintf(debugFP, "Got premove: fromX %d,"
7104 "fromY %d, toX %d, toY %d\n",
7105 fromX, fromY, toX, toY);
7107 DrawPosition(TRUE, boards[currentMove]);
7116 /* EditPosition, empty square, or different color piece;
7117 click-click move is possible */
7118 if (toX == -2 || toY == -2) {
7119 boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7120 DrawPosition(FALSE, boards[currentMove]);
7122 } else if (toX >= 0 && toY >= 0) {
7123 if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7124 ChessSquare p = boards[0][rf][ff];
7125 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7126 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7127 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
7128 int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7129 DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7132 } else rightsBoard[toY][toX] = 0; // revoke rights on moving
7133 boards[0][toY][toX] = boards[0][fromY][fromX];
7134 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7135 if(boards[0][fromY][0] != EmptySquare) {
7136 if(boards[0][fromY][1]) boards[0][fromY][1]--;
7137 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
7140 if(fromX == BOARD_RGHT+1) {
7141 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7142 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7143 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7146 boards[0][fromY][fromX] = gatingPiece;
7148 DrawPosition(FALSE, boards[currentMove]);
7154 if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7155 pup = boards[currentMove][toY][toX];
7157 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7158 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7159 if( pup != EmptySquare ) return;
7160 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7161 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7162 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7163 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7164 if(fromX == 0) fromY = handSize-1 - fromY; // black holdings upside-down
7165 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7166 while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7170 /* [HGM] always test for legality, to get promotion info */
7171 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7172 fromY, fromX, toY, toX, promoChar);
7174 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7176 if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7178 /* [HGM] but possibly ignore an IllegalMove result */
7179 if (appData.testLegality) {
7180 if (moveType == IllegalMove || moveType == ImpossibleMove) {
7181 DisplayMoveError(_("Illegal move"));
7186 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7187 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7188 ClearPremoveHighlights(); // was included
7189 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
7190 DrawPosition(FALSE, NULL);
7194 if(addToBookFlag) { // adding moves to book
7195 char buf[MSG_SIZ], move[MSG_SIZ];
7196 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7197 if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7198 killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7199 snprintf(buf, MSG_SIZ, " 0.0%% 1 %s\n", move);
7201 addToBookFlag = FALSE;
7206 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7209 /* Common tail of UserMoveEvent and DropMenuEvent */
7211 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7215 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7216 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7217 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7218 if(WhiteOnMove(currentMove)) {
7219 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7221 if(!boards[currentMove][handSize-1-k][1]) return 0;
7225 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7226 move type in caller when we know the move is a legal promotion */
7227 if(moveType == NormalMove && promoChar)
7228 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7230 /* [HGM] <popupFix> The following if has been moved here from
7231 UserMoveEvent(). Because it seemed to belong here (why not allow
7232 piece drops in training games?), and because it can only be
7233 performed after it is known to what we promote. */
7234 if (gameMode == Training) {
7235 /* compare the move played on the board to the next move in the
7236 * game. If they match, display the move and the opponent's response.
7237 * If they don't match, display an error message.
7241 CopyBoard(testBoard, boards[currentMove]);
7242 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7244 if (CompareBoards(testBoard, boards[currentMove+1])) {
7245 ForwardInner(currentMove+1);
7247 /* Autoplay the opponent's response.
7248 * if appData.animate was TRUE when Training mode was entered,
7249 * the response will be animated.
7251 saveAnimate = appData.animate;
7252 appData.animate = animateTraining;
7253 ForwardInner(currentMove+1);
7254 appData.animate = saveAnimate;
7256 /* check for the end of the game */
7257 if (currentMove >= forwardMostMove) {
7258 gameMode = PlayFromGameFile;
7260 SetTrainingModeOff();
7261 DisplayInformation(_("End of game"));
7264 DisplayError(_("Incorrect move"), 0);
7269 /* Ok, now we know that the move is good, so we can kill
7270 the previous line in Analysis Mode */
7271 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7272 && currentMove < forwardMostMove) {
7273 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7274 else forwardMostMove = currentMove;
7279 /* If we need the chess program but it's dead, restart it */
7280 ResurrectChessProgram();
7282 /* A user move restarts a paused game*/
7286 thinkOutput[0] = NULLCHAR;
7288 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7290 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7291 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7295 if (gameMode == BeginningOfGame) {
7296 if (appData.noChessProgram) {
7297 gameMode = EditGame;
7301 gameMode = MachinePlaysBlack;
7304 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7306 if (first.sendName) {
7307 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7308 SendToProgram(buf, &first);
7315 /* Relay move to ICS or chess engine */
7316 if (appData.icsActive) {
7317 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7318 gameMode == IcsExamining) {
7319 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7320 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7322 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7324 // also send plain move, in case ICS does not understand atomic claims
7325 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7329 if (first.sendTime && (gameMode == BeginningOfGame ||
7330 gameMode == MachinePlaysWhite ||
7331 gameMode == MachinePlaysBlack)) {
7332 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7334 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7335 // [HGM] book: if program might be playing, let it use book
7336 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7337 first.maybeThinking = TRUE;
7338 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7339 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7340 SendBoard(&first, currentMove+1);
7341 if(second.analyzing) {
7342 if(!second.useSetboard) SendToProgram("undo\n", &second);
7343 SendBoard(&second, currentMove+1);
7346 SendMoveToProgram(forwardMostMove-1, &first);
7347 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7349 if (currentMove == cmailOldMove + 1) {
7350 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7354 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7358 if(appData.testLegality)
7359 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7365 if (WhiteOnMove(currentMove)) {
7366 GameEnds(BlackWins, "Black mates", GE_PLAYER);
7368 GameEnds(WhiteWins, "White mates", GE_PLAYER);
7372 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7377 case MachinePlaysBlack:
7378 case MachinePlaysWhite:
7379 /* disable certain menu options while machine is thinking */
7380 SetMachineThinkingEnables();
7387 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7388 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7390 if(bookHit) { // [HGM] book: simulate book reply
7391 static char bookMove[MSG_SIZ]; // a bit generous?
7393 programStats.nodes = programStats.depth = programStats.time =
7394 programStats.score = programStats.got_only_move = 0;
7395 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7397 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7398 strcat(bookMove, bookHit);
7399 HandleMachineMove(bookMove, &first);
7405 MarkByFEN(char *fen)
7408 if(!appData.markers || !appData.highlightDragging) return;
7409 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = marker[r][f] = 0;
7410 r=BOARD_HEIGHT-1-deadRanks; f=BOARD_LEFT;
7413 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7414 if(*fen == 'B') legal[r][f] = 4; else // request auto-promotion to victim
7415 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 6; else
7416 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7417 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7418 if(*fen == 'T') marker[r][f++] = 0; else
7419 if(*fen == 'Y') marker[r][f++] = 1; else
7420 if(*fen == 'G') marker[r][f++] = 3; else
7421 if(*fen == 'B') marker[r][f++] = 4; else
7422 if(*fen == 'C') marker[r][f++] = 5; else
7423 if(*fen == 'M') marker[r][f++] = 6; else
7424 if(*fen == 'W') marker[r][f++] = 7; else
7425 if(*fen == 'D') marker[r][f++] = 8; else
7426 if(*fen == 'R') marker[r][f++] = 2; else {
7427 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7430 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7434 DrawPosition(TRUE, NULL);
7437 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7440 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7442 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7443 Markers *m = (Markers *) closure;
7444 if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7445 kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7446 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7447 || kind == WhiteCapturesEnPassant
7448 || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7449 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7452 static int hoverSavedValid;
7455 MarkTargetSquares (int clear)
7458 if(clear) { // no reason to ever suppress clearing
7459 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7460 hoverSavedValid = 0;
7461 if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7464 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7465 !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7466 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7467 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7468 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7470 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;
7473 DrawPosition(FALSE, NULL);
7477 Explode (Board board, int fromX, int fromY, int toX, int toY)
7479 if(gameInfo.variant == VariantAtomic &&
7480 (board[toY][toX] != EmptySquare || // capture?
7481 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7482 board[fromY][fromX] == BlackPawn )
7484 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7490 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7493 CanPromote (ChessSquare piece, int y)
7495 int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7496 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7497 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7498 if(IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantXiangqi ||
7499 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7500 (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7501 gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7502 return (piece == BlackPawn && y <= zone ||
7503 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7504 piece == BlackLance && y <= zone ||
7505 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7509 HoverEvent (int xPix, int yPix, int x, int y)
7511 static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7513 if(!first.highlight) return;
7514 if(fromX != oldFromX || fromY != oldFromY) oldX = oldY = -1; // kludge to fake entry on from-click
7515 if(x == oldX && y == oldY) return; // only do something if we enter new square
7516 oldFromX = fromX; oldFromY = fromY;
7517 if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7518 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7519 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7520 hoverSavedValid = 1;
7521 } else if(oldX != x || oldY != y) {
7522 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7523 if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7524 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7525 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7526 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7528 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7529 SendToProgram(buf, &first);
7532 // SetHighlights(fromX, fromY, x, y);
7536 void ReportClick(char *action, int x, int y)
7538 char buf[MSG_SIZ]; // Inform engine of what user does
7540 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7541 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7542 legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7543 if(!first.highlight || gameMode == EditPosition) return;
7544 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7545 SendToProgram(buf, &first);
7548 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7549 Boolean deferChoice;
7552 LeftClick (ClickType clickType, int xPix, int yPix)
7555 static Boolean saveAnimate;
7556 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7557 char promoChoice = NULLCHAR;
7559 static TimeMark lastClickTime, prevClickTime;
7561 if(flashing) return;
7563 if(!deferChoice) { // when called for a retry, skip everything to the point where we left off
7564 x = EventToSquare(xPix, BOARD_WIDTH);
7565 y = EventToSquare(yPix, BOARD_HEIGHT);
7566 if (!flipView && y >= 0) {
7567 y = BOARD_HEIGHT - 1 - y;
7569 if (flipView && x >= 0) {
7570 x = BOARD_WIDTH - 1 - x;
7573 if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7575 RightClick(clickType, xPix, yPix, &dummy, &dummy);
7580 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7582 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7584 if (clickType == Press) ErrorPopDown();
7585 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7587 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7588 defaultPromoChoice = promoSweep;
7589 promoSweep = EmptySquare; // terminate sweep
7590 promoDefaultAltered = TRUE;
7591 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7594 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7595 if(clickType == Release) return; // ignore upclick of click-click destination
7596 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7597 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7598 if(gameInfo.holdingsWidth &&
7599 (WhiteOnMove(currentMove)
7600 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7601 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7602 // click in right holdings, for determining promotion piece
7603 ChessSquare p = boards[currentMove][y][x];
7604 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7605 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7606 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7607 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7612 DrawPosition(FALSE, boards[currentMove]);
7616 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7617 if(clickType == Press
7618 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7619 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7620 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7623 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7624 // could be static click on premove from-square: abort premove
7626 ClearPremoveHighlights();
7629 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7630 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7632 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7633 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7634 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7635 defaultPromoChoice = DefaultPromoChoice(side);
7638 autoQueen = appData.alwaysPromoteToQueen;
7642 gatingPiece = EmptySquare;
7643 if (clickType != Press) {
7644 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7645 DragPieceEnd(xPix, yPix); dragging = 0;
7646 DrawPosition(FALSE, NULL);
7650 doubleClick = FALSE;
7651 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7652 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7654 fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7655 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7656 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7657 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7659 if (OKToStartUserMove(fromX, fromY)) {
7661 ReportClick("lift", x, y);
7662 MarkTargetSquares(0);
7663 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7664 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7665 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7666 promoSweep = defaultPromoChoice;
7667 selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7668 Sweep(0); // Pawn that is going to promote: preview promotion piece
7669 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7671 if (appData.highlightDragging) {
7672 SetHighlights(fromX, fromY, -1, -1);
7676 } else fromX = fromY = -1;
7682 if (clickType == Press && gameMode != EditPosition) {
7687 // ignore off-board to clicks
7688 if(y < 0 || x < 0) return;
7690 /* Check if clicking again on the same color piece */
7691 fromP = boards[currentMove][fromY][fromX];
7692 toP = boards[currentMove][y][x];
7693 frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7694 if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7695 marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7696 ((WhitePawn <= fromP && fromP <= WhiteKing &&
7697 WhitePawn <= toP && toP <= WhiteKing &&
7698 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7699 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7700 (BlackPawn <= fromP && fromP <= BlackKing &&
7701 BlackPawn <= toP && toP <= BlackKing &&
7702 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7703 !(fromP == BlackKing && toP == BlackRook && frc)))) {
7704 /* Clicked again on same color piece -- changed his mind */
7705 second = (x == fromX && y == fromY);
7706 killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7707 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7708 second = FALSE; // first double-click rather than scond click
7709 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7711 promoDefaultAltered = FALSE;
7712 if(!second) MarkTargetSquares(1);
7713 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7714 if (appData.highlightDragging) {
7715 SetHighlights(x, y, -1, -1);
7719 if (OKToStartUserMove(x, y)) {
7720 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7721 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7722 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7723 gatingPiece = boards[currentMove][fromY][fromX];
7724 else gatingPiece = doubleClick ? fromP : EmptySquare;
7726 fromY = y; dragging = 1;
7727 if(!second) ReportClick("lift", x, y);
7728 MarkTargetSquares(0);
7729 DragPieceBegin(xPix, yPix, FALSE);
7730 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7731 promoSweep = defaultPromoChoice;
7732 selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7733 Sweep(0); // Pawn that is going to promote: preview promotion piece
7737 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7740 // ignore clicks on holdings
7741 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7744 if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7745 gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7746 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7750 if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7751 DragPieceEnd(xPix, yPix); dragging = 0;
7753 // a deferred attempt to click-click move an empty square on top of a piece
7754 boards[currentMove][y][x] = EmptySquare;
7756 DrawPosition(FALSE, boards[currentMove]);
7757 fromX = fromY = -1; clearFlag = 0;
7760 if (appData.animateDragging) {
7761 /* Undo animation damage if any */
7762 DrawPosition(FALSE, NULL);
7765 /* Second up/down in same square; just abort move */
7768 gatingPiece = EmptySquare;
7771 ClearPremoveHighlights();
7772 MarkTargetSquares(-1);
7773 DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7775 /* First upclick in same square; start click-click mode */
7776 SetHighlights(x, y, -1, -1);
7783 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7784 fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7785 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7786 DisplayMessage(_("only marked squares are legal"),"");
7787 DrawPosition(TRUE, NULL);
7788 return; // ignore to-click
7791 /* we now have a different from- and (possibly off-board) to-square */
7792 /* Completed move */
7793 if(!sweepSelecting) {
7798 piece = boards[currentMove][fromY][fromX];
7800 saveAnimate = appData.animate;
7801 if (clickType == Press) {
7802 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7803 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7804 // must be Edit Position mode with empty-square selected
7805 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7806 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7809 if(dragging == 2) { // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7812 if(x == killX && y == killY) { // second click on this square, which was selected as first-leg target
7813 killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1; // this informs us no second leg is coming, so treat as to-click without intermediate
7815 if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7816 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7817 if(appData.sweepSelect) {
7818 promoSweep = defaultPromoChoice;
7819 if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7820 selectFlag = 0; lastX = xPix; lastY = yPix;
7821 ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7822 saveFlash = appData.flashCount; appData.flashCount = 0;
7823 Sweep(0); // Pawn that is going to promote: preview promotion piece
7825 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7826 MarkTargetSquares(1);
7828 return; // promo popup appears on up-click
7830 /* Finish clickclick move */
7831 if (appData.animate || appData.highlightLastMove) {
7832 SetHighlights(fromX, fromY, toX, toY);
7836 MarkTargetSquares(1);
7837 } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7838 sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7839 *promoRestrict = 0; appData.flashCount = saveFlash;
7840 if (appData.animate || appData.highlightLastMove) {
7841 SetHighlights(fromX, fromY, toX, toY);
7845 MarkTargetSquares(1);
7848 // [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
7849 /* Finish drag move */
7850 if (appData.highlightLastMove) {
7851 SetHighlights(fromX, fromY, toX, toY);
7856 if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7857 defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7858 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7859 if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7860 dragging *= 2; // flag button-less dragging if we are dragging
7861 MarkTargetSquares(1);
7862 if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7864 kill2X = killX; kill2Y = killY;
7865 killX = x; killY = y; // remember this square as intermediate
7866 ReportClick("put", x, y); // and inform engine
7867 ReportClick("lift", x, y);
7868 MarkTargetSquares(0);
7872 DragPieceEnd(xPix, yPix); dragging = 0;
7873 /* Don't animate move and drag both */
7874 appData.animate = FALSE;
7875 MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7878 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7879 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7880 ChessSquare piece = boards[currentMove][fromY][fromX];
7881 if(gameMode == EditPosition && piece != EmptySquare &&
7882 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7885 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7886 n = PieceToNumber(piece - (int)BlackPawn);
7887 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7888 boards[currentMove][handSize-1 - n][0] = piece;
7889 boards[currentMove][handSize-1 - n][1]++;
7891 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7892 n = PieceToNumber(piece);
7893 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7894 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7895 boards[currentMove][n][BOARD_WIDTH-2]++;
7897 boards[currentMove][fromY][fromX] = EmptySquare;
7901 MarkTargetSquares(1);
7902 DrawPosition(TRUE, boards[currentMove]);
7906 // off-board moves should not be highlighted
7907 if(x < 0 || y < 0) {
7909 DrawPosition(FALSE, NULL);
7910 } else ReportClick("put", x, y);
7912 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7915 if(legal[toY][toX] == 2) { // highlight-induced promotion
7916 if(piece == defaultPromoChoice) promoChoice = NULLCHAR; // deferral
7917 else promoChoice = ToLower(PieceToChar(defaultPromoChoice));
7918 } else if(legal[toY][toX] == 4) { // blue target square: engine must supply promotion choice
7919 if(!*promoRestrict) { // but has not done that yet
7920 deferChoice = TRUE; // set up retry for when it does
7921 return; // and wait for that
7923 promoChoice = ToLower(*promoRestrict); // force engine's choice
7924 deferChoice = FALSE;
7927 if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7928 SetHighlights(fromX, fromY, toX, toY);
7929 MarkTargetSquares(1);
7930 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7931 // [HGM] super: promotion to captured piece selected from holdings
7932 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7933 promotionChoice = TRUE;
7934 // kludge follows to temporarily execute move on display, without promoting yet
7935 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7936 boards[currentMove][toY][toX] = p;
7937 DrawPosition(FALSE, boards[currentMove]);
7938 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7939 boards[currentMove][toY][toX] = q;
7940 DisplayMessage("Click in holdings to choose piece", "");
7943 DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
7944 PromotionPopUp(promoChoice);
7946 int oldMove = currentMove;
7947 flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
7948 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7949 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7950 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
7951 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7952 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7953 DrawPosition(TRUE, boards[currentMove]);
7954 else DrawPosition(FALSE, NULL);
7958 appData.animate = saveAnimate;
7959 if (appData.animate || appData.animateDragging) {
7960 /* Undo animation damage if needed */
7961 // DrawPosition(FALSE, NULL);
7966 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7967 { // front-end-free part taken out of PieceMenuPopup
7968 int whichMenu; int xSqr, ySqr;
7970 if(seekGraphUp) { // [HGM] seekgraph
7971 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7972 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7976 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7977 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7978 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7979 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7980 if(action == Press) {
7981 originalFlip = flipView;
7982 flipView = !flipView; // temporarily flip board to see game from partners perspective
7983 DrawPosition(TRUE, partnerBoard);
7984 DisplayMessage(partnerStatus, "");
7986 } else if(action == Release) {
7987 flipView = originalFlip;
7988 DrawPosition(TRUE, boards[currentMove]);
7994 xSqr = EventToSquare(x, BOARD_WIDTH);
7995 ySqr = EventToSquare(y, BOARD_HEIGHT);
7996 if (action == Release) {
7997 if(pieceSweep != EmptySquare) {
7998 EditPositionMenuEvent(pieceSweep, toX, toY);
7999 pieceSweep = EmptySquare;
8000 } else UnLoadPV(); // [HGM] pv
8002 if (action != Press) return -2; // return code to be ignored
8005 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
8007 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
8008 if (xSqr < 0 || ySqr < 0) return -1;
8009 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
8010 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
8011 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
8012 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
8016 if(!appData.icsEngineAnalyze) return -1;
8017 case IcsPlayingWhite:
8018 case IcsPlayingBlack:
8019 if(!appData.zippyPlay) goto noZip;
8022 case MachinePlaysWhite:
8023 case MachinePlaysBlack:
8024 case TwoMachinesPlay: // [HGM] pv: use for showing PV
8025 if (!appData.dropMenu) {
8027 return 2; // flag front-end to grab mouse events
8029 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8030 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8033 if (xSqr < 0 || ySqr < 0) return -1;
8034 if (!appData.dropMenu || appData.testLegality &&
8035 gameInfo.variant != VariantBughouse &&
8036 gameInfo.variant != VariantCrazyhouse) return -1;
8037 whichMenu = 1; // drop menu
8043 if (((*fromX = xSqr) < 0) ||
8044 ((*fromY = ySqr) < 0)) {
8045 *fromX = *fromY = -1;
8049 *fromX = BOARD_WIDTH - 1 - *fromX;
8051 *fromY = BOARD_HEIGHT - 1 - *fromY;
8057 Wheel (int dir, int x, int y)
8059 if(gameMode == EditPosition) {
8060 int xSqr = EventToSquare(x, BOARD_WIDTH);
8061 int ySqr = EventToSquare(y, BOARD_HEIGHT);
8062 if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8063 if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8065 boards[currentMove][ySqr][xSqr] += dir;
8066 if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8067 if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8068 } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8069 DrawPosition(FALSE, boards[currentMove]);
8070 } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8074 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8076 // char * hint = lastHint;
8077 FrontEndProgramStats stats;
8079 stats.which = cps == &first ? 0 : 1;
8080 stats.depth = cpstats->depth;
8081 stats.nodes = cpstats->nodes;
8082 stats.score = cpstats->score;
8083 stats.time = cpstats->time;
8084 stats.pv = cpstats->movelist;
8085 stats.hint = lastHint;
8086 stats.an_move_index = 0;
8087 stats.an_move_count = 0;
8089 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8090 stats.hint = cpstats->move_name;
8091 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8092 stats.an_move_count = cpstats->nr_moves;
8095 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
8097 if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8098 && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8100 SetProgramStats( &stats );
8104 ClearEngineOutputPane (int which)
8106 static FrontEndProgramStats dummyStats;
8107 dummyStats.which = which;
8108 dummyStats.pv = "#";
8109 SetProgramStats( &dummyStats );
8112 #define MAXPLAYERS 500
8115 TourneyStandings (int display)
8117 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8118 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8119 char result, *p, *names[MAXPLAYERS];
8121 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8122 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8123 names[0] = p = strdup(appData.participants);
8124 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8126 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8128 while(result = appData.results[nr]) {
8129 color = Pairing(nr, nPlayers, &w, &b, &dummy);
8130 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8131 wScore = bScore = 0;
8133 case '+': wScore = 2; break;
8134 case '-': bScore = 2; break;
8135 case '=': wScore = bScore = 1; break;
8137 case '*': return strdup("busy"); // tourney not finished
8145 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8146 for(w=0; w<nPlayers; w++) {
8148 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8149 ranking[w] = b; points[w] = bScore; score[b] = -2;
8151 p = malloc(nPlayers*34+1);
8152 for(w=0; w<nPlayers && w<display; w++)
8153 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8159 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8160 { // count all piece types
8162 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8163 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8164 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8167 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8168 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8169 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8170 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8171 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
8172 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8177 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8179 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8180 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8182 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8183 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8184 if(myPawns == 2 && nMine == 3) // KPP
8185 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8186 if(myPawns == 1 && nMine == 2) // KP
8187 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8188 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8189 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8190 if(myPawns) return FALSE;
8191 if(pCnt[WhiteRook+side])
8192 return pCnt[BlackRook-side] ||
8193 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8194 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8195 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8196 if(pCnt[WhiteCannon+side]) {
8197 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8198 return majorDefense || pCnt[BlackAlfil-side] >= 2;
8200 if(pCnt[WhiteKnight+side])
8201 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8206 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8208 VariantClass v = gameInfo.variant;
8210 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8211 if(v == VariantShatranj) return TRUE; // always winnable through baring
8212 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8213 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8215 if(v == VariantXiangqi) {
8216 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8218 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8219 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8220 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8221 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8222 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8223 if(stale) // we have at least one last-rank P plus perhaps C
8224 return majors // KPKX
8225 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8227 return pCnt[WhiteFerz+side] // KCAK
8228 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8229 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8230 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8232 } else if(v == VariantKnightmate) {
8233 if(nMine == 1) return FALSE;
8234 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8235 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8236 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8238 if(nMine == 1) return FALSE; // bare King
8239 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
8240 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8241 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8242 // by now we have King + 1 piece (or multiple Bishops on the same color)
8243 if(pCnt[WhiteKnight+side])
8244 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8245 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8246 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8248 return (pCnt[BlackKnight-side]); // KBKN, KFKN
8249 if(pCnt[WhiteAlfil+side])
8250 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8251 if(pCnt[WhiteWazir+side])
8252 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8259 CompareWithRights (Board b1, Board b2)
8262 if(!CompareBoards(b1, b2)) return FALSE;
8263 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8264 /* compare castling rights */
8265 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8266 rights++; /* King lost rights, while rook still had them */
8267 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8268 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8269 rights++; /* but at least one rook lost them */
8271 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8273 if( b1[CASTLING][5] != NoRights ) {
8274 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8281 Adjudicate (ChessProgramState *cps)
8282 { // [HGM] some adjudications useful with buggy engines
8283 // [HGM] adjudicate: made into separate routine, which now can be called after every move
8284 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8285 // Actually ending the game is now based on the additional internal condition canAdjudicate.
8286 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8287 int k, drop, count = 0; static int bare = 1;
8288 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8289 Boolean canAdjudicate = !appData.icsActive;
8291 // most tests only when we understand the game, i.e. legality-checking on
8292 if( appData.testLegality )
8293 { /* [HGM] Some more adjudications for obstinate engines */
8294 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8295 static int moveCount = 6;
8297 char *reason = NULL;
8299 /* Count what is on board. */
8300 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8302 /* Some material-based adjudications that have to be made before stalemate test */
8303 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8304 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8305 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8306 if(canAdjudicate && appData.checkMates) {
8308 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8309 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8310 "Xboard adjudication: King destroyed", GE_XBOARD );
8315 /* Bare King in Shatranj (loses) or Losers (wins) */
8316 if( nrW == 1 || nrB == 1) {
8317 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8318 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
8319 if(canAdjudicate && appData.checkMates) {
8321 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8322 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8323 "Xboard adjudication: Bare king", GE_XBOARD );
8327 if( gameInfo.variant == VariantShatranj && --bare < 0)
8329 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8330 if(canAdjudicate && appData.checkMates) {
8331 /* but only adjudicate if adjudication enabled */
8333 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8334 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8335 "Xboard adjudication: Bare king", GE_XBOARD );
8342 // don't wait for engine to announce game end if we can judge ourselves
8343 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8345 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8346 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
8347 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8348 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8351 reason = "Xboard adjudication: 3rd check";
8352 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8363 reason = "Xboard adjudication: Stalemate";
8364 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8365 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
8366 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8367 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
8368 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8369 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8370 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8371 EP_CHECKMATE : EP_WINS);
8372 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8373 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8377 reason = "Xboard adjudication: Checkmate";
8378 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8379 if(gameInfo.variant == VariantShogi) {
8380 if(forwardMostMove > backwardMostMove
8381 && moveList[forwardMostMove-1][1] == '@'
8382 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8383 reason = "XBoard adjudication: pawn-drop mate";
8384 boards[forwardMostMove][EP_STATUS] = EP_WINS;
8390 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8392 result = GameIsDrawn; break;
8394 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8396 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8400 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8402 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8403 GameEnds( result, reason, GE_XBOARD );
8407 /* Next absolutely insufficient mating material. */
8408 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8409 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8410 { /* includes KBK, KNK, KK of KBKB with like Bishops */
8412 /* always flag draws, for judging claims */
8413 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8415 if(canAdjudicate && appData.materialDraws) {
8416 /* but only adjudicate them if adjudication enabled */
8417 if(engineOpponent) {
8418 SendToProgram("force\n", engineOpponent); // suppress reply
8419 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8421 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8426 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8427 if(gameInfo.variant == VariantXiangqi ?
8428 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8430 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8431 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
8432 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
8433 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8435 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8436 { /* if the first 3 moves do not show a tactical win, declare draw */
8437 if(engineOpponent) {
8438 SendToProgram("force\n", engineOpponent); // suppress reply
8439 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8441 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8444 } else moveCount = 6;
8447 // Repetition draws and 50-move rule can be applied independently of legality testing
8449 /* Check for rep-draws */
8451 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8452 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8453 for(k = forwardMostMove-2;
8454 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8455 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8456 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8459 if(CompareBoards(boards[k], boards[forwardMostMove])) {
8460 /* compare castling rights */
8461 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8462 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8463 rights++; /* King lost rights, while rook still had them */
8464 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8465 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8466 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8467 rights++; /* but at least one rook lost them */
8469 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8470 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8472 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8473 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8474 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8477 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8478 && appData.drawRepeats > 1) {
8479 /* adjudicate after user-specified nr of repeats */
8480 int result = GameIsDrawn;
8481 char *details = "XBoard adjudication: repetition draw";
8482 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8483 // [HGM] xiangqi: check for forbidden perpetuals
8484 int m, ourPerpetual = 1, hisPerpetual = 1;
8485 for(m=forwardMostMove; m>k; m-=2) {
8486 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8487 ourPerpetual = 0; // the current mover did not always check
8488 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8489 hisPerpetual = 0; // the opponent did not always check
8491 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8492 ourPerpetual, hisPerpetual);
8493 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8494 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8495 details = "Xboard adjudication: perpetual checking";
8497 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8498 break; // (or we would have caught him before). Abort repetition-checking loop.
8500 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8501 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8503 details = "Xboard adjudication: repetition";
8505 } else // it must be XQ
8506 // Now check for perpetual chases
8507 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8508 hisPerpetual = PerpetualChase(k, forwardMostMove);
8509 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8510 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8511 static char resdet[MSG_SIZ];
8512 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8514 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8516 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8517 break; // Abort repetition-checking loop.
8519 // if neither of us is checking or chasing all the time, or both are, it is draw
8521 if(engineOpponent) {
8522 SendToProgram("force\n", engineOpponent); // suppress reply
8523 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8525 GameEnds( result, details, GE_XBOARD );
8528 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8529 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8533 /* Now we test for 50-move draws. Determine ply count */
8534 count = forwardMostMove;
8535 /* look for last irreversble move */
8536 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8538 /* if we hit starting position, add initial plies */
8539 if( count == backwardMostMove )
8540 count -= initialRulePlies;
8541 count = forwardMostMove - count;
8542 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8543 // adjust reversible move counter for checks in Xiangqi
8544 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8545 if(i < backwardMostMove) i = backwardMostMove;
8546 while(i <= forwardMostMove) {
8547 lastCheck = inCheck; // check evasion does not count
8548 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8549 if(inCheck || lastCheck) count--; // check does not count
8554 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8555 /* this is used to judge if draw claims are legal */
8556 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8557 if(engineOpponent) {
8558 SendToProgram("force\n", engineOpponent); // suppress reply
8559 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8561 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8565 /* if draw offer is pending, treat it as a draw claim
8566 * when draw condition present, to allow engines a way to
8567 * claim draws before making their move to avoid a race
8568 * condition occurring after their move
8570 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8572 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8573 p = "Draw claim: 50-move rule";
8574 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8575 p = "Draw claim: 3-fold repetition";
8576 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8577 p = "Draw claim: insufficient mating material";
8578 if( p != NULL && canAdjudicate) {
8579 if(engineOpponent) {
8580 SendToProgram("force\n", engineOpponent); // suppress reply
8581 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8583 GameEnds( GameIsDrawn, p, GE_XBOARD );
8588 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8589 if(engineOpponent) {
8590 SendToProgram("force\n", engineOpponent); // suppress reply
8591 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8593 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8599 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8600 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8601 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8606 int pieces[10], squares[10], cnt=0, r, f, res;
8608 static PPROBE_EGBB probeBB;
8609 if(!appData.testLegality) return 10;
8610 if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8611 if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8612 if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8613 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8614 ChessSquare piece = boards[forwardMostMove][r][f];
8615 int black = (piece >= BlackPawn);
8616 int type = piece - black*BlackPawn;
8617 if(piece == EmptySquare) continue;
8618 if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8619 if(type == WhiteKing) type = WhiteQueen + 1;
8620 type = egbbCode[type];
8621 squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8622 pieces[cnt] = type + black*6;
8623 if(++cnt > 5) return 11;
8625 pieces[cnt] = squares[cnt] = 0;
8627 if(loaded == 2) return 13; // loading failed before
8629 char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8632 loaded = 2; // prepare for failure
8633 if(!path) return 13; // no egbb installed
8634 strncpy(buf, path + 8, MSG_SIZ);
8635 if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8636 snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8637 lib = LoadLibrary(buf);
8638 if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8639 loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8640 probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8641 if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8642 p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8643 loaded = 1; // success!
8645 res = probeBB(forwardMostMove & 1, pieces, squares);
8646 return res > 0 ? 1 : res < 0 ? -1 : 0;
8650 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8651 { // [HGM] book: this routine intercepts moves to simulate book replies
8652 char *bookHit = NULL;
8654 if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8656 snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8657 SendToProgram(buf, cps);
8659 //first determine if the incoming move brings opponent into his book
8660 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8661 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8662 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8663 if(bookHit != NULL && !cps->bookSuspend) {
8664 // make sure opponent is not going to reply after receiving move to book position
8665 SendToProgram("force\n", cps);
8666 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8668 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8669 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8670 // now arrange restart after book miss
8672 // after a book hit we never send 'go', and the code after the call to this routine
8673 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8674 char buf[MSG_SIZ], *move = bookHit;
8676 int fromX, fromY, toX, toY;
8680 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8681 &fromX, &fromY, &toX, &toY, &promoChar)) {
8682 (void) CoordsToAlgebraic(boards[forwardMostMove],
8683 PosFlags(forwardMostMove),
8684 fromY, fromX, toY, toX, promoChar, move);
8686 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8690 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8691 SendToProgram(buf, cps);
8692 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8693 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8694 SendToProgram("go\n", cps);
8695 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8696 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8697 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8698 SendToProgram("go\n", cps);
8699 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8701 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8705 LoadError (char *errmess, ChessProgramState *cps)
8706 { // unloads engine and switches back to -ncp mode if it was first
8707 if(cps->initDone) return FALSE;
8708 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8709 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8712 appData.noChessProgram = TRUE;
8713 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8714 gameMode = BeginningOfGame; ModeHighlight();
8717 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8718 DisplayMessage("", ""); // erase waiting message
8719 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8724 ChessProgramState *savedState;
8726 DeferredBookMove (void)
8728 if(savedState->lastPing != savedState->lastPong)
8729 ScheduleDelayedEvent(DeferredBookMove, 10);
8731 HandleMachineMove(savedMessage, savedState);
8734 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8735 static ChessProgramState *stalledEngine;
8736 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8739 HandleMachineMove (char *message, ChessProgramState *cps)
8741 static char firstLeg[20], legs;
8742 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8743 char realname[MSG_SIZ];
8744 int fromX, fromY, toX, toY;
8746 char promoChar, roar;
8751 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8752 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8753 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8754 DisplayError(_("Invalid pairing from pairing engine"), 0);
8757 pairingReceived = 1;
8759 return; // Skim the pairing messages here.
8762 oldError = cps->userError; cps->userError = 0;
8764 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8766 * Kludge to ignore BEL characters
8768 while (*message == '\007') message++;
8771 * [HGM] engine debug message: ignore lines starting with '#' character
8773 if(cps->debug && *message == '#') return;
8776 * Look for book output
8778 if (cps == &first && bookRequested) {
8779 if (message[0] == '\t' || message[0] == ' ') {
8780 /* Part of the book output is here; append it */
8781 strcat(bookOutput, message);
8782 strcat(bookOutput, " \n");
8784 } else if (bookOutput[0] != NULLCHAR) {
8785 /* All of book output has arrived; display it */
8786 char *p = bookOutput;
8787 while (*p != NULLCHAR) {
8788 if (*p == '\t') *p = ' ';
8791 DisplayInformation(bookOutput);
8792 bookRequested = FALSE;
8793 /* Fall through to parse the current output */
8798 * Look for machine move.
8800 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8801 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8803 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8804 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8805 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8806 stalledEngine = cps;
8807 if(appData.ponderNextMove) { // bring opponent out of ponder
8808 if(gameMode == TwoMachinesPlay) {
8809 if(cps->other->pause)
8810 PauseEngine(cps->other);
8812 SendToProgram("easy\n", cps->other);
8821 /* This method is only useful on engines that support ping */
8822 if(abortEngineThink) {
8823 if (appData.debugMode) {
8824 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8826 SendToProgram("undo\n", cps);
8830 if (cps->lastPing != cps->lastPong) {
8831 /* Extra move from before last new; ignore */
8832 if (appData.debugMode) {
8833 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8840 int machineWhite = FALSE;
8843 case BeginningOfGame:
8844 /* Extra move from before last reset; ignore */
8845 if (appData.debugMode) {
8846 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8853 /* Extra move after we tried to stop. The mode test is
8854 not a reliable way of detecting this problem, but it's
8855 the best we can do on engines that don't support ping.
8857 if (appData.debugMode) {
8858 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8859 cps->which, gameMode);
8861 SendToProgram("undo\n", cps);
8864 case MachinePlaysWhite:
8865 case IcsPlayingWhite:
8866 machineWhite = TRUE;
8869 case MachinePlaysBlack:
8870 case IcsPlayingBlack:
8871 machineWhite = FALSE;
8874 case TwoMachinesPlay:
8875 machineWhite = (cps->twoMachinesColor[0] == 'w');
8878 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8879 if (appData.debugMode) {
8881 "Ignoring move out of turn by %s, gameMode %d"
8882 ", forwardMost %d\n",
8883 cps->which, gameMode, forwardMostMove);
8889 if(cps->alphaRank) AlphaRank(machineMove, 4);
8891 // [HGM] lion: (some very limited) support for Alien protocol
8892 killX = killY = kill2X = kill2Y = -1;
8893 if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8894 if(legs++) return; // middle leg contains only redundant info, ignore (but count it)
8895 safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8898 if(p = strchr(machineMove, ',')) { // we got both legs in one (happens on book move)
8899 char *q = strchr(p+1, ','); // second comma?
8900 safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8901 if(q) legs = 2, p = q; else legs = 1; // with 3-leg move we clipof first two legs!
8902 safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8904 if(firstLeg[0]) { // there was a previous leg;
8905 // only support case where same piece makes two step
8906 char buf[20], *p = machineMove+1, *q = buf+1, f;
8907 safeStrCpy(buf, machineMove, 20);
8908 while(isdigit(*q)) q++; // find start of to-square
8909 safeStrCpy(machineMove, firstLeg, 20);
8910 while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8911 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
8912 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)
8913 safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8914 sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8915 firstLeg[0] = NULLCHAR; legs = 0;
8918 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8919 &fromX, &fromY, &toX, &toY, &promoChar)) {
8920 /* Machine move could not be parsed; ignore it. */
8921 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8922 machineMove, _(cps->which));
8923 DisplayMoveError(buf1);
8924 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8925 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8926 if (gameMode == TwoMachinesPlay) {
8927 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8933 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8934 /* So we have to redo legality test with true e.p. status here, */
8935 /* to make sure an illegal e.p. capture does not slip through, */
8936 /* to cause a forfeit on a justified illegal-move complaint */
8937 /* of the opponent. */
8938 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8940 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8941 fromY, fromX, toY, toX, promoChar);
8942 if(moveType == IllegalMove) {
8943 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8944 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8945 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8948 } else if(!appData.fischerCastling)
8949 /* [HGM] Kludge to handle engines that send FRC-style castling
8950 when they shouldn't (like TSCP-Gothic) */
8952 case WhiteASideCastleFR:
8953 case BlackASideCastleFR:
8955 currentMoveString[2]++;
8957 case WhiteHSideCastleFR:
8958 case BlackHSideCastleFR:
8960 currentMoveString[2]--;
8962 default: ; // nothing to do, but suppresses warning of pedantic compilers
8965 hintRequested = FALSE;
8966 lastHint[0] = NULLCHAR;
8967 bookRequested = FALSE;
8968 /* Program may be pondering now */
8969 cps->maybeThinking = TRUE;
8970 if (cps->sendTime == 2) cps->sendTime = 1;
8971 if (cps->offeredDraw) cps->offeredDraw--;
8973 /* [AS] Save move info*/
8974 pvInfoList[ forwardMostMove ].score = programStats.score;
8975 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8976 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8978 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8980 /* Test suites abort the 'game' after one move */
8981 if(*appData.finger) {
8983 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8984 if(!f) f = fopen(appData.finger, "w");
8985 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8986 else { DisplayFatalError("Bad output file", errno, 0); return; }
8988 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8991 if(solvingTime >= 0) {
8992 snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
8993 totalTime += solvingTime; first.matchWins++; solvingTime = -1;
8995 snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
8996 if(solvingTime == -2) second.matchWins++;
8998 OutputKibitz(2, buf1);
8999 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9002 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
9003 if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
9006 while( count < adjudicateLossPlies ) {
9007 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
9010 score = -score; /* Flip score for winning side */
9013 if( score > appData.adjudicateLossThreshold ) {
9020 if( count >= adjudicateLossPlies ) {
9021 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9023 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
9024 "Xboard adjudication",
9031 if(Adjudicate(cps)) {
9032 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9033 return; // [HGM] adjudicate: for all automatic game ends
9037 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9039 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9040 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9042 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9044 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9046 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9047 char buf[3*MSG_SIZ];
9049 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9050 programStats.score / 100.,
9052 programStats.time / 100.,
9053 (unsigned int)programStats.nodes,
9054 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9055 programStats.movelist);
9061 /* [AS] Clear stats for next move */
9062 ClearProgramStats();
9063 thinkOutput[0] = NULLCHAR;
9064 hiddenThinkOutputState = 0;
9067 if (gameMode == TwoMachinesPlay) {
9068 /* [HGM] relaying draw offers moved to after reception of move */
9069 /* and interpreting offer as claim if it brings draw condition */
9070 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9071 SendToProgram("draw\n", cps->other);
9073 if (cps->other->sendTime) {
9074 SendTimeRemaining(cps->other,
9075 cps->other->twoMachinesColor[0] == 'w');
9077 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9078 if (firstMove && !bookHit) {
9080 if (cps->other->useColors) {
9081 SendToProgram(cps->other->twoMachinesColor, cps->other);
9083 SendToProgram("go\n", cps->other);
9085 cps->other->maybeThinking = TRUE;
9088 roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9090 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9092 if (!pausing && appData.ringBellAfterMoves) {
9093 if(!roar) RingBell();
9097 * Reenable menu items that were disabled while
9098 * machine was thinking
9100 if (gameMode != TwoMachinesPlay)
9101 SetUserThinkingEnables();
9103 // [HGM] book: after book hit opponent has received move and is now in force mode
9104 // force the book reply into it, and then fake that it outputted this move by jumping
9105 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9107 static char bookMove[MSG_SIZ]; // a bit generous?
9109 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9110 strcat(bookMove, bookHit);
9113 programStats.nodes = programStats.depth = programStats.time =
9114 programStats.score = programStats.got_only_move = 0;
9115 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9117 if(cps->lastPing != cps->lastPong) {
9118 savedMessage = message; // args for deferred call
9120 ScheduleDelayedEvent(DeferredBookMove, 10);
9129 /* Set special modes for chess engines. Later something general
9130 * could be added here; for now there is just one kludge feature,
9131 * needed because Crafty 15.10 and earlier don't ignore SIGINT
9132 * when "xboard" is given as an interactive command.
9134 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9135 cps->useSigint = FALSE;
9136 cps->useSigterm = FALSE;
9138 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9139 ParseFeatures(message+8, cps);
9140 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9143 if (!strncmp(message, "setup ", 6) &&
9144 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9145 NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9146 ) { // [HGM] allow first engine to define opening position
9147 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9148 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9150 if(sscanf(message, "setup (%s", buf) == 1) {
9151 s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9152 ASSIGN(appData.pieceToCharTable, buf);
9154 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9156 while(message[s] && message[s++] != ' ');
9157 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9158 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9159 if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
9160 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9161 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
9162 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9163 if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9164 startedFromSetupPosition = FALSE;
9167 if(startedFromSetupPosition) return;
9168 ParseFEN(boards[0], &dummy, message+s, FALSE);
9169 DrawPosition(TRUE, boards[0]);
9170 CopyBoard(initialPosition, boards[0]);
9171 startedFromSetupPosition = TRUE;
9174 if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9175 ChessSquare piece = WhitePawn;
9176 char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0;
9177 if(*p == '+') promoted++, ID = *++p;
9178 if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9179 piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece);
9180 if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9181 /* always accept definition of */ && piece != WhiteFalcon && piece != BlackFalcon
9182 /* wild-card pieces. */ && piece != WhiteCobra && piece != BlackCobra
9183 /* For variants we don't have */ && gameInfo.variant != VariantBerolina
9184 /* correct rules for, we cannot */ && gameInfo.variant != VariantCylinder
9185 /* enforce legality on our own! */ && gameInfo.variant != VariantUnknown
9186 && gameInfo.variant != VariantGreat
9187 && gameInfo.variant != VariantFairy ) return;
9188 if(piece < EmptySquare) {
9190 ASSIGN(pieceDesc[piece], buf1);
9191 if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9195 if(!appData.testLegality && sscanf(message, "choice %s", promoRestrict) == 1) {
9197 LeftClick(Press, 0, 0); // finish the click that was interrupted
9198 } else if(promoSweep != EmptySquare) {
9199 promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9200 if(strlen(promoRestrict) > 1) Sweep(0);
9204 /* [HGM] Allow engine to set up a position. Don't ask me why one would
9205 * want this, I was asked to put it in, and obliged.
9207 if (!strncmp(message, "setboard ", 9)) {
9208 Board initial_position;
9210 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9212 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9213 DisplayError(_("Bad FEN received from engine"), 0);
9217 CopyBoard(boards[0], initial_position);
9218 initialRulePlies = FENrulePlies;
9219 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9220 else gameMode = MachinePlaysBlack;
9221 DrawPosition(FALSE, boards[currentMove]);
9227 * Look for communication commands
9229 if (!strncmp(message, "telluser ", 9)) {
9230 if(message[9] == '\\' && message[10] == '\\')
9231 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9233 DisplayNote(message + 9);
9236 if (!strncmp(message, "tellusererror ", 14)) {
9238 if(message[14] == '\\' && message[15] == '\\')
9239 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9241 DisplayError(message + 14, 0);
9244 if (!strncmp(message, "tellopponent ", 13)) {
9245 if (appData.icsActive) {
9247 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9251 DisplayNote(message + 13);
9255 if (!strncmp(message, "tellothers ", 11)) {
9256 if (appData.icsActive) {
9258 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9261 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9264 if (!strncmp(message, "tellall ", 8)) {
9265 if (appData.icsActive) {
9267 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9271 DisplayNote(message + 8);
9275 if (strncmp(message, "warning", 7) == 0) {
9276 /* Undocumented feature, use tellusererror in new code */
9277 DisplayError(message, 0);
9280 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9281 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9282 strcat(realname, " query");
9283 AskQuestion(realname, buf2, buf1, cps->pr);
9286 /* Commands from the engine directly to ICS. We don't allow these to be
9287 * sent until we are logged on. Crafty kibitzes have been known to
9288 * interfere with the login process.
9291 if (!strncmp(message, "tellics ", 8)) {
9292 SendToICS(message + 8);
9296 if (!strncmp(message, "tellicsnoalias ", 15)) {
9297 SendToICS(ics_prefix);
9298 SendToICS(message + 15);
9302 /* The following are for backward compatibility only */
9303 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9304 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9305 SendToICS(ics_prefix);
9311 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9312 if(initPing == cps->lastPong) {
9313 if(gameInfo.variant == VariantUnknown) {
9314 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9315 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9316 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9320 if(cps->lastPing == cps->lastPong && abortEngineThink) {
9321 abortEngineThink = FALSE;
9322 DisplayMessage("", "");
9327 if(!strncmp(message, "highlight ", 10)) {
9328 if(appData.testLegality && !*engineVariant && appData.markers) return;
9329 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9332 if(!strncmp(message, "click ", 6)) {
9333 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9334 if(appData.testLegality || !appData.oneClick) return;
9335 sscanf(message+6, "%c%d%c", &f, &y, &c);
9336 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9337 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9338 x = x*squareSize + (x+1)*lineGap + squareSize/2;
9339 y = y*squareSize + (y+1)*lineGap + squareSize/2;
9340 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9341 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9342 LeftClick(Release, lastLeftX, lastLeftY);
9343 controlKey = (c == ',');
9344 LeftClick(Press, x, y);
9345 LeftClick(Release, x, y);
9346 first.highlight = f;
9350 * If the move is illegal, cancel it and redraw the board.
9351 * Also deal with other error cases. Matching is rather loose
9352 * here to accommodate engines written before the spec.
9354 if (strncmp(message + 1, "llegal move", 11) == 0 ||
9355 strncmp(message, "Error", 5) == 0) {
9356 if (StrStr(message, "name") ||
9357 StrStr(message, "rating") || StrStr(message, "?") ||
9358 StrStr(message, "result") || StrStr(message, "board") ||
9359 StrStr(message, "bk") || StrStr(message, "computer") ||
9360 StrStr(message, "variant") || StrStr(message, "hint") ||
9361 StrStr(message, "random") || StrStr(message, "depth") ||
9362 StrStr(message, "accepted")) {
9365 if (StrStr(message, "protover")) {
9366 /* Program is responding to input, so it's apparently done
9367 initializing, and this error message indicates it is
9368 protocol version 1. So we don't need to wait any longer
9369 for it to initialize and send feature commands. */
9370 FeatureDone(cps, 1);
9371 cps->protocolVersion = 1;
9374 cps->maybeThinking = FALSE;
9376 if (StrStr(message, "draw")) {
9377 /* Program doesn't have "draw" command */
9378 cps->sendDrawOffers = 0;
9381 if (cps->sendTime != 1 &&
9382 (StrStr(message, "time") || StrStr(message, "otim"))) {
9383 /* Program apparently doesn't have "time" or "otim" command */
9387 if (StrStr(message, "analyze")) {
9388 cps->analysisSupport = FALSE;
9389 cps->analyzing = FALSE;
9390 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9391 EditGameEvent(); // [HGM] try to preserve loaded game
9392 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9393 DisplayError(buf2, 0);
9396 if (StrStr(message, "(no matching move)st")) {
9397 /* Special kludge for GNU Chess 4 only */
9398 cps->stKludge = TRUE;
9399 SendTimeControl(cps, movesPerSession, timeControl,
9400 timeIncrement, appData.searchDepth,
9404 if (StrStr(message, "(no matching move)sd")) {
9405 /* Special kludge for GNU Chess 4 only */
9406 cps->sdKludge = TRUE;
9407 SendTimeControl(cps, movesPerSession, timeControl,
9408 timeIncrement, appData.searchDepth,
9412 if (!StrStr(message, "llegal")) {
9415 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9416 gameMode == IcsIdle) return;
9417 if (forwardMostMove <= backwardMostMove) return;
9418 if (pausing) PauseEvent();
9419 if(appData.forceIllegal) {
9420 // [HGM] illegal: machine refused move; force position after move into it
9421 SendToProgram("force\n", cps);
9422 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9423 // we have a real problem now, as SendBoard will use the a2a3 kludge
9424 // when black is to move, while there might be nothing on a2 or black
9425 // might already have the move. So send the board as if white has the move.
9426 // But first we must change the stm of the engine, as it refused the last move
9427 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9428 if(WhiteOnMove(forwardMostMove)) {
9429 SendToProgram("a7a6\n", cps); // for the engine black still had the move
9430 SendBoard(cps, forwardMostMove); // kludgeless board
9432 SendToProgram("a2a3\n", cps); // for the engine white still had the move
9433 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9434 SendBoard(cps, forwardMostMove+1); // kludgeless board
9436 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9437 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9438 gameMode == TwoMachinesPlay)
9439 SendToProgram("go\n", cps);
9442 if (gameMode == PlayFromGameFile) {
9443 /* Stop reading this game file */
9444 gameMode = EditGame;
9447 /* [HGM] illegal-move claim should forfeit game when Xboard */
9448 /* only passes fully legal moves */
9449 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9450 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9451 "False illegal-move claim", GE_XBOARD );
9452 return; // do not take back move we tested as valid
9454 currentMove = forwardMostMove-1;
9455 DisplayMove(currentMove-1); /* before DisplayMoveError */
9456 SwitchClocks(forwardMostMove-1); // [HGM] race
9457 DisplayBothClocks();
9458 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9459 parseList[currentMove], _(cps->which));
9460 DisplayMoveError(buf1);
9461 DrawPosition(FALSE, boards[currentMove]);
9463 SetUserThinkingEnables();
9466 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9467 /* Program has a broken "time" command that
9468 outputs a string not ending in newline.
9472 if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9473 if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9474 sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1 ) return;
9478 * If chess program startup fails, exit with an error message.
9479 * Attempts to recover here are futile. [HGM] Well, we try anyway
9481 if ((StrStr(message, "unknown host") != NULL)
9482 || (StrStr(message, "No remote directory") != NULL)
9483 || (StrStr(message, "not found") != NULL)
9484 || (StrStr(message, "No such file") != NULL)
9485 || (StrStr(message, "can't alloc") != NULL)
9486 || (StrStr(message, "Permission denied") != NULL)) {
9488 cps->maybeThinking = FALSE;
9489 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9490 _(cps->which), cps->program, cps->host, message);
9491 RemoveInputSource(cps->isr);
9492 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9493 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9494 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9500 * Look for hint output
9502 if (sscanf(message, "Hint: %s", buf1) == 1) {
9503 if (cps == &first && hintRequested) {
9504 hintRequested = FALSE;
9505 if (ParseOneMove(buf1, forwardMostMove, &moveType,
9506 &fromX, &fromY, &toX, &toY, &promoChar)) {
9507 (void) CoordsToAlgebraic(boards[forwardMostMove],
9508 PosFlags(forwardMostMove),
9509 fromY, fromX, toY, toX, promoChar, buf1);
9510 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9511 DisplayInformation(buf2);
9513 /* Hint move could not be parsed!? */
9514 snprintf(buf2, sizeof(buf2),
9515 _("Illegal hint move \"%s\"\nfrom %s chess program"),
9516 buf1, _(cps->which));
9517 DisplayError(buf2, 0);
9520 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9526 * Ignore other messages if game is not in progress
9528 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9529 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9532 * look for win, lose, draw, or draw offer
9534 if (strncmp(message, "1-0", 3) == 0) {
9535 char *p, *q, *r = "";
9536 p = strchr(message, '{');
9544 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9546 } else if (strncmp(message, "0-1", 3) == 0) {
9547 char *p, *q, *r = "";
9548 p = strchr(message, '{');
9556 /* Kludge for Arasan 4.1 bug */
9557 if (strcmp(r, "Black resigns") == 0) {
9558 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9561 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9563 } else if (strncmp(message, "1/2", 3) == 0) {
9564 char *p, *q, *r = "";
9565 p = strchr(message, '{');
9574 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9577 } else if (strncmp(message, "White resign", 12) == 0) {
9578 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9580 } else if (strncmp(message, "Black resign", 12) == 0) {
9581 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9583 } else if (strncmp(message, "White matches", 13) == 0 ||
9584 strncmp(message, "Black matches", 13) == 0 ) {
9585 /* [HGM] ignore GNUShogi noises */
9587 } else if (strncmp(message, "White", 5) == 0 &&
9588 message[5] != '(' &&
9589 StrStr(message, "Black") == NULL) {
9590 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9592 } else if (strncmp(message, "Black", 5) == 0 &&
9593 message[5] != '(') {
9594 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9596 } else if (strcmp(message, "resign") == 0 ||
9597 strcmp(message, "computer resigns") == 0) {
9599 case MachinePlaysBlack:
9600 case IcsPlayingBlack:
9601 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9603 case MachinePlaysWhite:
9604 case IcsPlayingWhite:
9605 GameEnds(BlackWins, "White resigns", GE_ENGINE);
9607 case TwoMachinesPlay:
9608 if (cps->twoMachinesColor[0] == 'w')
9609 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9611 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9618 } else if (strncmp(message, "opponent mates", 14) == 0) {
9620 case MachinePlaysBlack:
9621 case IcsPlayingBlack:
9622 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9624 case MachinePlaysWhite:
9625 case IcsPlayingWhite:
9626 GameEnds(BlackWins, "Black mates", GE_ENGINE);
9628 case TwoMachinesPlay:
9629 if (cps->twoMachinesColor[0] == 'w')
9630 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9632 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9639 } else if (strncmp(message, "computer mates", 14) == 0) {
9641 case MachinePlaysBlack:
9642 case IcsPlayingBlack:
9643 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9645 case MachinePlaysWhite:
9646 case IcsPlayingWhite:
9647 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9649 case TwoMachinesPlay:
9650 if (cps->twoMachinesColor[0] == 'w')
9651 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9653 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9660 } else if (strncmp(message, "checkmate", 9) == 0) {
9661 if (WhiteOnMove(forwardMostMove)) {
9662 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9664 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9667 } else if (strstr(message, "Draw") != NULL ||
9668 strstr(message, "game is a draw") != NULL) {
9669 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9671 } else if (strstr(message, "offer") != NULL &&
9672 strstr(message, "draw") != NULL) {
9674 if (appData.zippyPlay && first.initDone) {
9675 /* Relay offer to ICS */
9676 SendToICS(ics_prefix);
9677 SendToICS("draw\n");
9680 cps->offeredDraw = 2; /* valid until this engine moves twice */
9681 if (gameMode == TwoMachinesPlay) {
9682 if (cps->other->offeredDraw) {
9683 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9684 /* [HGM] in two-machine mode we delay relaying draw offer */
9685 /* until after we also have move, to see if it is really claim */
9687 } else if (gameMode == MachinePlaysWhite ||
9688 gameMode == MachinePlaysBlack) {
9689 if (userOfferedDraw) {
9690 DisplayInformation(_("Machine accepts your draw offer"));
9691 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9693 DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9700 * Look for thinking output
9702 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9703 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9705 int plylev, mvleft, mvtot, curscore, time;
9706 char mvname[MOVE_LEN];
9710 int prefixHint = FALSE;
9711 mvname[0] = NULLCHAR;
9714 case MachinePlaysBlack:
9715 case IcsPlayingBlack:
9716 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9718 case MachinePlaysWhite:
9719 case IcsPlayingWhite:
9720 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9725 case IcsObserving: /* [DM] icsEngineAnalyze */
9726 if (!appData.icsEngineAnalyze) ignore = TRUE;
9728 case TwoMachinesPlay:
9729 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9739 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9742 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9743 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9744 char score_buf[MSG_SIZ];
9746 if(nodes>>32 == u64Const(0xFFFFFFFF)) // [HGM] negative node count read
9747 nodes += u64Const(0x100000000);
9749 if (plyext != ' ' && plyext != '\t') {
9753 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9754 if( cps->scoreIsAbsolute &&
9755 ( gameMode == MachinePlaysBlack ||
9756 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9757 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9758 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9759 !WhiteOnMove(currentMove)
9762 curscore = -curscore;
9765 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9767 if(*bestMove) { // rememer time best EPD move was first found
9768 int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9769 ChessMove mt; char *p = bestMove;
9770 int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9772 while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9773 if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9774 solvingTime = (solvingTime < 0 ? time : solvingTime);
9778 while(*p && *p != ' ') p++;
9779 while(*p == ' ') p++;
9781 if(!solved) solvingTime = -1;
9783 if(*avoidMove && !solved) {
9784 int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9785 ChessMove mt; char *p = avoidMove, solved = 1;
9786 int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9787 while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9788 if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9789 solved = 0; solvingTime = -2;
9792 while(*p && *p != ' ') p++;
9793 while(*p == ' ') p++;
9795 if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9798 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9801 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9802 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9803 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9804 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9805 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9806 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9810 /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9811 DisplayError(_("failed writing PV"), 0);
9814 tempStats.depth = plylev;
9815 tempStats.nodes = nodes;
9816 tempStats.time = time;
9817 tempStats.score = curscore;
9818 tempStats.got_only_move = 0;
9820 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9823 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9824 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9825 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9826 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9827 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9828 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9829 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9830 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9833 /* Buffer overflow protection */
9834 if (pv[0] != NULLCHAR) {
9835 if (strlen(pv) >= sizeof(tempStats.movelist)
9836 && appData.debugMode) {
9838 "PV is too long; using the first %u bytes.\n",
9839 (unsigned) sizeof(tempStats.movelist) - 1);
9842 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9844 sprintf(tempStats.movelist, " no PV\n");
9847 if (tempStats.seen_stat) {
9848 tempStats.ok_to_send = 1;
9851 if (strchr(tempStats.movelist, '(') != NULL) {
9852 tempStats.line_is_book = 1;
9853 tempStats.nr_moves = 0;
9854 tempStats.moves_left = 0;
9856 tempStats.line_is_book = 0;
9859 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9860 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9862 SendProgramStatsToFrontend( cps, &tempStats );
9865 [AS] Protect the thinkOutput buffer from overflow... this
9866 is only useful if buf1 hasn't overflowed first!
9868 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9869 if(curscore >= MATE_SCORE)
9870 snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9871 else if(curscore <= -MATE_SCORE)
9872 snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9874 snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9875 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9877 (gameMode == TwoMachinesPlay ?
9878 ToUpper(cps->twoMachinesColor[0]) : ' '),
9880 prefixHint ? lastHint : "",
9881 prefixHint ? " " : "" );
9883 if( buf1[0] != NULLCHAR ) {
9884 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9886 if( strlen(pv) > max_len ) {
9887 if( appData.debugMode) {
9888 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9890 pv[max_len+1] = '\0';
9893 strcat( thinkOutput, pv);
9896 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9897 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9898 DisplayMove(currentMove - 1);
9902 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9903 /* crafty (9.25+) says "(only move) <move>"
9904 * if there is only 1 legal move
9906 sscanf(p, "(only move) %s", buf1);
9907 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9908 sprintf(programStats.movelist, "%s (only move)", buf1);
9909 programStats.depth = 1;
9910 programStats.nr_moves = 1;
9911 programStats.moves_left = 1;
9912 programStats.nodes = 1;
9913 programStats.time = 1;
9914 programStats.got_only_move = 1;
9916 /* Not really, but we also use this member to
9917 mean "line isn't going to change" (Crafty
9918 isn't searching, so stats won't change) */
9919 programStats.line_is_book = 1;
9921 SendProgramStatsToFrontend( cps, &programStats );
9923 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9924 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9925 DisplayMove(currentMove - 1);
9928 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9929 &time, &nodes, &plylev, &mvleft,
9930 &mvtot, mvname) >= 5) {
9931 /* The stat01: line is from Crafty (9.29+) in response
9932 to the "." command */
9933 programStats.seen_stat = 1;
9934 cps->maybeThinking = TRUE;
9936 if (programStats.got_only_move || !appData.periodicUpdates)
9939 programStats.depth = plylev;
9940 programStats.time = time;
9941 programStats.nodes = nodes;
9942 programStats.moves_left = mvleft;
9943 programStats.nr_moves = mvtot;
9944 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9945 programStats.ok_to_send = 1;
9946 programStats.movelist[0] = '\0';
9948 SendProgramStatsToFrontend( cps, &programStats );
9952 } else if (strncmp(message,"++",2) == 0) {
9953 /* Crafty 9.29+ outputs this */
9954 programStats.got_fail = 2;
9957 } else if (strncmp(message,"--",2) == 0) {
9958 /* Crafty 9.29+ outputs this */
9959 programStats.got_fail = 1;
9962 } else if (thinkOutput[0] != NULLCHAR &&
9963 strncmp(message, " ", 4) == 0) {
9964 unsigned message_len;
9967 while (*p && *p == ' ') p++;
9969 message_len = strlen( p );
9971 /* [AS] Avoid buffer overflow */
9972 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9973 strcat(thinkOutput, " ");
9974 strcat(thinkOutput, p);
9977 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9978 strcat(programStats.movelist, " ");
9979 strcat(programStats.movelist, p);
9982 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9983 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9984 DisplayMove(currentMove - 1);
9992 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9993 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9995 ChessProgramStats cpstats;
9997 if (plyext != ' ' && plyext != '\t') {
10001 /* [AS] Negate score if machine is playing black and reporting absolute scores */
10002 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
10003 curscore = -curscore;
10006 cpstats.depth = plylev;
10007 cpstats.nodes = nodes;
10008 cpstats.time = time;
10009 cpstats.score = curscore;
10010 cpstats.got_only_move = 0;
10011 cpstats.movelist[0] = '\0';
10013 if (buf1[0] != NULLCHAR) {
10014 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
10017 cpstats.ok_to_send = 0;
10018 cpstats.line_is_book = 0;
10019 cpstats.nr_moves = 0;
10020 cpstats.moves_left = 0;
10022 SendProgramStatsToFrontend( cps, &cpstats );
10029 /* Parse a game score from the character string "game", and
10030 record it as the history of the current game. The game
10031 score is NOT assumed to start from the standard position.
10032 The display is not updated in any way.
10035 ParseGameHistory (char *game)
10037 ChessMove moveType;
10038 int fromX, fromY, toX, toY, boardIndex, mask;
10043 if (appData.debugMode)
10044 fprintf(debugFP, "Parsing game history: %s\n", game);
10046 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10047 gameInfo.site = StrSave(appData.icsHost);
10048 gameInfo.date = PGNDate();
10049 gameInfo.round = StrSave("-");
10051 /* Parse out names of players */
10052 while (*game == ' ') game++;
10054 while (*game != ' ') *p++ = *game++;
10056 gameInfo.white = StrSave(buf);
10057 while (*game == ' ') game++;
10059 while (*game != ' ' && *game != '\n') *p++ = *game++;
10061 gameInfo.black = StrSave(buf);
10064 boardIndex = blackPlaysFirst ? 1 : 0;
10067 yyboardindex = boardIndex;
10068 moveType = (ChessMove) Myylex();
10069 switch (moveType) {
10070 case IllegalMove: /* maybe suicide chess, etc. */
10071 if (appData.debugMode) {
10072 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10073 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10074 setbuf(debugFP, NULL);
10076 case WhitePromotion:
10077 case BlackPromotion:
10078 case WhiteNonPromotion:
10079 case BlackNonPromotion:
10082 case WhiteCapturesEnPassant:
10083 case BlackCapturesEnPassant:
10084 case WhiteKingSideCastle:
10085 case WhiteQueenSideCastle:
10086 case BlackKingSideCastle:
10087 case BlackQueenSideCastle:
10088 case WhiteKingSideCastleWild:
10089 case WhiteQueenSideCastleWild:
10090 case BlackKingSideCastleWild:
10091 case BlackQueenSideCastleWild:
10093 case WhiteHSideCastleFR:
10094 case WhiteASideCastleFR:
10095 case BlackHSideCastleFR:
10096 case BlackASideCastleFR:
10098 fromX = currentMoveString[0] - AAA;
10099 fromY = currentMoveString[1] - ONE;
10100 toX = currentMoveString[2] - AAA;
10101 toY = currentMoveString[3] - ONE;
10102 promoChar = currentMoveString[4];
10106 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10107 fromX = moveType == WhiteDrop ?
10108 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10109 (int) CharToPiece(ToLower(currentMoveString[0]));
10111 toX = currentMoveString[2] - AAA;
10112 toY = currentMoveString[3] - ONE;
10113 promoChar = NULLCHAR;
10115 case AmbiguousMove:
10117 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10118 if (appData.debugMode) {
10119 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10120 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10121 setbuf(debugFP, NULL);
10123 DisplayError(buf, 0);
10125 case ImpossibleMove:
10127 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10128 if (appData.debugMode) {
10129 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10130 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10131 setbuf(debugFP, NULL);
10133 DisplayError(buf, 0);
10136 if (boardIndex < backwardMostMove) {
10137 /* Oops, gap. How did that happen? */
10138 DisplayError(_("Gap in move list"), 0);
10141 backwardMostMove = blackPlaysFirst ? 1 : 0;
10142 if (boardIndex > forwardMostMove) {
10143 forwardMostMove = boardIndex;
10147 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10148 strcat(parseList[boardIndex-1], " ");
10149 strcat(parseList[boardIndex-1], yy_text);
10161 case GameUnfinished:
10162 if (gameMode == IcsExamining) {
10163 if (boardIndex < backwardMostMove) {
10164 /* Oops, gap. How did that happen? */
10167 backwardMostMove = blackPlaysFirst ? 1 : 0;
10170 gameInfo.result = moveType;
10171 p = strchr(yy_text, '{');
10172 if (p == NULL) p = strchr(yy_text, '(');
10175 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10177 q = strchr(p, *p == '{' ? '}' : ')');
10178 if (q != NULL) *q = NULLCHAR;
10181 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10182 gameInfo.resultDetails = StrSave(p);
10185 if (boardIndex >= forwardMostMove &&
10186 !(gameMode == IcsObserving && ics_gamenum == -1)) {
10187 backwardMostMove = blackPlaysFirst ? 1 : 0;
10190 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10191 fromY, fromX, toY, toX, promoChar,
10192 parseList[boardIndex]);
10193 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10194 /* currentMoveString is set as a side-effect of yylex */
10195 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10196 strcat(moveList[boardIndex], "\n");
10198 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10199 mask = (WhiteOnMove(boardIndex) ? 0xFF : 0xFF00);
10200 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10206 if(boards[boardIndex][CHECK_COUNT]) boards[boardIndex][CHECK_COUNT] -= mask & 0x101;
10207 if(!boards[boardIndex][CHECK_COUNT] || boards[boardIndex][CHECK_COUNT] & mask) {
10208 if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[boardIndex - 1], "+");
10213 strcat(parseList[boardIndex - 1], "#");
10220 /* Apply a move to the given board */
10222 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10224 ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, epFile, lastFile, lastRank, berolina = 0;
10225 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10227 /* [HGM] compute & store e.p. status and castling rights for new position */
10228 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10230 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10231 oldEP = (signed char)board[EP_STATUS]; epRank = board[EP_RANK]; epFile = board[EP_FILE]; lastFile = board[LAST_TO] & 255,lastRank = board[LAST_TO] >> 8;
10232 board[EP_STATUS] = EP_NONE;
10233 board[EP_FILE] = board[EP_RANK] = 100, board[LAST_TO] = 0x4040;
10235 if (fromY == DROP_RANK) {
10236 /* must be first */
10237 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10238 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10241 piece = board[toY][toX] = (ChessSquare) fromX;
10243 // ChessSquare victim;
10246 if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10247 // victim = board[killY][killX],
10248 killed = board[killY][killX],
10249 board[killY][killX] = EmptySquare,
10250 board[EP_STATUS] = EP_CAPTURE;
10251 if( kill2X >= 0 && kill2Y >= 0)
10252 killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10255 if( board[toY][toX] != EmptySquare ) {
10256 board[EP_STATUS] = EP_CAPTURE;
10257 if( (fromX != toX || fromY != toY) && // not igui!
10258 (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10259 captured == BlackLion && board[fromY][fromX] != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules
10260 board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10264 pawn = board[fromY][fromX];
10265 if(pieceDesc[pawn] && strchr(pieceDesc[pawn], 'e')) { // piece with user-defined e.p. capture
10266 if(captured == EmptySquare && toX == epFile && (toY == (epRank & 127) || toY + (pawn < BlackPawn ? -1 : 1) == epRank - 128)) {
10267 captured = board[lastRank][lastFile]; // remove victim
10268 board[lastRank][lastFile] = EmptySquare;
10269 pawn = EmptySquare; // kludge to suppress old e.p. code
10272 if( pawn == WhiteLance || pawn == BlackLance ) {
10273 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10274 if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10275 else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10278 if( pawn == WhitePawn ) {
10279 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10280 board[EP_STATUS] = EP_PAWN_MOVE;
10281 if( toY-fromY>=2) {
10282 board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10283 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
10284 gameInfo.variant != VariantBerolina || toX < fromX)
10285 board[EP_STATUS] = toX | berolina;
10286 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10287 gameInfo.variant != VariantBerolina || toX > fromX)
10288 board[EP_STATUS] = toX;
10289 board[LAST_TO] = toX + 256*toY;
10292 if( pawn == BlackPawn ) {
10293 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10294 board[EP_STATUS] = EP_PAWN_MOVE;
10295 if( toY-fromY<= -2) {
10296 board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10297 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
10298 gameInfo.variant != VariantBerolina || toX < fromX)
10299 board[EP_STATUS] = toX | berolina;
10300 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10301 gameInfo.variant != VariantBerolina || toX > fromX)
10302 board[EP_STATUS] = toX;
10303 board[LAST_TO] = toX + 256*toY;
10307 if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10308 if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10309 if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10310 if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10312 for(i=0; i<nrCastlingRights; i++) {
10313 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10314 board[CASTLING][i] == toX && castlingRank[i] == toY
10315 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10318 if(gameInfo.variant == VariantSChess) { // update virginity
10319 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10320 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10321 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
10322 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
10325 if (fromX == toX && fromY == toY && killX < 0) return;
10327 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10328 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10329 if(gameInfo.variant == VariantKnightmate)
10330 king += (int) WhiteUnicorn - (int) WhiteKing;
10332 if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10333 && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) { // and tramples own
10334 board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10335 board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10336 board[EP_STATUS] = EP_NONE; // capture was fake!
10338 if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10339 board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10340 board[toY][toX] = piece;
10341 board[EP_STATUS] = EP_NONE; // capture was fake!
10343 /* Code added by Tord: */
10344 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10345 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10346 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10347 board[EP_STATUS] = EP_NONE; // capture was fake!
10348 board[fromY][fromX] = EmptySquare;
10349 board[toY][toX] = EmptySquare;
10350 if((toX > fromX) != (piece == WhiteRook)) {
10351 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10353 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10355 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10356 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10357 board[EP_STATUS] = EP_NONE;
10358 board[fromY][fromX] = EmptySquare;
10359 board[toY][toX] = EmptySquare;
10360 if((toX > fromX) != (piece == BlackRook)) {
10361 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10363 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10365 /* End of code added by Tord */
10367 } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10368 board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10369 board[toY][toX] = piece;
10370 } else if (board[fromY][fromX] == king
10371 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10372 && toY == fromY && toX > fromX+1) {
10373 for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10374 ; // castle with nearest piece
10375 board[fromY][toX-1] = board[fromY][rookX];
10376 board[fromY][rookX] = EmptySquare;
10377 board[fromY][fromX] = EmptySquare;
10378 board[toY][toX] = king;
10379 } else if (board[fromY][fromX] == king
10380 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10381 && toY == fromY && toX < fromX-1) {
10382 for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10383 ; // castle with nearest piece
10384 board[fromY][toX+1] = board[fromY][rookX];
10385 board[fromY][rookX] = EmptySquare;
10386 board[fromY][fromX] = EmptySquare;
10387 board[toY][toX] = king;
10388 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10389 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10390 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10392 /* white pawn promotion */
10393 board[toY][toX] = CharToPiece(ToUpper(promoChar));
10394 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10395 board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10396 board[fromY][fromX] = EmptySquare;
10397 } else if ((fromY >= BOARD_HEIGHT>>1)
10398 && (epFile == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10400 && gameInfo.variant != VariantXiangqi
10401 && gameInfo.variant != VariantBerolina
10402 && (pawn == WhitePawn)
10403 && (board[toY][toX] == EmptySquare)) {
10404 if(lastFile == 100) lastFile = (board[fromY][toX] == BlackPawn ? toX : fromX), lastRank = fromY; // assume FIDE e.p. if victim present
10405 board[fromY][fromX] = EmptySquare;
10406 board[toY][toX] = piece;
10407 captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10408 } else if ((fromY == BOARD_HEIGHT-4)
10410 && gameInfo.variant == VariantBerolina
10411 && (board[fromY][fromX] == WhitePawn)
10412 && (board[toY][toX] == EmptySquare)) {
10413 board[fromY][fromX] = EmptySquare;
10414 board[toY][toX] = WhitePawn;
10415 if(oldEP & EP_BEROLIN_A) {
10416 captured = board[fromY][fromX-1];
10417 board[fromY][fromX-1] = EmptySquare;
10418 }else{ captured = board[fromY][fromX+1];
10419 board[fromY][fromX+1] = EmptySquare;
10421 } else if (board[fromY][fromX] == king
10422 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10423 && toY == fromY && toX > fromX+1) {
10424 for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10426 board[fromY][toX-1] = board[fromY][rookX];
10427 board[fromY][rookX] = EmptySquare;
10428 board[fromY][fromX] = EmptySquare;
10429 board[toY][toX] = king;
10430 } else if (board[fromY][fromX] == king
10431 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10432 && toY == fromY && toX < fromX-1) {
10433 for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10435 board[fromY][toX+1] = board[fromY][rookX];
10436 board[fromY][rookX] = EmptySquare;
10437 board[fromY][fromX] = EmptySquare;
10438 board[toY][toX] = king;
10439 } else if (fromY == 7 && fromX == 3
10440 && board[fromY][fromX] == BlackKing
10441 && toY == 7 && toX == 5) {
10442 board[fromY][fromX] = EmptySquare;
10443 board[toY][toX] = BlackKing;
10444 board[fromY][7] = EmptySquare;
10445 board[toY][4] = BlackRook;
10446 } else if (fromY == 7 && fromX == 3
10447 && board[fromY][fromX] == BlackKing
10448 && toY == 7 && toX == 1) {
10449 board[fromY][fromX] = EmptySquare;
10450 board[toY][toX] = BlackKing;
10451 board[fromY][0] = EmptySquare;
10452 board[toY][2] = BlackRook;
10453 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10454 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10455 && toY < promoRank && promoChar
10457 /* black pawn promotion */
10458 board[toY][toX] = CharToPiece(ToLower(promoChar));
10459 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10460 board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10461 board[fromY][fromX] = EmptySquare;
10462 } else if ((fromY < BOARD_HEIGHT>>1)
10463 && (epFile == toX && epRank == toY || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10465 && gameInfo.variant != VariantXiangqi
10466 && gameInfo.variant != VariantBerolina
10467 && (pawn == BlackPawn)
10468 && (board[toY][toX] == EmptySquare)) {
10469 if(lastFile == 100) lastFile = (board[fromY][toX] == WhitePawn ? toX : fromX), lastRank = fromY;
10470 board[fromY][fromX] = EmptySquare;
10471 board[toY][toX] = piece;
10472 captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10473 } else if ((fromY == 3)
10475 && gameInfo.variant == VariantBerolina
10476 && (board[fromY][fromX] == BlackPawn)
10477 && (board[toY][toX] == EmptySquare)) {
10478 board[fromY][fromX] = EmptySquare;
10479 board[toY][toX] = BlackPawn;
10480 if(oldEP & EP_BEROLIN_A) {
10481 captured = board[fromY][fromX-1];
10482 board[fromY][fromX-1] = EmptySquare;
10483 }else{ captured = board[fromY][fromX+1];
10484 board[fromY][fromX+1] = EmptySquare;
10487 ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10488 board[fromY][fromX] = EmptySquare;
10489 board[toY][toX] = piece;
10493 if (gameInfo.holdingsWidth != 0) {
10495 /* !!A lot more code needs to be written to support holdings */
10496 /* [HGM] OK, so I have written it. Holdings are stored in the */
10497 /* penultimate board files, so they are automaticlly stored */
10498 /* in the game history. */
10499 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10500 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10501 /* Delete from holdings, by decreasing count */
10502 /* and erasing image if necessary */
10503 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10504 if(p < (int) BlackPawn) { /* white drop */
10505 p -= (int)WhitePawn;
10506 p = PieceToNumber((ChessSquare)p);
10507 if(p >= gameInfo.holdingsSize) p = 0;
10508 if(--board[p][BOARD_WIDTH-2] <= 0)
10509 board[p][BOARD_WIDTH-1] = EmptySquare;
10510 if((int)board[p][BOARD_WIDTH-2] < 0)
10511 board[p][BOARD_WIDTH-2] = 0;
10512 } else { /* black drop */
10513 p -= (int)BlackPawn;
10514 p = PieceToNumber((ChessSquare)p);
10515 if(p >= gameInfo.holdingsSize) p = 0;
10516 if(--board[handSize-1-p][1] <= 0)
10517 board[handSize-1-p][0] = EmptySquare;
10518 if((int)board[handSize-1-p][1] < 0)
10519 board[handSize-1-p][1] = 0;
10522 if (captured != EmptySquare && gameInfo.holdingsSize > 0
10523 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
10524 /* [HGM] holdings: Add to holdings, if holdings exist */
10525 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10526 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10527 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10529 p = (int) captured;
10530 if (p >= (int) BlackPawn) {
10531 p -= (int)BlackPawn;
10532 if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10533 /* Restore shogi-promoted piece to its original first */
10534 captured = (ChessSquare) (DEMOTED(captured));
10537 p = PieceToNumber((ChessSquare)p);
10538 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10539 board[p][BOARD_WIDTH-2]++;
10540 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10542 p -= (int)WhitePawn;
10543 if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10544 captured = (ChessSquare) (DEMOTED(captured));
10547 p = PieceToNumber((ChessSquare)p);
10548 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10549 board[handSize-1-p][1]++;
10550 board[handSize-1-p][0] = WHITE_TO_BLACK captured;
10553 } else if (gameInfo.variant == VariantAtomic) {
10554 if (captured != EmptySquare) {
10556 for (y = toY-1; y <= toY+1; y++) {
10557 for (x = toX-1; x <= toX+1; x++) {
10558 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10559 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10560 board[y][x] = EmptySquare;
10564 board[toY][toX] = EmptySquare;
10568 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10569 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10571 if(promoChar == '+') {
10572 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10573 board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10574 if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10575 board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10576 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10577 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10578 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10579 && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10580 board[toY][toX] = newPiece;
10582 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10583 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10584 // [HGM] superchess: take promotion piece out of holdings
10585 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10586 if((int)piece < (int)BlackPawn) { // determine stm from piece color
10587 if(!--board[k][BOARD_WIDTH-2])
10588 board[k][BOARD_WIDTH-1] = EmptySquare;
10590 if(!--board[handSize-1-k][1])
10591 board[handSize-1-k][0] = EmptySquare;
10596 /* Updates forwardMostMove */
10598 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10600 int x = toX, y = toY, mask;
10601 char *s = parseList[forwardMostMove];
10602 ChessSquare p = boards[forwardMostMove][toY][toX];
10603 // forwardMostMove++; // [HGM] bare: moved downstream
10605 if(kill2X >= 0) x = kill2X, y = kill2Y; else
10606 if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10607 (void) CoordsToAlgebraic(boards[forwardMostMove],
10608 PosFlags(forwardMostMove),
10609 fromY, fromX, y, x, (killX < 0)*promoChar,
10611 if(kill2X >= 0 && kill2Y >= 0)
10612 sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10613 if(killX >= 0 && killY >= 0)
10614 sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10615 toX + AAA, toY + ONE - '0', promoChar);
10617 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10618 int timeLeft; static int lastLoadFlag=0; int king, piece;
10619 piece = boards[forwardMostMove][fromY][fromX];
10620 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10621 if(gameInfo.variant == VariantKnightmate)
10622 king += (int) WhiteUnicorn - (int) WhiteKing;
10623 if(forwardMostMove == 0) {
10624 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10625 fprintf(serverMoves, "%s;", UserName());
10626 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10627 fprintf(serverMoves, "%s;", second.tidy);
10628 fprintf(serverMoves, "%s;", first.tidy);
10629 if(gameMode == MachinePlaysWhite)
10630 fprintf(serverMoves, "%s;", UserName());
10631 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10632 fprintf(serverMoves, "%s;", second.tidy);
10633 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10634 lastLoadFlag = loadFlag;
10636 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10637 // print castling suffix
10638 if( toY == fromY && piece == king ) {
10640 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10642 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10645 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10646 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
10647 boards[forwardMostMove][toY][toX] == EmptySquare
10648 && fromX != toX && fromY != toY)
10649 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10650 // promotion suffix
10651 if(promoChar != NULLCHAR) {
10652 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10653 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10654 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10655 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10658 char buf[MOVE_LEN*2], *p; int len;
10659 fprintf(serverMoves, "/%d/%d",
10660 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10661 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10662 else timeLeft = blackTimeRemaining/1000;
10663 fprintf(serverMoves, "/%d", timeLeft);
10664 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10665 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10666 if(p = strchr(buf, '=')) *p = NULLCHAR;
10667 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10668 fprintf(serverMoves, "/%s", buf);
10670 fflush(serverMoves);
10673 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10674 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10677 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10678 if (commentList[forwardMostMove+1] != NULL) {
10679 free(commentList[forwardMostMove+1]);
10680 commentList[forwardMostMove+1] = NULL;
10682 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10683 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10684 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10685 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10686 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10687 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10688 adjustedClock = FALSE;
10689 gameInfo.result = GameUnfinished;
10690 if (gameInfo.resultDetails != NULL) {
10691 free(gameInfo.resultDetails);
10692 gameInfo.resultDetails = NULL;
10694 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10695 moveList[forwardMostMove - 1]);
10696 mask = (WhiteOnMove(forwardMostMove) ? 0xFF : 0xFF00);
10697 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10703 if(boards[forwardMostMove][CHECK_COUNT]) boards[forwardMostMove][CHECK_COUNT] -= mask & 0x101;
10704 if(!boards[forwardMostMove][CHECK_COUNT] || boards[forwardMostMove][CHECK_COUNT] & mask) {
10705 if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[forwardMostMove - 1], "+");
10710 strcat(parseList[forwardMostMove - 1], "#");
10715 /* Updates currentMove if not pausing */
10717 ShowMove (int fromX, int fromY, int toX, int toY)
10719 int instant = (gameMode == PlayFromGameFile) ?
10720 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10721 if(appData.noGUI) return;
10722 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10724 if (forwardMostMove == currentMove + 1) {
10725 AnimateMove(boards[forwardMostMove - 1],
10726 fromX, fromY, toX, toY);
10729 currentMove = forwardMostMove;
10732 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10734 if (instant) return;
10736 DisplayMove(currentMove - 1);
10737 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10738 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10739 SetHighlights(fromX, fromY, toX, toY);
10742 DrawPosition(FALSE, boards[currentMove]);
10743 DisplayBothClocks();
10744 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10748 SendEgtPath (ChessProgramState *cps)
10749 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10750 char buf[MSG_SIZ], name[MSG_SIZ], *p;
10752 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10755 char c, *q = name+1, *r, *s;
10757 name[0] = ','; // extract next format name from feature and copy with prefixed ','
10758 while(*p && *p != ',') *q++ = *p++;
10759 *q++ = ':'; *q = 0;
10760 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10761 strcmp(name, ",nalimov:") == 0 ) {
10762 // take nalimov path from the menu-changeable option first, if it is defined
10763 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10764 SendToProgram(buf,cps); // send egtbpath command for nalimov
10766 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10767 (s = StrStr(appData.egtFormats, name)) != NULL) {
10768 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10769 s = r = StrStr(s, ":") + 1; // beginning of path info
10770 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10771 c = *r; *r = 0; // temporarily null-terminate path info
10772 *--q = 0; // strip of trailig ':' from name
10773 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10775 SendToProgram(buf,cps); // send egtbpath command for this format
10777 if(*p == ',') p++; // read away comma to position for next format name
10782 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10784 int width = 8, height = 8, holdings = 0; // most common sizes
10785 if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10786 // correct the deviations default for each variant
10787 if( v == VariantXiangqi ) width = 9, height = 10;
10788 if( v == VariantShogi ) width = 9, height = 9, holdings = 7;
10789 if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10790 if( v == VariantCapablanca || v == VariantCapaRandom ||
10791 v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10793 if( v == VariantCourier ) width = 12;
10794 if( v == VariantSuper ) holdings = 8;
10795 if( v == VariantGreat ) width = 10, holdings = 8;
10796 if( v == VariantSChess ) holdings = 7;
10797 if( v == VariantGrand ) width = 10, height = 10, holdings = 7;
10798 if( v == VariantChuChess) width = 10, height = 10;
10799 if( v == VariantChu ) width = 12, height = 12;
10800 return boardWidth >= 0 && boardWidth != width || // -1 is default,
10801 boardHeight >= 0 && boardHeight != height || // and thus by definition OK
10802 holdingsSize >= 0 && holdingsSize != holdings;
10805 char variantError[MSG_SIZ];
10808 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10809 { // returns error message (recognizable by upper-case) if engine does not support the variant
10810 char *p, *variant = VariantName(v);
10811 static char b[MSG_SIZ];
10812 if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10813 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10814 holdingsSize, variant); // cook up sized variant name
10815 /* [HGM] varsize: try first if this deviant size variant is specifically known */
10816 if(StrStr(list, b) == NULL) {
10817 // specific sized variant not known, check if general sizing allowed
10818 if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10819 snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10820 boardWidth, boardHeight, holdingsSize, engine);
10823 /* [HGM] here we really should compare with the maximum supported board size */
10825 } else snprintf(b, MSG_SIZ,"%s", variant);
10826 if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10827 p = StrStr(list, b);
10828 while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10830 // occurs not at all in list, or only as sub-string
10831 snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10832 if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10833 int l = strlen(variantError);
10835 while(p != list && p[-1] != ',') p--;
10836 q = strchr(p, ',');
10837 if(q) *q = NULLCHAR;
10838 snprintf(variantError + l, MSG_SIZ - l, _(", but %s is"), p);
10847 InitChessProgram (ChessProgramState *cps, int setup)
10848 /* setup needed to setup FRC opening position */
10850 char buf[MSG_SIZ], *b;
10851 if (appData.noChessProgram) return;
10852 hintRequested = FALSE;
10853 bookRequested = FALSE;
10855 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10856 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10857 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10858 if(cps->memSize) { /* [HGM] memory */
10859 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10860 SendToProgram(buf, cps);
10862 SendEgtPath(cps); /* [HGM] EGT */
10863 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10864 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10865 SendToProgram(buf, cps);
10868 setboardSpoiledMachineBlack = FALSE;
10869 SendToProgram(cps->initString, cps);
10870 if (gameInfo.variant != VariantNormal &&
10871 gameInfo.variant != VariantLoadable
10872 /* [HGM] also send variant if board size non-standard */
10873 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10875 b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10876 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10880 char c, *q = cps->variants, *p = strchr(q, ',');
10881 if(p) *p = NULLCHAR;
10882 v = StringToVariant(q);
10883 DisplayError(variantError, 0);
10884 if(v != VariantUnknown && cps == &first) {
10886 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10887 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10888 ASSIGN(appData.variant, q);
10889 Reset(TRUE, FALSE);
10895 snprintf(buf, MSG_SIZ, "variant %s\n", b);
10896 SendToProgram(buf, cps);
10898 currentlyInitializedVariant = gameInfo.variant;
10900 /* [HGM] send opening position in FRC to first engine */
10902 SendToProgram("force\n", cps);
10904 /* engine is now in force mode! Set flag to wake it up after first move. */
10905 setboardSpoiledMachineBlack = 1;
10908 if (cps->sendICS) {
10909 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10910 SendToProgram(buf, cps);
10912 cps->maybeThinking = FALSE;
10913 cps->offeredDraw = 0;
10914 if (!appData.icsActive) {
10915 SendTimeControl(cps, movesPerSession, timeControl,
10916 timeIncrement, appData.searchDepth,
10919 if (appData.showThinking
10920 // [HGM] thinking: four options require thinking output to be sent
10921 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10923 SendToProgram("post\n", cps);
10925 SendToProgram("hard\n", cps);
10926 if (!appData.ponderNextMove) {
10927 /* Warning: "easy" is a toggle in GNU Chess, so don't send
10928 it without being sure what state we are in first. "hard"
10929 is not a toggle, so that one is OK.
10931 SendToProgram("easy\n", cps);
10933 if (cps->usePing) {
10934 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10935 SendToProgram(buf, cps);
10937 cps->initDone = TRUE;
10938 ClearEngineOutputPane(cps == &second);
10943 ResendOptions (ChessProgramState *cps, int toEngine)
10944 { // send the stored value of the options
10946 static char buf2[MSG_SIZ*10];
10947 char buf[MSG_SIZ], *p = buf2;
10948 Option *opt = cps->option;
10950 for(i=0; i<cps->nrOptions; i++, opt++) {
10952 switch(opt->type) {
10956 if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
10957 snprintf(buf, MSG_SIZ, "%s=%d", opt->name, opt->value);
10960 if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
10961 snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->choice[opt->value]);
10964 if(strcmp(opt->textValue, opt->name + MSG_SIZ - 100))
10965 snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->textValue);
10973 snprintf(buf2, MSG_SIZ, "option %s\n", buf);
10974 SendToProgram(buf2, cps);
10976 if(p != buf2) *p++ = ',';
10977 strncpy(p, buf, 10*MSG_SIZ-1 - (p - buf2));
10986 StartChessProgram (ChessProgramState *cps)
10991 if (appData.noChessProgram) return;
10992 cps->initDone = FALSE;
10994 if (strcmp(cps->host, "localhost") == 0) {
10995 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10996 } else if (*appData.remoteShell == NULLCHAR) {
10997 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10999 if (*appData.remoteUser == NULLCHAR) {
11000 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
11003 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
11004 cps->host, appData.remoteUser, cps->program);
11006 err = StartChildProcess(buf, "", &cps->pr);
11010 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
11011 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
11012 if(cps != &first) return;
11013 appData.noChessProgram = TRUE;
11016 // DisplayFatalError(buf, err, 1);
11017 // cps->pr = NoProc;
11018 // cps->isr = NULL;
11022 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
11023 if (cps->protocolVersion > 1) {
11024 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
11025 if(!cps->reload) { // do not clear options when reloading because of -xreuse
11026 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
11027 cps->comboCnt = 0; // and values of combo boxes
11029 SendToProgram(buf, cps);
11030 if(cps->reload) ResendOptions(cps, TRUE);
11032 SendToProgram("xboard\n", cps);
11037 TwoMachinesEventIfReady P((void))
11039 static int curMess = 0;
11040 if (first.lastPing != first.lastPong) {
11041 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
11042 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11045 if (second.lastPing != second.lastPong) {
11046 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
11047 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11050 DisplayMessage("", ""); curMess = 0;
11051 TwoMachinesEvent();
11055 MakeName (char *template)
11059 static char buf[MSG_SIZ];
11063 clock = time((time_t *)NULL);
11064 tm = localtime(&clock);
11066 while(*p++ = *template++) if(p[-1] == '%') {
11067 switch(*template++) {
11068 case 0: *p = 0; return buf;
11069 case 'Y': i = tm->tm_year+1900; break;
11070 case 'y': i = tm->tm_year-100; break;
11071 case 'M': i = tm->tm_mon+1; break;
11072 case 'd': i = tm->tm_mday; break;
11073 case 'h': i = tm->tm_hour; break;
11074 case 'm': i = tm->tm_min; break;
11075 case 's': i = tm->tm_sec; break;
11078 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11084 CountPlayers (char *p)
11087 while(p = strchr(p, '\n')) p++, n++; // count participants
11092 WriteTourneyFile (char *results, FILE *f)
11093 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11094 if(f == NULL) f = fopen(appData.tourneyFile, "w");
11095 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11096 // create a file with tournament description
11097 fprintf(f, "-participants {%s}\n", appData.participants);
11098 fprintf(f, "-seedBase %d\n", appData.seedBase);
11099 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11100 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11101 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11102 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11103 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11104 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11105 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11106 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11107 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11108 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11109 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11110 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11111 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11112 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11113 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11114 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11115 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11116 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11117 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11118 fprintf(f, "-smpCores %d\n", appData.smpCores);
11120 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11122 fprintf(f, "-mps %d\n", appData.movesPerSession);
11123 fprintf(f, "-tc %s\n", appData.timeControl);
11124 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11126 fprintf(f, "-results \"%s\"\n", results);
11131 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11134 Substitute (char *participants, int expunge)
11136 int i, changed, changes=0, nPlayers=0;
11137 char *p, *q, *r, buf[MSG_SIZ];
11138 if(participants == NULL) return;
11139 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11140 r = p = participants; q = appData.participants;
11141 while(*p && *p == *q) {
11142 if(*p == '\n') r = p+1, nPlayers++;
11145 if(*p) { // difference
11146 while(*p && *p++ != '\n')
11148 while(*q && *q++ != '\n')
11150 changed = nPlayers;
11151 changes = 1 + (strcmp(p, q) != 0);
11153 if(changes == 1) { // a single engine mnemonic was changed
11154 q = r; while(*q) nPlayers += (*q++ == '\n');
11155 p = buf; while(*r && (*p = *r++) != '\n') p++;
11157 NamesToList(firstChessProgramNames, command, mnemonic, "all");
11158 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11159 if(mnemonic[i]) { // The substitute is valid
11161 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11162 flock(fileno(f), LOCK_EX);
11163 ParseArgsFromFile(f);
11164 fseek(f, 0, SEEK_SET);
11165 FREE(appData.participants); appData.participants = participants;
11166 if(expunge) { // erase results of replaced engine
11167 int len = strlen(appData.results), w, b, dummy;
11168 for(i=0; i<len; i++) {
11169 Pairing(i, nPlayers, &w, &b, &dummy);
11170 if((w == changed || b == changed) && appData.results[i] == '*') {
11171 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11176 for(i=0; i<len; i++) {
11177 Pairing(i, nPlayers, &w, &b, &dummy);
11178 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11181 WriteTourneyFile(appData.results, f);
11182 fclose(f); // release lock
11185 } else DisplayError(_("No engine with the name you gave is installed"), 0);
11187 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11188 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
11189 free(participants);
11194 CheckPlayers (char *participants)
11197 char buf[MSG_SIZ], *p;
11198 NamesToList(firstChessProgramNames, command, mnemonic, "all");
11199 while(p = strchr(participants, '\n')) {
11201 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11203 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11205 DisplayError(buf, 0);
11209 participants = p + 1;
11215 CreateTourney (char *name)
11218 if(matchMode && strcmp(name, appData.tourneyFile)) {
11219 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11221 if(name[0] == NULLCHAR) {
11222 if(appData.participants[0])
11223 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11226 f = fopen(name, "r");
11227 if(f) { // file exists
11228 ASSIGN(appData.tourneyFile, name);
11229 ParseArgsFromFile(f); // parse it
11231 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11232 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11233 DisplayError(_("Not enough participants"), 0);
11236 if(CheckPlayers(appData.participants)) return 0;
11237 ASSIGN(appData.tourneyFile, name);
11238 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11239 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11242 appData.noChessProgram = FALSE;
11243 appData.clockMode = TRUE;
11249 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11251 char buf[2*MSG_SIZ], *p, *q;
11252 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11253 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11254 skip = !all && group[0]; // if group requested, we start in skip mode
11255 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11256 p = names; q = buf; header = 0;
11257 while(*p && *p != '\n') *q++ = *p++;
11259 if(*p == '\n') p++;
11260 if(buf[0] == '#') {
11261 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11262 depth++; // we must be entering a new group
11263 if(all) continue; // suppress printing group headers when complete list requested
11265 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11267 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11268 if(engineList[i]) free(engineList[i]);
11269 engineList[i] = strdup(buf);
11270 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11271 if(engineMnemonic[i]) free(engineMnemonic[i]);
11272 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11274 sscanf(q + 8, "%s", buf + strlen(buf));
11277 engineMnemonic[i] = strdup(buf);
11280 engineList[i] = engineMnemonic[i] = NULL;
11285 SaveEngineSettings (int n)
11287 int len; char *p, *q, *s, buf[MSG_SIZ], *optionSettings;
11288 if(!currentEngine[n] || !currentEngine[n][0]) { DisplayMessage("saving failed: engine not from list", ""); return; } // no engine from list is loaded
11289 p = strstr(firstChessProgramNames, currentEngine[n]);
11290 if(!p) { DisplayMessage("saving failed: engine not found in list", ""); return; } // sanity check; engine could be deleted from list after loading
11291 optionSettings = ResendOptions(n ? &second : &first, FALSE);
11292 len = strlen(currentEngine[n]);
11293 q = p + len; *p = 0; // cut list into head and tail piece
11294 s = strstr(currentEngine[n], "firstOptions");
11295 if(s && (s[-1] == '-' || s[-1] == '/') && (s[12] == ' ' || s[12] == '=') && (s[13] == '"' || s[13] == '\'')) {
11297 while(*r && *r != s[13]) r++;
11298 s[14] = 0; // cut currentEngine into head and tail part, removing old settings
11299 snprintf(buf, MSG_SIZ, "%s%s%s", currentEngine[n], optionSettings, *r ? r : "\""); // synthesize new engine line
11300 } else if(*optionSettings) {
11301 snprintf(buf, MSG_SIZ, "%s -firstOptions \"%s\"", currentEngine[n], optionSettings);
11303 ASSIGN(currentEngine[n], buf); // updated engine line
11304 len = p - firstChessProgramNames + strlen(q) + strlen(currentEngine[n]) + 1;
11306 snprintf(s, len, "%s%s%s", firstChessProgramNames, currentEngine[n], q);
11307 FREE(firstChessProgramNames); firstChessProgramNames = s; // new list
11310 // following implemented as macro to avoid type limitations
11311 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11314 SwapEngines (int n)
11315 { // swap settings for first engine and other engine (so far only some selected options)
11320 SWAP(chessProgram, p)
11322 SWAP(hasOwnBookUCI, h)
11323 SWAP(protocolVersion, h)
11325 SWAP(scoreIsAbsolute, h)
11330 SWAP(engOptions, p)
11331 SWAP(engInitString, p)
11332 SWAP(computerString, p)
11334 SWAP(fenOverride, p)
11336 SWAP(accumulateTC, h)
11343 GetEngineLine (char *s, int n)
11347 extern char *icsNames;
11348 if(!s || !*s) return 0;
11349 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11350 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11351 if(!mnemonic[i]) return 0;
11352 if(n == 11) return 1; // just testing if there was a match
11353 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11354 if(n == 1) SwapEngines(n);
11355 ParseArgsFromString(buf);
11356 if(n == 1) SwapEngines(n);
11357 if(n < 2) { ASSIGN(currentEngine[n], command[i]); }
11358 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11359 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11360 ParseArgsFromString(buf);
11366 SetPlayer (int player, char *p)
11367 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
11369 char buf[MSG_SIZ], *engineName;
11370 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11371 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11372 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11374 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11375 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11376 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11377 ParseArgsFromString(buf);
11378 } else { // no engine with this nickname is installed!
11379 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11380 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11381 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11383 DisplayError(buf, 0);
11390 char *recentEngines;
11393 RecentEngineEvent (int nr)
11396 // SwapEngines(1); // bump first to second
11397 // ReplaceEngine(&second, 1); // and load it there
11398 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11399 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11400 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11401 ReplaceEngine(&first, 0);
11402 FloatToFront(&appData.recentEngineList, command[n]);
11407 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11408 { // determine players from game number
11409 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11411 if(appData.tourneyType == 0) {
11412 roundsPerCycle = (nPlayers - 1) | 1;
11413 pairingsPerRound = nPlayers / 2;
11414 } else if(appData.tourneyType > 0) {
11415 roundsPerCycle = nPlayers - appData.tourneyType;
11416 pairingsPerRound = appData.tourneyType;
11418 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11419 gamesPerCycle = gamesPerRound * roundsPerCycle;
11420 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11421 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11422 curRound = nr / gamesPerRound; nr %= gamesPerRound;
11423 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11424 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11425 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11427 if(appData.cycleSync) *syncInterval = gamesPerCycle;
11428 if(appData.roundSync) *syncInterval = gamesPerRound;
11430 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11432 if(appData.tourneyType == 0) {
11433 if(curPairing == (nPlayers-1)/2 ) {
11434 *whitePlayer = curRound;
11435 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11437 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11438 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11439 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11440 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11442 } else if(appData.tourneyType > 1) {
11443 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11444 *whitePlayer = curRound + appData.tourneyType;
11445 } else if(appData.tourneyType > 0) {
11446 *whitePlayer = curPairing;
11447 *blackPlayer = curRound + appData.tourneyType;
11450 // take care of white/black alternation per round.
11451 // For cycles and games this is already taken care of by default, derived from matchGame!
11452 return curRound & 1;
11456 NextTourneyGame (int nr, int *swapColors)
11457 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11459 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11461 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11462 tf = fopen(appData.tourneyFile, "r");
11463 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11464 ParseArgsFromFile(tf); fclose(tf);
11465 InitTimeControls(); // TC might be altered from tourney file
11467 nPlayers = CountPlayers(appData.participants); // count participants
11468 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11469 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11472 p = q = appData.results;
11473 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11474 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11475 DisplayMessage(_("Waiting for other game(s)"),"");
11476 waitingForGame = TRUE;
11477 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11480 waitingForGame = FALSE;
11483 if(appData.tourneyType < 0) {
11484 if(nr>=0 && !pairingReceived) {
11486 if(pairing.pr == NoProc) {
11487 if(!appData.pairingEngine[0]) {
11488 DisplayFatalError(_("No pairing engine specified"), 0, 1);
11491 StartChessProgram(&pairing); // starts the pairing engine
11493 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11494 SendToProgram(buf, &pairing);
11495 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11496 SendToProgram(buf, &pairing);
11497 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11499 pairingReceived = 0; // ... so we continue here
11501 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11502 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11503 matchGame = 1; roundNr = nr / syncInterval + 1;
11506 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11508 // redefine engines, engine dir, etc.
11509 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11510 if(first.pr == NoProc) {
11511 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11512 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
11514 if(second.pr == NoProc) {
11516 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11517 SwapEngines(1); // and make that valid for second engine by swapping
11518 InitEngine(&second, 1);
11520 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
11521 UpdateLogos(FALSE); // leave display to ModeHiglight()
11527 { // performs game initialization that does not invoke engines, and then tries to start the game
11528 int res, firstWhite, swapColors = 0;
11529 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11530 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
11532 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11533 if(strcmp(buf, currentDebugFile)) { // name has changed
11534 FILE *f = fopen(buf, "w");
11535 if(f) { // if opening the new file failed, just keep using the old one
11536 ASSIGN(currentDebugFile, buf);
11540 if(appData.serverFileName) {
11541 if(serverFP) fclose(serverFP);
11542 serverFP = fopen(appData.serverFileName, "w");
11543 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11544 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11548 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11549 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11550 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
11551 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11552 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11553 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11554 Reset(FALSE, first.pr != NoProc);
11555 res = LoadGameOrPosition(matchGame); // setup game
11556 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11557 if(!res) return; // abort when bad game/pos file
11558 if(appData.epd) {// in EPD mode we make sure first engine is to move
11559 firstWhite = !(forwardMostMove & 1);
11560 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
11561 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11563 TwoMachinesEvent();
11567 UserAdjudicationEvent (int result)
11569 ChessMove gameResult = GameIsDrawn;
11572 gameResult = WhiteWins;
11574 else if( result < 0 ) {
11575 gameResult = BlackWins;
11578 if( gameMode == TwoMachinesPlay ) {
11579 GameEnds( gameResult, "User adjudication", GE_XBOARD );
11584 // [HGM] save: calculate checksum of game to make games easily identifiable
11586 StringCheckSum (char *s)
11589 if(s==NULL) return 0;
11590 while(*s) i = i*259 + *s++;
11598 for(i=backwardMostMove; i<forwardMostMove; i++) {
11599 sum += pvInfoList[i].depth;
11600 sum += StringCheckSum(parseList[i]);
11601 sum += StringCheckSum(commentList[i]);
11604 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11605 return sum + StringCheckSum(commentList[i]);
11606 } // end of save patch
11609 GameEnds (ChessMove result, char *resultDetails, int whosays)
11611 GameMode nextGameMode;
11613 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11615 if(endingGame) return; /* [HGM] crash: forbid recursion */
11617 if(twoBoards) { // [HGM] dual: switch back to one board
11618 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11619 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11621 if (appData.debugMode) {
11622 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11623 result, resultDetails ? resultDetails : "(null)", whosays);
11626 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11628 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11630 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11631 /* If we are playing on ICS, the server decides when the
11632 game is over, but the engine can offer to draw, claim
11636 if (appData.zippyPlay && first.initDone) {
11637 if (result == GameIsDrawn) {
11638 /* In case draw still needs to be claimed */
11639 SendToICS(ics_prefix);
11640 SendToICS("draw\n");
11641 } else if (StrCaseStr(resultDetails, "resign")) {
11642 SendToICS(ics_prefix);
11643 SendToICS("resign\n");
11647 endingGame = 0; /* [HGM] crash */
11651 /* If we're loading the game from a file, stop */
11652 if (whosays == GE_FILE) {
11653 (void) StopLoadGameTimer();
11657 /* Cancel draw offers */
11658 first.offeredDraw = second.offeredDraw = 0;
11660 /* If this is an ICS game, only ICS can really say it's done;
11661 if not, anyone can. */
11662 isIcsGame = (gameMode == IcsPlayingWhite ||
11663 gameMode == IcsPlayingBlack ||
11664 gameMode == IcsObserving ||
11665 gameMode == IcsExamining);
11667 if (!isIcsGame || whosays == GE_ICS) {
11668 /* OK -- not an ICS game, or ICS said it was done */
11670 if (!isIcsGame && !appData.noChessProgram)
11671 SetUserThinkingEnables();
11673 /* [HGM] if a machine claims the game end we verify this claim */
11674 if(gameMode == TwoMachinesPlay && appData.testClaims) {
11675 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11677 ChessMove trueResult = (ChessMove) -1;
11679 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
11680 first.twoMachinesColor[0] :
11681 second.twoMachinesColor[0] ;
11683 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11684 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11685 /* [HGM] verify: engine mate claims accepted if they were flagged */
11686 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11688 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11689 /* [HGM] verify: engine mate claims accepted if they were flagged */
11690 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11692 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11693 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11696 // now verify win claims, but not in drop games, as we don't understand those yet
11697 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11698 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11699 (result == WhiteWins && claimer == 'w' ||
11700 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
11701 if (appData.debugMode) {
11702 fprintf(debugFP, "result=%d sp=%d move=%d\n",
11703 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11705 if(result != trueResult) {
11706 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11707 result = claimer == 'w' ? BlackWins : WhiteWins;
11708 resultDetails = buf;
11711 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11712 && (forwardMostMove <= backwardMostMove ||
11713 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11714 (claimer=='b')==(forwardMostMove&1))
11716 /* [HGM] verify: draws that were not flagged are false claims */
11717 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11718 result = claimer == 'w' ? BlackWins : WhiteWins;
11719 resultDetails = buf;
11721 /* (Claiming a loss is accepted no questions asked!) */
11722 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11723 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11724 result = GameUnfinished;
11725 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11727 /* [HGM] bare: don't allow bare King to win */
11728 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11729 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11730 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11731 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11732 && result != GameIsDrawn)
11733 { int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11734 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11735 int p = (int)boards[forwardMostMove][i][j] - color;
11736 if(p >= 0 && p <= (int)WhiteKing) k++;
11737 oppoKings += (p + color == WhiteKing + BlackPawn - color);
11739 if (appData.debugMode) {
11740 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11741 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11743 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11744 result = GameIsDrawn;
11745 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11746 resultDetails = buf;
11752 if(serverMoves != NULL && !loadFlag) { char c = '=';
11753 if(result==WhiteWins) c = '+';
11754 if(result==BlackWins) c = '-';
11755 if(resultDetails != NULL)
11756 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11758 if (resultDetails != NULL) {
11759 gameInfo.result = result;
11760 gameInfo.resultDetails = StrSave(resultDetails);
11762 /* display last move only if game was not loaded from file */
11763 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11764 DisplayMove(currentMove - 1);
11766 if (forwardMostMove != 0) {
11767 if (gameMode != PlayFromGameFile && gameMode != EditGame
11768 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11770 if (*appData.saveGameFile != NULLCHAR) {
11771 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11772 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11774 SaveGameToFile(appData.saveGameFile, TRUE);
11775 } else if (appData.autoSaveGames) {
11776 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11778 if (*appData.savePositionFile != NULLCHAR) {
11779 SavePositionToFile(appData.savePositionFile);
11781 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11785 /* Tell program how game ended in case it is learning */
11786 /* [HGM] Moved this to after saving the PGN, just in case */
11787 /* engine died and we got here through time loss. In that */
11788 /* case we will get a fatal error writing the pipe, which */
11789 /* would otherwise lose us the PGN. */
11790 /* [HGM] crash: not needed anymore, but doesn't hurt; */
11791 /* output during GameEnds should never be fatal anymore */
11792 if (gameMode == MachinePlaysWhite ||
11793 gameMode == MachinePlaysBlack ||
11794 gameMode == TwoMachinesPlay ||
11795 gameMode == IcsPlayingWhite ||
11796 gameMode == IcsPlayingBlack ||
11797 gameMode == BeginningOfGame) {
11799 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11801 if (first.pr != NoProc) {
11802 SendToProgram(buf, &first);
11804 if (second.pr != NoProc &&
11805 gameMode == TwoMachinesPlay) {
11806 SendToProgram(buf, &second);
11811 if (appData.icsActive) {
11812 if (appData.quietPlay &&
11813 (gameMode == IcsPlayingWhite ||
11814 gameMode == IcsPlayingBlack)) {
11815 SendToICS(ics_prefix);
11816 SendToICS("set shout 1\n");
11818 nextGameMode = IcsIdle;
11819 ics_user_moved = FALSE;
11820 /* clean up premove. It's ugly when the game has ended and the
11821 * premove highlights are still on the board.
11824 gotPremove = FALSE;
11825 ClearPremoveHighlights();
11826 DrawPosition(FALSE, boards[currentMove]);
11828 if (whosays == GE_ICS) {
11831 if (gameMode == IcsPlayingWhite)
11833 else if(gameMode == IcsPlayingBlack)
11834 PlayIcsLossSound();
11837 if (gameMode == IcsPlayingBlack)
11839 else if(gameMode == IcsPlayingWhite)
11840 PlayIcsLossSound();
11843 PlayIcsDrawSound();
11846 PlayIcsUnfinishedSound();
11849 if(appData.quitNext) { ExitEvent(0); return; }
11850 } else if (gameMode == EditGame ||
11851 gameMode == PlayFromGameFile ||
11852 gameMode == AnalyzeMode ||
11853 gameMode == AnalyzeFile) {
11854 nextGameMode = gameMode;
11856 nextGameMode = EndOfGame;
11861 nextGameMode = gameMode;
11864 if (appData.noChessProgram) {
11865 gameMode = nextGameMode;
11867 endingGame = 0; /* [HGM] crash */
11872 /* Put first chess program into idle state */
11873 if (first.pr != NoProc &&
11874 (gameMode == MachinePlaysWhite ||
11875 gameMode == MachinePlaysBlack ||
11876 gameMode == TwoMachinesPlay ||
11877 gameMode == IcsPlayingWhite ||
11878 gameMode == IcsPlayingBlack ||
11879 gameMode == BeginningOfGame)) {
11880 SendToProgram("force\n", &first);
11881 if (first.usePing) {
11883 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11884 SendToProgram(buf, &first);
11887 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11888 /* Kill off first chess program */
11889 if (first.isr != NULL)
11890 RemoveInputSource(first.isr);
11893 if (first.pr != NoProc) {
11895 DoSleep( appData.delayBeforeQuit );
11896 SendToProgram("quit\n", &first);
11897 DestroyChildProcess(first.pr, 4 + first.useSigterm);
11898 first.reload = TRUE;
11902 if (second.reuse) {
11903 /* Put second chess program into idle state */
11904 if (second.pr != NoProc &&
11905 gameMode == TwoMachinesPlay) {
11906 SendToProgram("force\n", &second);
11907 if (second.usePing) {
11909 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11910 SendToProgram(buf, &second);
11913 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11914 /* Kill off second chess program */
11915 if (second.isr != NULL)
11916 RemoveInputSource(second.isr);
11919 if (second.pr != NoProc) {
11920 DoSleep( appData.delayBeforeQuit );
11921 SendToProgram("quit\n", &second);
11922 DestroyChildProcess(second.pr, 4 + second.useSigterm);
11923 second.reload = TRUE;
11925 second.pr = NoProc;
11928 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11929 char resChar = '=';
11933 if (first.twoMachinesColor[0] == 'w') {
11936 second.matchWins++;
11941 if (first.twoMachinesColor[0] == 'b') {
11944 second.matchWins++;
11947 case GameUnfinished:
11953 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11954 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11955 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11956 ReserveGame(nextGame, resChar); // sets nextGame
11957 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11958 else ranking = strdup("busy"); //suppress popup when aborted but not finished
11959 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11961 if (nextGame <= appData.matchGames && !abortMatch) {
11962 gameMode = nextGameMode;
11963 matchGame = nextGame; // this will be overruled in tourney mode!
11964 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11965 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11966 endingGame = 0; /* [HGM] crash */
11969 gameMode = nextGameMode;
11971 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
11972 OutputKibitz(2, buf);
11973 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
11974 OutputKibitz(2, buf);
11975 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
11976 if(second.matchWins) OutputKibitz(2, buf);
11977 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
11978 OutputKibitz(2, buf);
11980 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11981 first.tidy, second.tidy,
11982 first.matchWins, second.matchWins,
11983 appData.matchGames - (first.matchWins + second.matchWins));
11984 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11985 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11986 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11987 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11988 first.twoMachinesColor = "black\n";
11989 second.twoMachinesColor = "white\n";
11991 first.twoMachinesColor = "white\n";
11992 second.twoMachinesColor = "black\n";
11996 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11997 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11999 gameMode = nextGameMode;
12001 endingGame = 0; /* [HGM] crash */
12002 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
12003 if(matchMode == TRUE) { // match through command line: exit with or without popup
12005 ToNrEvent(forwardMostMove);
12006 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
12008 } else DisplayFatalError(buf, 0, 0);
12009 } else { // match through menu; just stop, with or without popup
12010 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
12013 if(strcmp(ranking, "busy")) DisplayNote(ranking);
12014 } else DisplayNote(buf);
12016 if(ranking) free(ranking);
12020 /* Assumes program was just initialized (initString sent).
12021 Leaves program in force mode. */
12023 FeedMovesToProgram (ChessProgramState *cps, int upto)
12027 if (appData.debugMode)
12028 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
12029 startedFromSetupPosition ? "position and " : "",
12030 backwardMostMove, upto, cps->which);
12031 if(currentlyInitializedVariant != gameInfo.variant) {
12033 // [HGM] variantswitch: make engine aware of new variant
12034 if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
12035 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
12036 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
12037 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
12038 SendToProgram(buf, cps);
12039 currentlyInitializedVariant = gameInfo.variant;
12041 SendToProgram("force\n", cps);
12042 if (startedFromSetupPosition) {
12043 SendBoard(cps, backwardMostMove);
12044 if (appData.debugMode) {
12045 fprintf(debugFP, "feedMoves\n");
12048 for (i = backwardMostMove; i < upto; i++) {
12049 SendMoveToProgram(i, cps);
12055 ResurrectChessProgram ()
12057 /* The chess program may have exited.
12058 If so, restart it and feed it all the moves made so far. */
12059 static int doInit = 0;
12061 if (appData.noChessProgram) return 1;
12063 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
12064 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
12065 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
12066 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
12068 if (first.pr != NoProc) return 1;
12069 StartChessProgram(&first);
12071 InitChessProgram(&first, FALSE);
12072 FeedMovesToProgram(&first, currentMove);
12074 if (!first.sendTime) {
12075 /* can't tell gnuchess what its clock should read,
12076 so we bow to its notion. */
12078 timeRemaining[0][currentMove] = whiteTimeRemaining;
12079 timeRemaining[1][currentMove] = blackTimeRemaining;
12082 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
12083 appData.icsEngineAnalyze) && first.analysisSupport) {
12084 SendToProgram("analyze\n", &first);
12085 first.analyzing = TRUE;
12091 * Button procedures
12094 Reset (int redraw, int init)
12098 if (appData.debugMode) {
12099 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12100 redraw, init, gameMode);
12102 pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12103 deadRanks = 0; // assume entire board is used
12104 for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12105 CleanupTail(); // [HGM] vari: delete any stored variations
12106 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12107 pausing = pauseExamInvalid = FALSE;
12108 startedFromSetupPosition = blackPlaysFirst = FALSE;
12110 whiteFlag = blackFlag = FALSE;
12111 userOfferedDraw = FALSE;
12112 hintRequested = bookRequested = FALSE;
12113 first.maybeThinking = FALSE;
12114 second.maybeThinking = FALSE;
12115 first.bookSuspend = FALSE; // [HGM] book
12116 second.bookSuspend = FALSE;
12117 thinkOutput[0] = NULLCHAR;
12118 lastHint[0] = NULLCHAR;
12119 ClearGameInfo(&gameInfo);
12120 gameInfo.variant = StringToVariant(appData.variant);
12121 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12122 gameInfo.variant = VariantUnknown;
12123 strncpy(engineVariant, appData.variant, MSG_SIZ);
12125 ics_user_moved = ics_clock_paused = FALSE;
12126 ics_getting_history = H_FALSE;
12128 white_holding[0] = black_holding[0] = NULLCHAR;
12129 ClearProgramStats();
12130 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12134 flipView = appData.flipView;
12135 ClearPremoveHighlights();
12136 gotPremove = FALSE;
12137 alarmSounded = FALSE;
12138 killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12140 GameEnds(EndOfFile, NULL, GE_PLAYER);
12141 if(appData.serverMovesName != NULL) {
12142 /* [HGM] prepare to make moves file for broadcasting */
12143 clock_t t = clock();
12144 if(serverMoves != NULL) fclose(serverMoves);
12145 serverMoves = fopen(appData.serverMovesName, "r");
12146 if(serverMoves != NULL) {
12147 fclose(serverMoves);
12148 /* delay 15 sec before overwriting, so all clients can see end */
12149 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12151 serverMoves = fopen(appData.serverMovesName, "w");
12155 gameMode = BeginningOfGame;
12157 if(appData.icsActive) gameInfo.variant = VariantNormal;
12158 currentMove = forwardMostMove = backwardMostMove = 0;
12159 MarkTargetSquares(1);
12160 InitPosition(redraw);
12161 for (i = 0; i < MAX_MOVES; i++) {
12162 if (commentList[i] != NULL) {
12163 free(commentList[i]);
12164 commentList[i] = NULL;
12168 timeRemaining[0][0] = whiteTimeRemaining;
12169 timeRemaining[1][0] = blackTimeRemaining;
12171 if (first.pr == NoProc) {
12172 StartChessProgram(&first);
12175 InitChessProgram(&first, startedFromSetupPosition);
12178 DisplayMessage("", "");
12179 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12180 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12181 ClearMap(); // [HGM] exclude: invalidate map
12185 AutoPlayGameLoop ()
12188 if (!AutoPlayOneMove())
12190 if (matchMode || appData.timeDelay == 0)
12192 if (appData.timeDelay < 0)
12194 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12202 ReloadGame(1); // next game
12208 int fromX, fromY, toX, toY;
12210 if (appData.debugMode) {
12211 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12214 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12217 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12218 pvInfoList[currentMove].depth = programStats.depth;
12219 pvInfoList[currentMove].score = programStats.score;
12220 pvInfoList[currentMove].time = 0;
12221 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12222 else { // append analysis of final position as comment
12224 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12225 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12227 programStats.depth = 0;
12230 if (currentMove >= forwardMostMove) {
12231 if(gameMode == AnalyzeFile) {
12232 if(appData.loadGameIndex == -1) {
12233 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12234 ScheduleDelayedEvent(AnalyzeNextGame, 10);
12236 ExitAnalyzeMode(); SendToProgram("force\n", &first);
12239 // gameMode = EndOfGame;
12240 // ModeHighlight();
12242 /* [AS] Clear current move marker at the end of a game */
12243 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12248 toX = moveList[currentMove][2] - AAA;
12249 toY = moveList[currentMove][3] - ONE;
12251 if (moveList[currentMove][1] == '@') {
12252 if (appData.highlightLastMove) {
12253 SetHighlights(-1, -1, toX, toY);
12256 fromX = moveList[currentMove][0] - AAA;
12257 fromY = moveList[currentMove][1] - ONE;
12259 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12261 if(moveList[currentMove][4] == ';') { // multi-leg
12262 killX = moveList[currentMove][5] - AAA;
12263 killY = moveList[currentMove][6] - ONE;
12265 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12266 killX = killY = -1;
12268 if (appData.highlightLastMove) {
12269 SetHighlights(fromX, fromY, toX, toY);
12272 DisplayMove(currentMove);
12273 SendMoveToProgram(currentMove++, &first);
12274 DisplayBothClocks();
12275 DrawPosition(FALSE, boards[currentMove]);
12276 // [HGM] PV info: always display, routine tests if empty
12277 DisplayComment(currentMove - 1, commentList[currentMove]);
12283 LoadGameOneMove (ChessMove readAhead)
12285 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12286 char promoChar = NULLCHAR;
12287 ChessMove moveType;
12288 char move[MSG_SIZ];
12291 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12292 gameMode != AnalyzeMode && gameMode != Training) {
12297 yyboardindex = forwardMostMove;
12298 if (readAhead != EndOfFile) {
12299 moveType = readAhead;
12301 if (gameFileFP == NULL)
12303 moveType = (ChessMove) Myylex();
12307 switch (moveType) {
12309 if (appData.debugMode)
12310 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12313 /* append the comment but don't display it */
12314 AppendComment(currentMove, p, FALSE);
12317 case WhiteCapturesEnPassant:
12318 case BlackCapturesEnPassant:
12319 case WhitePromotion:
12320 case BlackPromotion:
12321 case WhiteNonPromotion:
12322 case BlackNonPromotion:
12325 case WhiteKingSideCastle:
12326 case WhiteQueenSideCastle:
12327 case BlackKingSideCastle:
12328 case BlackQueenSideCastle:
12329 case WhiteKingSideCastleWild:
12330 case WhiteQueenSideCastleWild:
12331 case BlackKingSideCastleWild:
12332 case BlackQueenSideCastleWild:
12334 case WhiteHSideCastleFR:
12335 case WhiteASideCastleFR:
12336 case BlackHSideCastleFR:
12337 case BlackASideCastleFR:
12339 if (appData.debugMode)
12340 fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12341 fromX = currentMoveString[0] - AAA;
12342 fromY = currentMoveString[1] - ONE;
12343 toX = currentMoveString[2] - AAA;
12344 toY = currentMoveString[3] - ONE;
12345 promoChar = currentMoveString[4];
12346 if(promoChar == ';') promoChar = currentMoveString[7];
12351 if (appData.debugMode)
12352 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12353 fromX = moveType == WhiteDrop ?
12354 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12355 (int) CharToPiece(ToLower(currentMoveString[0]));
12357 toX = currentMoveString[2] - AAA;
12358 toY = currentMoveString[3] - ONE;
12364 case GameUnfinished:
12365 if (appData.debugMode)
12366 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12367 p = strchr(yy_text, '{');
12368 if (p == NULL) p = strchr(yy_text, '(');
12371 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12373 q = strchr(p, *p == '{' ? '}' : ')');
12374 if (q != NULL) *q = NULLCHAR;
12377 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12378 GameEnds(moveType, p, GE_FILE);
12380 if (cmailMsgLoaded) {
12382 flipView = WhiteOnMove(currentMove);
12383 if (moveType == GameUnfinished) flipView = !flipView;
12384 if (appData.debugMode)
12385 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12390 if (appData.debugMode)
12391 fprintf(debugFP, "Parser hit end of file\n");
12392 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12398 if (WhiteOnMove(currentMove)) {
12399 GameEnds(BlackWins, "Black mates", GE_FILE);
12401 GameEnds(WhiteWins, "White mates", GE_FILE);
12405 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12411 case MoveNumberOne:
12412 if (lastLoadGameStart == GNUChessGame) {
12413 /* GNUChessGames have numbers, but they aren't move numbers */
12414 if (appData.debugMode)
12415 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12416 yy_text, (int) moveType);
12417 return LoadGameOneMove(EndOfFile); /* tail recursion */
12419 /* else fall thru */
12424 /* Reached start of next game in file */
12425 if (appData.debugMode)
12426 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12427 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12433 if (WhiteOnMove(currentMove)) {
12434 GameEnds(BlackWins, "Black mates", GE_FILE);
12436 GameEnds(WhiteWins, "White mates", GE_FILE);
12440 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12446 case PositionDiagram: /* should not happen; ignore */
12447 case ElapsedTime: /* ignore */
12448 case NAG: /* ignore */
12449 if (appData.debugMode)
12450 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12451 yy_text, (int) moveType);
12452 return LoadGameOneMove(EndOfFile); /* tail recursion */
12455 if (appData.testLegality) {
12456 if (appData.debugMode)
12457 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12458 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12459 (forwardMostMove / 2) + 1,
12460 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12461 DisplayError(move, 0);
12464 if (appData.debugMode)
12465 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12466 yy_text, currentMoveString);
12467 if(currentMoveString[1] == '@') {
12468 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12471 fromX = currentMoveString[0] - AAA;
12472 fromY = currentMoveString[1] - ONE;
12474 toX = currentMoveString[2] - AAA;
12475 toY = currentMoveString[3] - ONE;
12476 promoChar = currentMoveString[4];
12480 case AmbiguousMove:
12481 if (appData.debugMode)
12482 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12483 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12484 (forwardMostMove / 2) + 1,
12485 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12486 DisplayError(move, 0);
12491 case ImpossibleMove:
12492 if (appData.debugMode)
12493 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12494 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12495 (forwardMostMove / 2) + 1,
12496 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12497 DisplayError(move, 0);
12503 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12504 DrawPosition(FALSE, boards[currentMove]);
12505 DisplayBothClocks();
12506 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12507 DisplayComment(currentMove - 1, commentList[currentMove]);
12509 (void) StopLoadGameTimer();
12511 cmailOldMove = forwardMostMove;
12514 /* currentMoveString is set as a side-effect of yylex */
12516 thinkOutput[0] = NULLCHAR;
12517 MakeMove(fromX, fromY, toX, toY, promoChar);
12518 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12519 currentMove = forwardMostMove;
12524 /* Load the nth game from the given file */
12526 LoadGameFromFile (char *filename, int n, char *title, int useList)
12531 if (strcmp(filename, "-") == 0) {
12535 f = fopen(filename, "rb");
12537 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12538 DisplayError(buf, errno);
12542 if (fseek(f, 0, 0) == -1) {
12543 /* f is not seekable; probably a pipe */
12546 if (useList && n == 0) {
12547 int error = GameListBuild(f);
12549 DisplayError(_("Cannot build game list"), error);
12550 } else if (!ListEmpty(&gameList) &&
12551 ((ListGame *) gameList.tailPred)->number > 1) {
12552 GameListPopUp(f, title);
12559 return LoadGame(f, n, title, FALSE);
12564 MakeRegisteredMove ()
12566 int fromX, fromY, toX, toY;
12568 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12569 switch (cmailMoveType[lastLoadGameNumber - 1]) {
12572 if (appData.debugMode)
12573 fprintf(debugFP, "Restoring %s for game %d\n",
12574 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12576 thinkOutput[0] = NULLCHAR;
12577 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12578 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12579 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12580 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12581 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12582 promoChar = cmailMove[lastLoadGameNumber - 1][4];
12583 MakeMove(fromX, fromY, toX, toY, promoChar);
12584 ShowMove(fromX, fromY, toX, toY);
12586 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12593 if (WhiteOnMove(currentMove)) {
12594 GameEnds(BlackWins, "Black mates", GE_PLAYER);
12596 GameEnds(WhiteWins, "White mates", GE_PLAYER);
12601 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12608 if (WhiteOnMove(currentMove)) {
12609 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12611 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12616 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12627 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12629 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12633 if (gameNumber > nCmailGames) {
12634 DisplayError(_("No more games in this message"), 0);
12637 if (f == lastLoadGameFP) {
12638 int offset = gameNumber - lastLoadGameNumber;
12640 cmailMsg[0] = NULLCHAR;
12641 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12642 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12643 nCmailMovesRegistered--;
12645 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12646 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12647 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12650 if (! RegisterMove()) return FALSE;
12654 retVal = LoadGame(f, gameNumber, title, useList);
12656 /* Make move registered during previous look at this game, if any */
12657 MakeRegisteredMove();
12659 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12660 commentList[currentMove]
12661 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12662 DisplayComment(currentMove - 1, commentList[currentMove]);
12668 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12670 ReloadGame (int offset)
12672 int gameNumber = lastLoadGameNumber + offset;
12673 if (lastLoadGameFP == NULL) {
12674 DisplayError(_("No game has been loaded yet"), 0);
12677 if (gameNumber <= 0) {
12678 DisplayError(_("Can't back up any further"), 0);
12681 if (cmailMsgLoaded) {
12682 return CmailLoadGame(lastLoadGameFP, gameNumber,
12683 lastLoadGameTitle, lastLoadGameUseList);
12685 return LoadGame(lastLoadGameFP, gameNumber,
12686 lastLoadGameTitle, lastLoadGameUseList);
12690 int keys[EmptySquare+1];
12693 PositionMatches (Board b1, Board b2)
12696 switch(appData.searchMode) {
12697 case 1: return CompareWithRights(b1, b2);
12699 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12700 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12704 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12705 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12706 sum += keys[b1[r][f]] - keys[b2[r][f]];
12710 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12711 sum += keys[b1[r][f]] - keys[b2[r][f]];
12723 int pieceList[256], quickBoard[256];
12724 ChessSquare pieceType[256] = { EmptySquare };
12725 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12726 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12727 int soughtTotal, turn;
12728 Boolean epOK, flipSearch;
12731 unsigned char piece, to;
12734 #define DSIZE (250000)
12736 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12737 Move *moveDatabase = initialSpace;
12738 unsigned int movePtr, dataSize = DSIZE;
12741 MakePieceList (Board board, int *counts)
12743 int r, f, n=Q_PROMO, total=0;
12744 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12745 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12746 int sq = f + (r<<4);
12747 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12748 quickBoard[sq] = ++n;
12750 pieceType[n] = board[r][f];
12751 counts[board[r][f]]++;
12752 if(board[r][f] == WhiteKing) pieceList[1] = n; else
12753 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12757 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12762 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12764 int sq = fromX + (fromY<<4);
12765 int piece = quickBoard[sq], rook;
12766 quickBoard[sq] = 0;
12767 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12768 if(piece == pieceList[1] && fromY == toY) {
12769 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12770 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12771 moveDatabase[movePtr++].piece = Q_WCASTL;
12772 quickBoard[sq] = piece;
12773 piece = quickBoard[from]; quickBoard[from] = 0;
12774 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12775 } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12776 quickBoard[sq] = 0; // remove Rook
12777 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12778 moveDatabase[movePtr++].piece = Q_WCASTL;
12779 quickBoard[sq] = pieceList[1]; // put King
12781 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12784 if(piece == pieceList[2] && fromY == toY) {
12785 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12786 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12787 moveDatabase[movePtr++].piece = Q_BCASTL;
12788 quickBoard[sq] = piece;
12789 piece = quickBoard[from]; quickBoard[from] = 0;
12790 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12791 } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12792 quickBoard[sq] = 0; // remove Rook
12793 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12794 moveDatabase[movePtr++].piece = Q_BCASTL;
12795 quickBoard[sq] = pieceList[2]; // put King
12797 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12800 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12801 quickBoard[(fromY<<4)+toX] = 0;
12802 moveDatabase[movePtr].piece = Q_EP;
12803 moveDatabase[movePtr++].to = (fromY<<4)+toX;
12804 moveDatabase[movePtr].to = sq;
12806 if(promoPiece != pieceType[piece]) {
12807 moveDatabase[movePtr++].piece = Q_PROMO;
12808 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12810 moveDatabase[movePtr].piece = piece;
12811 quickBoard[sq] = piece;
12816 PackGame (Board board)
12818 Move *newSpace = NULL;
12819 moveDatabase[movePtr].piece = 0; // terminate previous game
12820 if(movePtr > dataSize) {
12821 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12822 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12823 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12826 Move *p = moveDatabase, *q = newSpace;
12827 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
12828 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12829 moveDatabase = newSpace;
12830 } else { // calloc failed, we must be out of memory. Too bad...
12831 dataSize = 0; // prevent calloc events for all subsequent games
12832 return 0; // and signal this one isn't cached
12836 MakePieceList(board, counts);
12841 QuickCompare (Board board, int *minCounts, int *maxCounts)
12842 { // compare according to search mode
12844 switch(appData.searchMode)
12846 case 1: // exact position match
12847 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12848 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12849 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12852 case 2: // can have extra material on empty squares
12853 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12854 if(board[r][f] == EmptySquare) continue;
12855 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12858 case 3: // material with exact Pawn structure
12859 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12860 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12861 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12862 } // fall through to material comparison
12863 case 4: // exact material
12864 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12866 case 6: // material range with given imbalance
12867 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12868 // fall through to range comparison
12869 case 5: // material range
12870 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12876 QuickScan (Board board, Move *move)
12877 { // reconstruct game,and compare all positions in it
12878 int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12880 int piece = move->piece;
12881 int to = move->to, from = pieceList[piece];
12882 if(found < 0) { // if already found just scan to game end for final piece count
12883 if(QuickCompare(soughtBoard, minSought, maxSought) ||
12884 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12885 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12886 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12888 static int lastCounts[EmptySquare+1];
12890 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12891 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12892 } else stretch = 0;
12893 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12894 if(found >= 0 && !appData.minPieces) return found;
12896 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12897 if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12898 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12899 piece = (++move)->piece;
12900 from = pieceList[piece];
12901 counts[pieceType[piece]]--;
12902 pieceType[piece] = (ChessSquare) move->to;
12903 counts[move->to]++;
12904 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12905 counts[pieceType[quickBoard[to]]]--;
12906 quickBoard[to] = 0; total--;
12909 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12910 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12911 from = pieceList[piece]; // so this must be King
12912 quickBoard[from] = 0;
12913 pieceList[piece] = to;
12914 from = pieceList[(++move)->piece]; // for FRC this has to be done here
12915 quickBoard[from] = 0; // rook
12916 quickBoard[to] = piece;
12917 to = move->to; piece = move->piece;
12921 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12922 if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12923 quickBoard[from] = 0;
12925 quickBoard[to] = piece;
12926 pieceList[piece] = to;
12936 flipSearch = FALSE;
12937 CopyBoard(soughtBoard, boards[currentMove]);
12938 soughtTotal = MakePieceList(soughtBoard, maxSought);
12939 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12940 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12941 CopyBoard(reverseBoard, boards[currentMove]);
12942 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12943 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12944 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12945 reverseBoard[r][f] = piece;
12947 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12948 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12949 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12950 || (boards[currentMove][CASTLING][2] == NoRights ||
12951 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12952 && (boards[currentMove][CASTLING][5] == NoRights ||
12953 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12956 CopyBoard(flipBoard, soughtBoard);
12957 CopyBoard(rotateBoard, reverseBoard);
12958 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12959 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
12960 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12963 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12964 if(appData.searchMode >= 5) {
12965 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12966 MakePieceList(soughtBoard, minSought);
12967 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12969 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12970 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12973 GameInfo dummyInfo;
12974 static int creatingBook;
12977 GameContainsPosition (FILE *f, ListGame *lg)
12979 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12980 int fromX, fromY, toX, toY;
12982 static int initDone=FALSE;
12984 // weed out games based on numerical tag comparison
12985 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12986 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12987 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12988 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12990 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12993 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12994 else CopyBoard(boards[scratch], initialPosition); // default start position
12997 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12998 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
13001 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13002 fseek(f, lg->offset, 0);
13005 yyboardindex = scratch;
13006 quickFlag = plyNr+1;
13011 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
13017 if(plyNr) return -1; // after we have seen moves, this is for new game
13020 case AmbiguousMove: // we cannot reconstruct the game beyond these two
13021 case ImpossibleMove:
13022 case WhiteWins: // game ends here with these four
13025 case GameUnfinished:
13029 if(appData.testLegality) return -1;
13030 case WhiteCapturesEnPassant:
13031 case BlackCapturesEnPassant:
13032 case WhitePromotion:
13033 case BlackPromotion:
13034 case WhiteNonPromotion:
13035 case BlackNonPromotion:
13038 case WhiteKingSideCastle:
13039 case WhiteQueenSideCastle:
13040 case BlackKingSideCastle:
13041 case BlackQueenSideCastle:
13042 case WhiteKingSideCastleWild:
13043 case WhiteQueenSideCastleWild:
13044 case BlackKingSideCastleWild:
13045 case BlackQueenSideCastleWild:
13046 case WhiteHSideCastleFR:
13047 case WhiteASideCastleFR:
13048 case BlackHSideCastleFR:
13049 case BlackASideCastleFR:
13050 fromX = currentMoveString[0] - AAA;
13051 fromY = currentMoveString[1] - ONE;
13052 toX = currentMoveString[2] - AAA;
13053 toY = currentMoveString[3] - ONE;
13054 promoChar = currentMoveString[4];
13058 fromX = next == WhiteDrop ?
13059 (int) CharToPiece(ToUpper(currentMoveString[0])) :
13060 (int) CharToPiece(ToLower(currentMoveString[0]));
13062 toX = currentMoveString[2] - AAA;
13063 toY = currentMoveString[3] - ONE;
13067 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
13069 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
13070 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13071 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
13072 if(appData.findMirror) {
13073 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
13074 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
13079 /* Load the nth game from open file f */
13081 LoadGame (FILE *f, int gameNumber, char *title, int useList)
13085 int gn = gameNumber;
13086 ListGame *lg = NULL;
13087 int numPGNTags = 0, i;
13089 GameMode oldGameMode;
13090 VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13091 char oldName[MSG_SIZ];
13093 safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13095 if (appData.debugMode)
13096 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13098 if (gameMode == Training )
13099 SetTrainingModeOff();
13101 oldGameMode = gameMode;
13102 if (gameMode != BeginningOfGame) {
13103 Reset(FALSE, TRUE);
13105 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13108 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13109 fclose(lastLoadGameFP);
13113 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13116 fseek(f, lg->offset, 0);
13117 GameListHighlight(gameNumber);
13118 pos = lg->position;
13122 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13123 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13125 DisplayError(_("Game number out of range"), 0);
13130 if (fseek(f, 0, 0) == -1) {
13131 if (f == lastLoadGameFP ?
13132 gameNumber == lastLoadGameNumber + 1 :
13136 DisplayError(_("Can't seek on game file"), 0);
13141 lastLoadGameFP = f;
13142 lastLoadGameNumber = gameNumber;
13143 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13144 lastLoadGameUseList = useList;
13148 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13149 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13150 lg->gameInfo.black);
13152 } else if (*title != NULLCHAR) {
13153 if (gameNumber > 1) {
13154 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13157 DisplayTitle(title);
13161 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13162 gameMode = PlayFromGameFile;
13166 currentMove = forwardMostMove = backwardMostMove = 0;
13167 CopyBoard(boards[0], initialPosition);
13171 * Skip the first gn-1 games in the file.
13172 * Also skip over anything that precedes an identifiable
13173 * start of game marker, to avoid being confused by
13174 * garbage at the start of the file. Currently
13175 * recognized start of game markers are the move number "1",
13176 * the pattern "gnuchess .* game", the pattern
13177 * "^[#;%] [^ ]* game file", and a PGN tag block.
13178 * A game that starts with one of the latter two patterns
13179 * will also have a move number 1, possibly
13180 * following a position diagram.
13181 * 5-4-02: Let's try being more lenient and allowing a game to
13182 * start with an unnumbered move. Does that break anything?
13184 cm = lastLoadGameStart = EndOfFile;
13186 yyboardindex = forwardMostMove;
13187 cm = (ChessMove) Myylex();
13190 if (cmailMsgLoaded) {
13191 nCmailGames = CMAIL_MAX_GAMES - gn;
13194 DisplayError(_("Game not found in file"), 0);
13201 lastLoadGameStart = cm;
13204 case MoveNumberOne:
13205 switch (lastLoadGameStart) {
13210 case MoveNumberOne:
13212 gn--; /* count this game */
13213 lastLoadGameStart = cm;
13222 switch (lastLoadGameStart) {
13225 case MoveNumberOne:
13227 gn--; /* count this game */
13228 lastLoadGameStart = cm;
13231 lastLoadGameStart = cm; /* game counted already */
13239 yyboardindex = forwardMostMove;
13240 cm = (ChessMove) Myylex();
13241 } while (cm == PGNTag || cm == Comment);
13248 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13249 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
13250 != CMAIL_OLD_RESULT) {
13252 cmailResult[ CMAIL_MAX_GAMES
13253 - gn - 1] = CMAIL_OLD_RESULT;
13260 /* Only a NormalMove can be at the start of a game
13261 * without a position diagram. */
13262 if (lastLoadGameStart == EndOfFile ) {
13264 lastLoadGameStart = MoveNumberOne;
13273 if (appData.debugMode)
13274 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13276 for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13278 if (cm == XBoardGame) {
13279 /* Skip any header junk before position diagram and/or move 1 */
13281 yyboardindex = forwardMostMove;
13282 cm = (ChessMove) Myylex();
13284 if (cm == EndOfFile ||
13285 cm == GNUChessGame || cm == XBoardGame) {
13286 /* Empty game; pretend end-of-file and handle later */
13291 if (cm == MoveNumberOne || cm == PositionDiagram ||
13292 cm == PGNTag || cm == Comment)
13295 } else if (cm == GNUChessGame) {
13296 if (gameInfo.event != NULL) {
13297 free(gameInfo.event);
13299 gameInfo.event = StrSave(yy_text);
13302 startedFromSetupPosition = startedFromPositionFile; // [HGM]
13303 while (cm == PGNTag) {
13304 if (appData.debugMode)
13305 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13306 err = ParsePGNTag(yy_text, &gameInfo);
13307 if (!err) numPGNTags++;
13309 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13310 if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13311 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13312 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13313 InitPosition(TRUE);
13314 oldVariant = gameInfo.variant;
13315 if (appData.debugMode)
13316 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13320 if (gameInfo.fen != NULL) {
13321 Board initial_position;
13322 startedFromSetupPosition = TRUE;
13323 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13325 DisplayError(_("Bad FEN position in file"), 0);
13328 CopyBoard(boards[0], initial_position);
13329 if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13330 CopyBoard(initialPosition, initial_position);
13331 if (blackPlaysFirst) {
13332 currentMove = forwardMostMove = backwardMostMove = 1;
13333 CopyBoard(boards[1], initial_position);
13334 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13335 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13336 timeRemaining[0][1] = whiteTimeRemaining;
13337 timeRemaining[1][1] = blackTimeRemaining;
13338 if (commentList[0] != NULL) {
13339 commentList[1] = commentList[0];
13340 commentList[0] = NULL;
13343 currentMove = forwardMostMove = backwardMostMove = 0;
13345 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13347 initialRulePlies = FENrulePlies;
13348 for( i=0; i< nrCastlingRights; i++ )
13349 initialRights[i] = initial_position[CASTLING][i];
13351 yyboardindex = forwardMostMove;
13352 free(gameInfo.fen);
13353 gameInfo.fen = NULL;
13356 yyboardindex = forwardMostMove;
13357 cm = (ChessMove) Myylex();
13359 /* Handle comments interspersed among the tags */
13360 while (cm == Comment) {
13362 if (appData.debugMode)
13363 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13365 AppendComment(currentMove, p, FALSE);
13366 yyboardindex = forwardMostMove;
13367 cm = (ChessMove) Myylex();
13371 /* don't rely on existence of Event tag since if game was
13372 * pasted from clipboard the Event tag may not exist
13374 if (numPGNTags > 0){
13376 if (gameInfo.variant == VariantNormal) {
13377 VariantClass v = StringToVariant(gameInfo.event);
13378 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13379 if(v < VariantShogi) gameInfo.variant = v;
13382 if( appData.autoDisplayTags ) {
13383 tags = PGNTags(&gameInfo);
13384 TagsPopUp(tags, CmailMsg());
13389 /* Make something up, but don't display it now */
13394 if (cm == PositionDiagram) {
13397 Board initial_position;
13399 if (appData.debugMode)
13400 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13402 if (!startedFromSetupPosition) {
13404 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13405 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13416 initial_position[i][j++] = CharToPiece(*p);
13419 while (*p == ' ' || *p == '\t' ||
13420 *p == '\n' || *p == '\r') p++;
13422 if (strncmp(p, "black", strlen("black"))==0)
13423 blackPlaysFirst = TRUE;
13425 blackPlaysFirst = FALSE;
13426 startedFromSetupPosition = TRUE;
13428 CopyBoard(boards[0], initial_position);
13429 if (blackPlaysFirst) {
13430 currentMove = forwardMostMove = backwardMostMove = 1;
13431 CopyBoard(boards[1], initial_position);
13432 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13433 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13434 timeRemaining[0][1] = whiteTimeRemaining;
13435 timeRemaining[1][1] = blackTimeRemaining;
13436 if (commentList[0] != NULL) {
13437 commentList[1] = commentList[0];
13438 commentList[0] = NULL;
13441 currentMove = forwardMostMove = backwardMostMove = 0;
13444 yyboardindex = forwardMostMove;
13445 cm = (ChessMove) Myylex();
13448 if(!creatingBook) {
13449 if (first.pr == NoProc) {
13450 StartChessProgram(&first);
13452 InitChessProgram(&first, FALSE);
13453 if(gameInfo.variant == VariantUnknown && *oldName) {
13454 safeStrCpy(engineVariant, oldName, MSG_SIZ);
13455 gameInfo.variant = v;
13457 SendToProgram("force\n", &first);
13458 if (startedFromSetupPosition) {
13459 SendBoard(&first, forwardMostMove);
13460 if (appData.debugMode) {
13461 fprintf(debugFP, "Load Game\n");
13463 DisplayBothClocks();
13467 /* [HGM] server: flag to write setup moves in broadcast file as one */
13468 loadFlag = appData.suppressLoadMoves;
13470 while (cm == Comment) {
13472 if (appData.debugMode)
13473 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13475 AppendComment(currentMove, p, FALSE);
13476 yyboardindex = forwardMostMove;
13477 cm = (ChessMove) Myylex();
13480 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13481 cm == WhiteWins || cm == BlackWins ||
13482 cm == GameIsDrawn || cm == GameUnfinished) {
13483 DisplayMessage("", _("No moves in game"));
13484 if (cmailMsgLoaded) {
13485 if (appData.debugMode)
13486 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13490 DrawPosition(FALSE, boards[currentMove]);
13491 DisplayBothClocks();
13492 gameMode = EditGame;
13499 // [HGM] PV info: routine tests if comment empty
13500 if (!matchMode && (pausing || appData.timeDelay != 0)) {
13501 DisplayComment(currentMove - 1, commentList[currentMove]);
13503 if (!matchMode && appData.timeDelay != 0)
13504 DrawPosition(FALSE, boards[currentMove]);
13506 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13507 programStats.ok_to_send = 1;
13510 /* if the first token after the PGN tags is a move
13511 * and not move number 1, retrieve it from the parser
13513 if (cm != MoveNumberOne)
13514 LoadGameOneMove(cm);
13516 /* load the remaining moves from the file */
13517 while (LoadGameOneMove(EndOfFile)) {
13518 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13519 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13522 /* rewind to the start of the game */
13523 currentMove = backwardMostMove;
13525 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13527 if (oldGameMode == AnalyzeFile) {
13528 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13529 AnalyzeFileEvent();
13531 if (oldGameMode == AnalyzeMode) {
13532 AnalyzeFileEvent();
13535 if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13536 long int w, b; // [HGM] adjourn: restore saved clock times
13537 char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13538 if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13539 timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13540 timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13544 if(creatingBook) return TRUE;
13545 if (!matchMode && pos > 0) {
13546 ToNrEvent(pos); // [HGM] no autoplay if selected on position
13548 if (matchMode || appData.timeDelay == 0) {
13550 } else if (appData.timeDelay > 0) {
13551 AutoPlayGameLoop();
13554 if (appData.debugMode)
13555 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13557 loadFlag = 0; /* [HGM] true game starts */
13561 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13563 ReloadPosition (int offset)
13565 int positionNumber = lastLoadPositionNumber + offset;
13566 if (lastLoadPositionFP == NULL) {
13567 DisplayError(_("No position has been loaded yet"), 0);
13570 if (positionNumber <= 0) {
13571 DisplayError(_("Can't back up any further"), 0);
13574 return LoadPosition(lastLoadPositionFP, positionNumber,
13575 lastLoadPositionTitle);
13578 /* Load the nth position from the given file */
13580 LoadPositionFromFile (char *filename, int n, char *title)
13585 if (strcmp(filename, "-") == 0) {
13586 return LoadPosition(stdin, n, "stdin");
13588 f = fopen(filename, "rb");
13590 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13591 DisplayError(buf, errno);
13594 return LoadPosition(f, n, title);
13599 /* Load the nth position from the given open file, and close it */
13601 LoadPosition (FILE *f, int positionNumber, char *title)
13603 char *p, line[MSG_SIZ];
13604 Board initial_position;
13605 int i, j, fenMode, pn;
13607 if (gameMode == Training )
13608 SetTrainingModeOff();
13610 if (gameMode != BeginningOfGame) {
13611 Reset(FALSE, TRUE);
13613 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13614 fclose(lastLoadPositionFP);
13616 if (positionNumber == 0) positionNumber = 1;
13617 lastLoadPositionFP = f;
13618 lastLoadPositionNumber = positionNumber;
13619 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13620 if (first.pr == NoProc && !appData.noChessProgram) {
13621 StartChessProgram(&first);
13622 InitChessProgram(&first, FALSE);
13624 pn = positionNumber;
13625 if (positionNumber < 0) {
13626 /* Negative position number means to seek to that byte offset */
13627 if (fseek(f, -positionNumber, 0) == -1) {
13628 DisplayError(_("Can't seek on position file"), 0);
13633 if (fseek(f, 0, 0) == -1) {
13634 if (f == lastLoadPositionFP ?
13635 positionNumber == lastLoadPositionNumber + 1 :
13636 positionNumber == 1) {
13639 DisplayError(_("Can't seek on position file"), 0);
13644 /* See if this file is FEN or old-style xboard */
13645 if (fgets(line, MSG_SIZ, f) == NULL) {
13646 DisplayError(_("Position not found in file"), 0);
13649 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13650 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13653 if (fenMode || line[0] == '#') pn--;
13655 /* skip positions before number pn */
13656 if (fgets(line, MSG_SIZ, f) == NULL) {
13658 DisplayError(_("Position not found in file"), 0);
13661 if (fenMode || line[0] == '#') pn--;
13667 if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13668 DisplayError(_("Bad FEN position in file"), 0);
13671 if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13672 sscanf(p+4, "%[^;]", bestMove);
13673 } else *bestMove = NULLCHAR;
13674 if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13675 sscanf(p+4, "%[^;]", avoidMove);
13676 } else *avoidMove = NULLCHAR;
13678 (void) fgets(line, MSG_SIZ, f);
13679 (void) fgets(line, MSG_SIZ, f);
13681 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13682 (void) fgets(line, MSG_SIZ, f);
13683 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13686 initial_position[i][j++] = CharToPiece(*p);
13690 blackPlaysFirst = FALSE;
13692 (void) fgets(line, MSG_SIZ, f);
13693 if (strncmp(line, "black", strlen("black"))==0)
13694 blackPlaysFirst = TRUE;
13697 startedFromSetupPosition = TRUE;
13699 CopyBoard(boards[0], initial_position);
13700 if (blackPlaysFirst) {
13701 currentMove = forwardMostMove = backwardMostMove = 1;
13702 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13703 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13704 CopyBoard(boards[1], initial_position);
13705 DisplayMessage("", _("Black to play"));
13707 currentMove = forwardMostMove = backwardMostMove = 0;
13708 DisplayMessage("", _("White to play"));
13710 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13711 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13712 SendToProgram("force\n", &first);
13713 SendBoard(&first, forwardMostMove);
13715 if (appData.debugMode) {
13717 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13718 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13719 fprintf(debugFP, "Load Position\n");
13722 if (positionNumber > 1) {
13723 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13724 DisplayTitle(line);
13726 DisplayTitle(title);
13728 gameMode = EditGame;
13731 timeRemaining[0][1] = whiteTimeRemaining;
13732 timeRemaining[1][1] = blackTimeRemaining;
13733 DrawPosition(FALSE, boards[currentMove]);
13740 CopyPlayerNameIntoFileName (char **dest, char *src)
13742 while (*src != NULLCHAR && *src != ',') {
13747 *(*dest)++ = *src++;
13753 DefaultFileName (char *ext)
13755 static char def[MSG_SIZ];
13758 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13760 CopyPlayerNameIntoFileName(&p, gameInfo.white);
13762 CopyPlayerNameIntoFileName(&p, gameInfo.black);
13764 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13771 /* Save the current game to the given file */
13773 SaveGameToFile (char *filename, int append)
13777 int result, i, t,tot=0;
13779 if (strcmp(filename, "-") == 0) {
13780 return SaveGame(stdout, 0, NULL);
13782 for(i=0; i<10; i++) { // upto 10 tries
13783 f = fopen(filename, append ? "a" : "w");
13784 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13785 if(f || errno != 13) break;
13786 DoSleep(t = 5 + random()%11); // wait 5-15 msec
13790 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13791 DisplayError(buf, errno);
13794 safeStrCpy(buf, lastMsg, MSG_SIZ);
13795 DisplayMessage(_("Waiting for access to save file"), "");
13796 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13797 DisplayMessage(_("Saving game"), "");
13798 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
13799 result = SaveGame(f, 0, NULL);
13800 DisplayMessage(buf, "");
13807 SavePart (char *str)
13809 static char buf[MSG_SIZ];
13812 p = strchr(str, ' ');
13813 if (p == NULL) return str;
13814 strncpy(buf, str, p - str);
13815 buf[p - str] = NULLCHAR;
13819 #define PGN_MAX_LINE 75
13821 #define PGN_SIDE_WHITE 0
13822 #define PGN_SIDE_BLACK 1
13825 FindFirstMoveOutOfBook (int side)
13829 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13830 int index = backwardMostMove;
13831 int has_book_hit = 0;
13833 if( (index % 2) != side ) {
13837 while( index < forwardMostMove ) {
13838 /* Check to see if engine is in book */
13839 int depth = pvInfoList[index].depth;
13840 int score = pvInfoList[index].score;
13846 else if( score == 0 && depth == 63 ) {
13847 in_book = 1; /* Zappa */
13849 else if( score == 2 && depth == 99 ) {
13850 in_book = 1; /* Abrok */
13853 has_book_hit += in_book;
13869 GetOutOfBookInfo (char * buf)
13873 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13875 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13876 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13880 if( oob[0] >= 0 || oob[1] >= 0 ) {
13881 for( i=0; i<2; i++ ) {
13885 if( i > 0 && oob[0] >= 0 ) {
13886 strcat( buf, " " );
13889 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13890 sprintf( buf+strlen(buf), "%s%.2f",
13891 pvInfoList[idx].score >= 0 ? "+" : "",
13892 pvInfoList[idx].score / 100.0 );
13898 /* Save game in PGN style */
13900 SaveGamePGN2 (FILE *f)
13902 int i, offset, linelen, newblock;
13905 int movelen, numlen, blank;
13906 char move_buffer[100]; /* [AS] Buffer for move+PV info */
13908 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13910 PrintPGNTags(f, &gameInfo);
13912 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13914 if (backwardMostMove > 0 || startedFromSetupPosition) {
13915 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13916 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13917 fprintf(f, "\n{--------------\n");
13918 PrintPosition(f, backwardMostMove);
13919 fprintf(f, "--------------}\n");
13923 /* [AS] Out of book annotation */
13924 if( appData.saveOutOfBookInfo ) {
13927 GetOutOfBookInfo( buf );
13929 if( buf[0] != '\0' ) {
13930 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13937 i = backwardMostMove;
13941 while (i < forwardMostMove) {
13942 /* Print comments preceding this move */
13943 if (commentList[i] != NULL) {
13944 if (linelen > 0) fprintf(f, "\n");
13945 fprintf(f, "%s", commentList[i]);
13950 /* Format move number */
13952 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13955 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13957 numtext[0] = NULLCHAR;
13959 numlen = strlen(numtext);
13962 /* Print move number */
13963 blank = linelen > 0 && numlen > 0;
13964 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13973 fprintf(f, "%s", numtext);
13977 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13978 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13981 blank = linelen > 0 && movelen > 0;
13982 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13991 fprintf(f, "%s", move_buffer);
13992 linelen += movelen;
13994 /* [AS] Add PV info if present */
13995 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13996 /* [HGM] add time */
13997 char buf[MSG_SIZ]; int seconds;
13999 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
14005 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
14008 seconds = (seconds + 4)/10; // round to full seconds
14010 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
14012 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
14015 if(appData.cumulativeTimePGN) {
14016 snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
14019 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
14020 pvInfoList[i].score >= 0 ? "+" : "",
14021 pvInfoList[i].score / 100.0,
14022 pvInfoList[i].depth,
14025 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
14027 /* Print score/depth */
14028 blank = linelen > 0 && movelen > 0;
14029 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14038 fprintf(f, "%s", move_buffer);
14039 linelen += movelen;
14045 /* Start a new line */
14046 if (linelen > 0) fprintf(f, "\n");
14048 /* Print comments after last move */
14049 if (commentList[i] != NULL) {
14050 fprintf(f, "%s\n", commentList[i]);
14054 if (gameInfo.resultDetails != NULL &&
14055 gameInfo.resultDetails[0] != NULLCHAR) {
14056 char buf[MSG_SIZ], *p = gameInfo.resultDetails;
14057 if(gameInfo.result == GameUnfinished && appData.clockMode &&
14058 (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
14059 snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
14060 fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
14062 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14066 /* Save game in PGN style and close the file */
14068 SaveGamePGN (FILE *f)
14072 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14076 /* Save game in old style and close the file */
14078 SaveGameOldStyle (FILE *f)
14083 tm = time((time_t *) NULL);
14085 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
14088 if (backwardMostMove > 0 || startedFromSetupPosition) {
14089 fprintf(f, "\n[--------------\n");
14090 PrintPosition(f, backwardMostMove);
14091 fprintf(f, "--------------]\n");
14096 i = backwardMostMove;
14097 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14099 while (i < forwardMostMove) {
14100 if (commentList[i] != NULL) {
14101 fprintf(f, "[%s]\n", commentList[i]);
14104 if ((i % 2) == 1) {
14105 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
14108 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
14110 if (commentList[i] != NULL) {
14114 if (i >= forwardMostMove) {
14118 fprintf(f, "%s\n", parseList[i]);
14123 if (commentList[i] != NULL) {
14124 fprintf(f, "[%s]\n", commentList[i]);
14127 /* This isn't really the old style, but it's close enough */
14128 if (gameInfo.resultDetails != NULL &&
14129 gameInfo.resultDetails[0] != NULLCHAR) {
14130 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14131 gameInfo.resultDetails);
14133 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14140 /* Save the current game to open file f and close the file */
14142 SaveGame (FILE *f, int dummy, char *dummy2)
14144 if (gameMode == EditPosition) EditPositionDone(TRUE);
14145 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14146 if (appData.oldSaveStyle)
14147 return SaveGameOldStyle(f);
14149 return SaveGamePGN(f);
14152 /* Save the current position to the given file */
14154 SavePositionToFile (char *filename)
14159 if (strcmp(filename, "-") == 0) {
14160 return SavePosition(stdout, 0, NULL);
14162 f = fopen(filename, "a");
14164 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14165 DisplayError(buf, errno);
14168 safeStrCpy(buf, lastMsg, MSG_SIZ);
14169 DisplayMessage(_("Waiting for access to save file"), "");
14170 flock(fileno(f), LOCK_EX); // [HGM] lock
14171 DisplayMessage(_("Saving position"), "");
14172 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
14173 SavePosition(f, 0, NULL);
14174 DisplayMessage(buf, "");
14180 /* Save the current position to the given open file and close the file */
14182 SavePosition (FILE *f, int dummy, char *dummy2)
14187 if (gameMode == EditPosition) EditPositionDone(TRUE);
14188 if (appData.oldSaveStyle) {
14189 tm = time((time_t *) NULL);
14191 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14193 fprintf(f, "[--------------\n");
14194 PrintPosition(f, currentMove);
14195 fprintf(f, "--------------]\n");
14197 fen = PositionToFEN(currentMove, NULL, 1);
14198 fprintf(f, "%s\n", fen);
14206 ReloadCmailMsgEvent (int unregister)
14209 static char *inFilename = NULL;
14210 static char *outFilename;
14212 struct stat inbuf, outbuf;
14215 /* Any registered moves are unregistered if unregister is set, */
14216 /* i.e. invoked by the signal handler */
14218 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14219 cmailMoveRegistered[i] = FALSE;
14220 if (cmailCommentList[i] != NULL) {
14221 free(cmailCommentList[i]);
14222 cmailCommentList[i] = NULL;
14225 nCmailMovesRegistered = 0;
14228 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14229 cmailResult[i] = CMAIL_NOT_RESULT;
14233 if (inFilename == NULL) {
14234 /* Because the filenames are static they only get malloced once */
14235 /* and they never get freed */
14236 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14237 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14239 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14240 sprintf(outFilename, "%s.out", appData.cmailGameName);
14243 status = stat(outFilename, &outbuf);
14245 cmailMailedMove = FALSE;
14247 status = stat(inFilename, &inbuf);
14248 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14251 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14252 counts the games, notes how each one terminated, etc.
14254 It would be nice to remove this kludge and instead gather all
14255 the information while building the game list. (And to keep it
14256 in the game list nodes instead of having a bunch of fixed-size
14257 parallel arrays.) Note this will require getting each game's
14258 termination from the PGN tags, as the game list builder does
14259 not process the game moves. --mann
14261 cmailMsgLoaded = TRUE;
14262 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14264 /* Load first game in the file or popup game menu */
14265 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14267 #endif /* !WIN32 */
14275 char string[MSG_SIZ];
14277 if ( cmailMailedMove
14278 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14279 return TRUE; /* Allow free viewing */
14282 /* Unregister move to ensure that we don't leave RegisterMove */
14283 /* with the move registered when the conditions for registering no */
14285 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14286 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14287 nCmailMovesRegistered --;
14289 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14291 free(cmailCommentList[lastLoadGameNumber - 1]);
14292 cmailCommentList[lastLoadGameNumber - 1] = NULL;
14296 if (cmailOldMove == -1) {
14297 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14301 if (currentMove > cmailOldMove + 1) {
14302 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14306 if (currentMove < cmailOldMove) {
14307 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14311 if (forwardMostMove > currentMove) {
14312 /* Silently truncate extra moves */
14316 if ( (currentMove == cmailOldMove + 1)
14317 || ( (currentMove == cmailOldMove)
14318 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14319 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14320 if (gameInfo.result != GameUnfinished) {
14321 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14324 if (commentList[currentMove] != NULL) {
14325 cmailCommentList[lastLoadGameNumber - 1]
14326 = StrSave(commentList[currentMove]);
14328 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14330 if (appData.debugMode)
14331 fprintf(debugFP, "Saving %s for game %d\n",
14332 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14334 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14336 f = fopen(string, "w");
14337 if (appData.oldSaveStyle) {
14338 SaveGameOldStyle(f); /* also closes the file */
14340 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14341 f = fopen(string, "w");
14342 SavePosition(f, 0, NULL); /* also closes the file */
14344 fprintf(f, "{--------------\n");
14345 PrintPosition(f, currentMove);
14346 fprintf(f, "--------------}\n\n");
14348 SaveGame(f, 0, NULL); /* also closes the file*/
14351 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14352 nCmailMovesRegistered ++;
14353 } else if (nCmailGames == 1) {
14354 DisplayError(_("You have not made a move yet"), 0);
14365 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14366 FILE *commandOutput;
14367 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14368 int nBytes = 0; /* Suppress warnings on uninitialized variables */
14374 if (! cmailMsgLoaded) {
14375 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14379 if (nCmailGames == nCmailResults) {
14380 DisplayError(_("No unfinished games"), 0);
14384 #if CMAIL_PROHIBIT_REMAIL
14385 if (cmailMailedMove) {
14386 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);
14387 DisplayError(msg, 0);
14392 if (! (cmailMailedMove || RegisterMove())) return;
14394 if ( cmailMailedMove
14395 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14396 snprintf(string, MSG_SIZ, partCommandString,
14397 appData.debugMode ? " -v" : "", appData.cmailGameName);
14398 commandOutput = popen(string, "r");
14400 if (commandOutput == NULL) {
14401 DisplayError(_("Failed to invoke cmail"), 0);
14403 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14404 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14406 if (nBuffers > 1) {
14407 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14408 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14409 nBytes = MSG_SIZ - 1;
14411 (void) memcpy(msg, buffer, nBytes);
14413 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14415 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14416 cmailMailedMove = TRUE; /* Prevent >1 moves */
14419 for (i = 0; i < nCmailGames; i ++) {
14420 if (cmailResult[i] == CMAIL_NOT_RESULT) {
14425 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14427 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14429 appData.cmailGameName,
14431 LoadGameFromFile(buffer, 1, buffer, FALSE);
14432 cmailMsgLoaded = FALSE;
14436 DisplayInformation(msg);
14437 pclose(commandOutput);
14440 if ((*cmailMsg) != '\0') {
14441 DisplayInformation(cmailMsg);
14446 #endif /* !WIN32 */
14455 int prependComma = 0;
14457 char string[MSG_SIZ]; /* Space for game-list */
14460 if (!cmailMsgLoaded) return "";
14462 if (cmailMailedMove) {
14463 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14465 /* Create a list of games left */
14466 snprintf(string, MSG_SIZ, "[");
14467 for (i = 0; i < nCmailGames; i ++) {
14468 if (! ( cmailMoveRegistered[i]
14469 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14470 if (prependComma) {
14471 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14473 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14477 strcat(string, number);
14480 strcat(string, "]");
14482 if (nCmailMovesRegistered + nCmailResults == 0) {
14483 switch (nCmailGames) {
14485 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14489 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14493 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14498 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14500 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14505 if (nCmailResults == nCmailGames) {
14506 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14508 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14513 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14525 if (gameMode == Training)
14526 SetTrainingModeOff();
14529 cmailMsgLoaded = FALSE;
14530 if (appData.icsActive) {
14531 SendToICS(ics_prefix);
14532 SendToICS("refresh\n");
14537 ExitEvent (int status)
14541 /* Give up on clean exit */
14545 /* Keep trying for clean exit */
14549 if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14550 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14552 if (telnetISR != NULL) {
14553 RemoveInputSource(telnetISR);
14555 if (icsPR != NoProc) {
14556 DestroyChildProcess(icsPR, TRUE);
14559 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14560 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14562 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14563 /* make sure this other one finishes before killing it! */
14564 if(endingGame) { int count = 0;
14565 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14566 while(endingGame && count++ < 10) DoSleep(1);
14567 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14570 /* Kill off chess programs */
14571 if (first.pr != NoProc) {
14574 DoSleep( appData.delayBeforeQuit );
14575 SendToProgram("quit\n", &first);
14576 DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14578 if (second.pr != NoProc) {
14579 DoSleep( appData.delayBeforeQuit );
14580 SendToProgram("quit\n", &second);
14581 DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14583 if (first.isr != NULL) {
14584 RemoveInputSource(first.isr);
14586 if (second.isr != NULL) {
14587 RemoveInputSource(second.isr);
14590 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14591 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14593 ShutDownFrontEnd();
14598 PauseEngine (ChessProgramState *cps)
14600 SendToProgram("pause\n", cps);
14605 UnPauseEngine (ChessProgramState *cps)
14607 SendToProgram("resume\n", cps);
14614 if (appData.debugMode)
14615 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14619 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14621 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14622 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14623 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14625 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14626 HandleMachineMove(stashedInputMove, stalledEngine);
14627 stalledEngine = NULL;
14630 if (gameMode == MachinePlaysWhite ||
14631 gameMode == TwoMachinesPlay ||
14632 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14633 if(first.pause) UnPauseEngine(&first);
14634 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14635 if(second.pause) UnPauseEngine(&second);
14636 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14639 DisplayBothClocks();
14641 if (gameMode == PlayFromGameFile) {
14642 if (appData.timeDelay >= 0)
14643 AutoPlayGameLoop();
14644 } else if (gameMode == IcsExamining && pauseExamInvalid) {
14645 Reset(FALSE, TRUE);
14646 SendToICS(ics_prefix);
14647 SendToICS("refresh\n");
14648 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14649 ForwardInner(forwardMostMove);
14651 pauseExamInvalid = FALSE;
14653 switch (gameMode) {
14657 pauseExamForwardMostMove = forwardMostMove;
14658 pauseExamInvalid = FALSE;
14661 case IcsPlayingWhite:
14662 case IcsPlayingBlack:
14666 case PlayFromGameFile:
14667 (void) StopLoadGameTimer();
14671 case BeginningOfGame:
14672 if (appData.icsActive) return;
14673 /* else fall through */
14674 case MachinePlaysWhite:
14675 case MachinePlaysBlack:
14676 case TwoMachinesPlay:
14677 if (forwardMostMove == 0)
14678 return; /* don't pause if no one has moved */
14679 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14680 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14681 if(onMove->pause) { // thinking engine can be paused
14682 PauseEngine(onMove); // do it
14683 if(onMove->other->pause) // pondering opponent can always be paused immediately
14684 PauseEngine(onMove->other);
14686 SendToProgram("easy\n", onMove->other);
14688 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14689 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14691 PauseEngine(&first);
14693 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14694 } else { // human on move, pause pondering by either method
14696 PauseEngine(&first);
14697 else if(appData.ponderNextMove)
14698 SendToProgram("easy\n", &first);
14701 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14711 EditCommentEvent ()
14713 char title[MSG_SIZ];
14715 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14716 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14718 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14719 WhiteOnMove(currentMove - 1) ? " " : ".. ",
14720 parseList[currentMove - 1]);
14723 EditCommentPopUp(currentMove, title, commentList[currentMove]);
14730 char *tags = PGNTags(&gameInfo);
14732 EditTagsPopUp(tags, NULL);
14739 if(second.analyzing) {
14740 SendToProgram("exit\n", &second);
14741 second.analyzing = FALSE;
14743 if (second.pr == NoProc) StartChessProgram(&second);
14744 InitChessProgram(&second, FALSE);
14745 FeedMovesToProgram(&second, currentMove);
14747 SendToProgram("analyze\n", &second);
14748 second.analyzing = TRUE;
14752 /* Toggle ShowThinking */
14754 ToggleShowThinking()
14756 appData.showThinking = !appData.showThinking;
14757 ShowThinkingEvent();
14761 AnalyzeModeEvent ()
14765 if (!first.analysisSupport) {
14766 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14767 DisplayError(buf, 0);
14770 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14771 if (appData.icsActive) {
14772 if (gameMode != IcsObserving) {
14773 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14774 DisplayError(buf, 0);
14776 if (appData.icsEngineAnalyze) {
14777 if (appData.debugMode)
14778 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14784 /* if enable, user wants to disable icsEngineAnalyze */
14785 if (appData.icsEngineAnalyze) {
14790 appData.icsEngineAnalyze = TRUE;
14791 if (appData.debugMode)
14792 fprintf(debugFP, "ICS engine analyze starting... \n");
14795 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14796 if (appData.noChessProgram || gameMode == AnalyzeMode)
14799 if (gameMode != AnalyzeFile) {
14800 if (!appData.icsEngineAnalyze) {
14802 if (gameMode != EditGame) return 0;
14804 if (!appData.showThinking) ToggleShowThinking();
14805 ResurrectChessProgram();
14806 SendToProgram("analyze\n", &first);
14807 first.analyzing = TRUE;
14808 /*first.maybeThinking = TRUE;*/
14809 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14810 EngineOutputPopUp();
14812 if (!appData.icsEngineAnalyze) {
14813 gameMode = AnalyzeMode;
14814 ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14820 StartAnalysisClock();
14821 GetTimeMark(&lastNodeCountTime);
14827 AnalyzeFileEvent ()
14829 if (appData.noChessProgram || gameMode == AnalyzeFile)
14832 if (!first.analysisSupport) {
14834 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14835 DisplayError(buf, 0);
14839 if (gameMode != AnalyzeMode) {
14840 keepInfo = 1; // mere annotating should not alter PGN tags
14843 if (gameMode != EditGame) return;
14844 if (!appData.showThinking) ToggleShowThinking();
14845 ResurrectChessProgram();
14846 SendToProgram("analyze\n", &first);
14847 first.analyzing = TRUE;
14848 /*first.maybeThinking = TRUE;*/
14849 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14850 EngineOutputPopUp();
14852 gameMode = AnalyzeFile;
14856 StartAnalysisClock();
14857 GetTimeMark(&lastNodeCountTime);
14859 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14860 AnalysisPeriodicEvent(1);
14864 MachineWhiteEvent ()
14867 char *bookHit = NULL;
14869 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14873 if (gameMode == PlayFromGameFile ||
14874 gameMode == TwoMachinesPlay ||
14875 gameMode == Training ||
14876 gameMode == AnalyzeMode ||
14877 gameMode == EndOfGame)
14880 if (gameMode == EditPosition)
14881 EditPositionDone(TRUE);
14883 if (!WhiteOnMove(currentMove)) {
14884 DisplayError(_("It is not White's turn"), 0);
14888 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14891 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14892 gameMode == AnalyzeFile)
14895 ResurrectChessProgram(); /* in case it isn't running */
14896 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14897 gameMode = MachinePlaysWhite;
14900 gameMode = MachinePlaysWhite;
14904 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14906 if (first.sendName) {
14907 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14908 SendToProgram(buf, &first);
14910 if (first.sendTime) {
14911 if (first.useColors) {
14912 SendToProgram("black\n", &first); /*gnu kludge*/
14914 SendTimeRemaining(&first, TRUE);
14916 if (first.useColors) {
14917 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14919 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14920 SetMachineThinkingEnables();
14921 first.maybeThinking = TRUE;
14925 if (appData.autoFlipView && !flipView) {
14926 flipView = !flipView;
14927 DrawPosition(FALSE, NULL);
14928 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14931 if(bookHit) { // [HGM] book: simulate book reply
14932 static char bookMove[MSG_SIZ]; // a bit generous?
14934 programStats.nodes = programStats.depth = programStats.time =
14935 programStats.score = programStats.got_only_move = 0;
14936 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14938 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14939 strcat(bookMove, bookHit);
14940 savedMessage = bookMove; // args for deferred call
14941 savedState = &first;
14942 ScheduleDelayedEvent(DeferredBookMove, 1);
14947 MachineBlackEvent ()
14950 char *bookHit = NULL;
14952 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14956 if (gameMode == PlayFromGameFile ||
14957 gameMode == TwoMachinesPlay ||
14958 gameMode == Training ||
14959 gameMode == AnalyzeMode ||
14960 gameMode == EndOfGame)
14963 if (gameMode == EditPosition)
14964 EditPositionDone(TRUE);
14966 if (WhiteOnMove(currentMove)) {
14967 DisplayError(_("It is not Black's turn"), 0);
14971 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14974 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14975 gameMode == AnalyzeFile)
14978 ResurrectChessProgram(); /* in case it isn't running */
14979 gameMode = MachinePlaysBlack;
14983 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14985 if (first.sendName) {
14986 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14987 SendToProgram(buf, &first);
14989 if (first.sendTime) {
14990 if (first.useColors) {
14991 SendToProgram("white\n", &first); /*gnu kludge*/
14993 SendTimeRemaining(&first, FALSE);
14995 if (first.useColors) {
14996 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14998 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14999 SetMachineThinkingEnables();
15000 first.maybeThinking = TRUE;
15003 if (appData.autoFlipView && flipView) {
15004 flipView = !flipView;
15005 DrawPosition(FALSE, NULL);
15006 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
15008 if(bookHit) { // [HGM] book: simulate book reply
15009 static char bookMove[MSG_SIZ]; // a bit generous?
15011 programStats.nodes = programStats.depth = programStats.time =
15012 programStats.score = programStats.got_only_move = 0;
15013 sprintf(programStats.movelist, "%s (xbook)", bookHit);
15015 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15016 strcat(bookMove, bookHit);
15017 savedMessage = bookMove; // args for deferred call
15018 savedState = &first;
15019 ScheduleDelayedEvent(DeferredBookMove, 1);
15025 DisplayTwoMachinesTitle ()
15028 if (appData.matchGames > 0) {
15029 if(appData.tourneyFile[0]) {
15030 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
15031 gameInfo.white, _("vs."), gameInfo.black,
15032 nextGame+1, appData.matchGames+1,
15033 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
15035 if (first.twoMachinesColor[0] == 'w') {
15036 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15037 gameInfo.white, _("vs."), gameInfo.black,
15038 first.matchWins, second.matchWins,
15039 matchGame - 1 - (first.matchWins + second.matchWins));
15041 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15042 gameInfo.white, _("vs."), gameInfo.black,
15043 second.matchWins, first.matchWins,
15044 matchGame - 1 - (first.matchWins + second.matchWins));
15047 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15053 SettingsMenuIfReady ()
15055 if (second.lastPing != second.lastPong) {
15056 DisplayMessage("", _("Waiting for second chess program"));
15057 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
15061 DisplayMessage("", "");
15062 SettingsPopUp(&second);
15066 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
15069 if (cps->pr == NoProc) {
15070 StartChessProgram(cps);
15071 if (cps->protocolVersion == 1) {
15073 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
15075 /* kludge: allow timeout for initial "feature" command */
15076 if(retry != TwoMachinesEventIfReady) FreezeUI();
15077 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
15078 DisplayMessage("", buf);
15079 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
15087 TwoMachinesEvent P((void))
15089 int i, move = forwardMostMove;
15091 ChessProgramState *onmove;
15092 char *bookHit = NULL;
15093 static int stalling = 0;
15097 if (appData.noChessProgram) return;
15099 switch (gameMode) {
15100 case TwoMachinesPlay:
15102 case MachinePlaysWhite:
15103 case MachinePlaysBlack:
15104 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15105 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15109 case BeginningOfGame:
15110 case PlayFromGameFile:
15113 if (gameMode != EditGame) return;
15116 EditPositionDone(TRUE);
15127 // forwardMostMove = currentMove;
15128 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15129 startingEngine = TRUE;
15131 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15133 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15134 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15135 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15139 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15141 if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15142 gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15143 startingEngine = matchMode = FALSE;
15144 DisplayError("second engine does not play this", 0);
15145 gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15146 EditGameEvent(); // switch back to EditGame mode
15151 InitChessProgram(&second, FALSE); // unbalances ping of second engine
15152 SendToProgram("force\n", &second);
15154 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15158 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15159 if(appData.matchPause>10000 || appData.matchPause<10)
15160 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15161 wait = SubtractTimeMarks(&now, &pauseStart);
15162 if(wait < appData.matchPause) {
15163 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15166 // we are now committed to starting the game
15168 DisplayMessage("", "");
15170 if (startedFromSetupPosition) {
15171 SendBoard(&second, backwardMostMove);
15172 if (appData.debugMode) {
15173 fprintf(debugFP, "Two Machines\n");
15176 for (i = backwardMostMove; i < forwardMostMove; i++) {
15177 SendMoveToProgram(i, &second);
15181 gameMode = TwoMachinesPlay;
15182 pausing = startingEngine = FALSE;
15183 ModeHighlight(); // [HGM] logo: this triggers display update of logos
15185 DisplayTwoMachinesTitle();
15187 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15192 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15193 SendToProgram(first.computerString, &first);
15194 if (first.sendName) {
15195 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15196 SendToProgram(buf, &first);
15199 SendToProgram(second.computerString, &second);
15200 if (second.sendName) {
15201 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15202 SendToProgram(buf, &second);
15206 if (!first.sendTime || !second.sendTime || move == 0) { // [HGM] first engine changed sides from Reset, so recalc time odds
15208 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15209 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15211 if (onmove->sendTime) {
15212 if (onmove->useColors) {
15213 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15215 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15217 if (onmove->useColors) {
15218 SendToProgram(onmove->twoMachinesColor, onmove);
15220 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15221 // SendToProgram("go\n", onmove);
15222 onmove->maybeThinking = TRUE;
15223 SetMachineThinkingEnables();
15227 if(bookHit) { // [HGM] book: simulate book reply
15228 static char bookMove[MSG_SIZ]; // a bit generous?
15230 programStats.nodes = programStats.depth = programStats.time =
15231 programStats.score = programStats.got_only_move = 0;
15232 sprintf(programStats.movelist, "%s (xbook)", bookHit);
15234 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15235 strcat(bookMove, bookHit);
15236 savedMessage = bookMove; // args for deferred call
15237 savedState = onmove;
15238 ScheduleDelayedEvent(DeferredBookMove, 1);
15245 if (gameMode == Training) {
15246 SetTrainingModeOff();
15247 gameMode = PlayFromGameFile;
15248 DisplayMessage("", _("Training mode off"));
15250 gameMode = Training;
15251 animateTraining = appData.animate;
15253 /* make sure we are not already at the end of the game */
15254 if (currentMove < forwardMostMove) {
15255 SetTrainingModeOn();
15256 DisplayMessage("", _("Training mode on"));
15258 gameMode = PlayFromGameFile;
15259 DisplayError(_("Already at end of game"), 0);
15268 if (!appData.icsActive) return;
15269 switch (gameMode) {
15270 case IcsPlayingWhite:
15271 case IcsPlayingBlack:
15274 case BeginningOfGame:
15282 EditPositionDone(TRUE);
15295 gameMode = IcsIdle;
15305 switch (gameMode) {
15307 SetTrainingModeOff();
15309 case MachinePlaysWhite:
15310 case MachinePlaysBlack:
15311 case BeginningOfGame:
15312 SendToProgram("force\n", &first);
15313 if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15314 if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15316 abortEngineThink = TRUE;
15317 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15318 SendToProgram(buf, &first);
15319 DisplayMessage("Aborting engine think", "");
15323 SetUserThinkingEnables();
15325 case PlayFromGameFile:
15326 (void) StopLoadGameTimer();
15327 if (gameFileFP != NULL) {
15332 EditPositionDone(TRUE);
15337 SendToProgram("force\n", &first);
15339 case TwoMachinesPlay:
15340 GameEnds(EndOfFile, NULL, GE_PLAYER);
15341 ResurrectChessProgram();
15342 SetUserThinkingEnables();
15345 ResurrectChessProgram();
15347 case IcsPlayingBlack:
15348 case IcsPlayingWhite:
15349 DisplayError(_("Warning: You are still playing a game"), 0);
15352 DisplayError(_("Warning: You are still observing a game"), 0);
15355 DisplayError(_("Warning: You are still examining a game"), 0);
15366 first.offeredDraw = second.offeredDraw = 0;
15368 if (gameMode == PlayFromGameFile) {
15369 whiteTimeRemaining = timeRemaining[0][currentMove];
15370 blackTimeRemaining = timeRemaining[1][currentMove];
15374 if (gameMode == MachinePlaysWhite ||
15375 gameMode == MachinePlaysBlack ||
15376 gameMode == TwoMachinesPlay ||
15377 gameMode == EndOfGame) {
15378 i = forwardMostMove;
15379 while (i > currentMove) {
15380 SendToProgram("undo\n", &first);
15383 if(!adjustedClock) {
15384 whiteTimeRemaining = timeRemaining[0][currentMove];
15385 blackTimeRemaining = timeRemaining[1][currentMove];
15386 DisplayBothClocks();
15388 if (whiteFlag || blackFlag) {
15389 whiteFlag = blackFlag = 0;
15394 gameMode = EditGame;
15400 EditPositionEvent ()
15403 if (gameMode == EditPosition) {
15409 if (gameMode != EditGame) return;
15411 gameMode = EditPosition;
15414 CopyBoard(rightsBoard, nullBoard);
15415 if (currentMove > 0)
15416 CopyBoard(boards[0], boards[currentMove]);
15417 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15418 rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15420 blackPlaysFirst = !WhiteOnMove(currentMove);
15422 currentMove = forwardMostMove = backwardMostMove = 0;
15423 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15425 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15431 /* [DM] icsEngineAnalyze - possible call from other functions */
15432 if (appData.icsEngineAnalyze) {
15433 appData.icsEngineAnalyze = FALSE;
15435 DisplayMessage("",_("Close ICS engine analyze..."));
15437 if (first.analysisSupport && first.analyzing) {
15438 SendToBoth("exit\n");
15439 first.analyzing = second.analyzing = FALSE;
15441 thinkOutput[0] = NULLCHAR;
15445 EditPositionDone (Boolean fakeRights)
15447 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15449 startedFromSetupPosition = TRUE;
15450 InitChessProgram(&first, FALSE);
15451 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15453 boards[0][EP_STATUS] = EP_NONE;
15454 for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
15455 for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
15456 if(rightsBoard[r][f]) {
15457 ChessSquare p = boards[0][r][f];
15458 if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
15459 else if(p == king) boards[0][CASTLING][2] = f;
15460 else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
15461 else rightsBoard[r][f] = 2; // mark for second pass
15464 for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
15465 if(rightsBoard[r][f] == 2) {
15466 ChessSquare p = boards[0][r][f];
15467 if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
15468 if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
15472 SendToProgram("force\n", &first);
15473 if (blackPlaysFirst) {
15474 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15475 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15476 currentMove = forwardMostMove = backwardMostMove = 1;
15477 CopyBoard(boards[1], boards[0]);
15479 currentMove = forwardMostMove = backwardMostMove = 0;
15481 SendBoard(&first, forwardMostMove);
15482 if (appData.debugMode) {
15483 fprintf(debugFP, "EditPosDone\n");
15486 DisplayMessage("", "");
15487 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15488 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15489 gameMode = EditGame;
15491 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15492 ClearHighlights(); /* [AS] */
15495 /* Pause for `ms' milliseconds */
15496 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15498 TimeDelay (long ms)
15505 } while (SubtractTimeMarks(&m2, &m1) < ms);
15508 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15510 SendMultiLineToICS (char *buf)
15512 char temp[MSG_SIZ+1], *p;
15519 strncpy(temp, buf, len);
15524 if (*p == '\n' || *p == '\r')
15529 strcat(temp, "\n");
15531 SendToPlayer(temp, strlen(temp));
15535 SetWhiteToPlayEvent ()
15537 if (gameMode == EditPosition) {
15538 blackPlaysFirst = FALSE;
15539 DisplayBothClocks(); /* works because currentMove is 0 */
15540 } else if (gameMode == IcsExamining) {
15541 SendToICS(ics_prefix);
15542 SendToICS("tomove white\n");
15547 SetBlackToPlayEvent ()
15549 if (gameMode == EditPosition) {
15550 blackPlaysFirst = TRUE;
15551 currentMove = 1; /* kludge */
15552 DisplayBothClocks();
15554 } else if (gameMode == IcsExamining) {
15555 SendToICS(ics_prefix);
15556 SendToICS("tomove black\n");
15561 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15564 ChessSquare piece = boards[0][y][x];
15565 static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15566 static int lastVariant;
15567 int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15569 if (gameMode != EditPosition && gameMode != IcsExamining) return;
15571 switch (selection) {
15573 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15574 MarkTargetSquares(1);
15575 CopyBoard(currentBoard, boards[0]);
15576 CopyBoard(menuBoard, initialPosition);
15577 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15578 SendToICS(ics_prefix);
15579 SendToICS("bsetup clear\n");
15580 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15581 SendToICS(ics_prefix);
15582 SendToICS("clearboard\n");
15585 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15586 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15587 for (y = 0; y < BOARD_HEIGHT; y++) {
15588 if (gameMode == IcsExamining) {
15589 if (boards[currentMove][y][x] != EmptySquare) {
15590 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15594 } else if(boards[0][y][x] != DarkSquare) {
15595 if(boards[0][y][x] != p) nonEmpty++;
15596 boards[0][y][x] = p;
15600 CopyBoard(rightsBoard, nullBoard);
15601 if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15603 for(r = 0; r < BOARD_HEIGHT; r++) {
15604 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
15605 ChessSquare p = menuBoard[r][x];
15606 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15609 menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
15610 DisplayMessage("Clicking clock again restores position", "");
15611 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15612 if(!nonEmpty) { // asked to clear an empty board
15613 CopyBoard(boards[0], menuBoard);
15615 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15616 CopyBoard(boards[0], initialPosition);
15618 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15619 && !CompareBoards(nullBoard, erasedBoard)) {
15620 CopyBoard(boards[0], erasedBoard);
15622 CopyBoard(erasedBoard, currentBoard);
15624 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15625 rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15628 if (gameMode == EditPosition) {
15629 DrawPosition(FALSE, boards[0]);
15634 SetWhiteToPlayEvent();
15638 SetBlackToPlayEvent();
15642 if (gameMode == IcsExamining) {
15643 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15644 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15647 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15648 if(x == BOARD_LEFT-2) {
15649 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15650 boards[0][y][1] = 0;
15652 if(x == BOARD_RGHT+1) {
15653 if(y >= gameInfo.holdingsSize) break;
15654 boards[0][y][BOARD_WIDTH-2] = 0;
15657 boards[0][y][x] = EmptySquare;
15658 DrawPosition(FALSE, boards[0]);
15663 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15664 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
15665 selection = (ChessSquare) (PROMOTED(piece));
15666 } else if(piece == EmptySquare) selection = WhiteSilver;
15667 else selection = (ChessSquare)((int)piece - 1);
15671 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15672 piece > (int)BlackMan && piece <= (int)BlackKing ) {
15673 selection = (ChessSquare) (DEMOTED(piece));
15674 } else if(piece == EmptySquare) selection = BlackSilver;
15675 else selection = (ChessSquare)((int)piece + 1);
15680 if(gameInfo.variant == VariantShatranj ||
15681 gameInfo.variant == VariantXiangqi ||
15682 gameInfo.variant == VariantCourier ||
15683 gameInfo.variant == VariantASEAN ||
15684 gameInfo.variant == VariantMakruk )
15685 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15691 if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15692 if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15698 if(gameInfo.variant == VariantXiangqi)
15699 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15700 if(gameInfo.variant == VariantKnightmate)
15701 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15702 if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15705 if (gameMode == IcsExamining) {
15706 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15707 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15708 PieceToChar(selection), AAA + x, ONE + y);
15711 rightsBoard[y][x] = hasRights;
15712 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15714 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15715 n = PieceToNumber(selection - BlackPawn);
15716 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15717 boards[0][handSize-1-n][0] = selection;
15718 boards[0][handSize-1-n][1]++;
15720 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15721 n = PieceToNumber(selection);
15722 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15723 boards[0][n][BOARD_WIDTH-1] = selection;
15724 boards[0][n][BOARD_WIDTH-2]++;
15727 boards[0][y][x] = selection;
15728 DrawPosition(TRUE, boards[0]);
15730 fromX = fromY = -1;
15738 DropMenuEvent (ChessSquare selection, int x, int y)
15740 ChessMove moveType;
15742 switch (gameMode) {
15743 case IcsPlayingWhite:
15744 case MachinePlaysBlack:
15745 if (!WhiteOnMove(currentMove)) {
15746 DisplayMoveError(_("It is Black's turn"));
15749 moveType = WhiteDrop;
15751 case IcsPlayingBlack:
15752 case MachinePlaysWhite:
15753 if (WhiteOnMove(currentMove)) {
15754 DisplayMoveError(_("It is White's turn"));
15757 moveType = BlackDrop;
15760 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15766 if (moveType == BlackDrop && selection < BlackPawn) {
15767 selection = (ChessSquare) ((int) selection
15768 + (int) BlackPawn - (int) WhitePawn);
15770 if (boards[currentMove][y][x] != EmptySquare) {
15771 DisplayMoveError(_("That square is occupied"));
15775 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15781 /* Accept a pending offer of any kind from opponent */
15783 if (appData.icsActive) {
15784 SendToICS(ics_prefix);
15785 SendToICS("accept\n");
15786 } else if (cmailMsgLoaded) {
15787 if (currentMove == cmailOldMove &&
15788 commentList[cmailOldMove] != NULL &&
15789 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15790 "Black offers a draw" : "White offers a draw")) {
15792 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15793 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15795 DisplayError(_("There is no pending offer on this move"), 0);
15796 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15799 /* Not used for offers from chess program */
15806 /* Decline a pending offer of any kind from opponent */
15808 if (appData.icsActive) {
15809 SendToICS(ics_prefix);
15810 SendToICS("decline\n");
15811 } else if (cmailMsgLoaded) {
15812 if (currentMove == cmailOldMove &&
15813 commentList[cmailOldMove] != NULL &&
15814 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15815 "Black offers a draw" : "White offers a draw")) {
15817 AppendComment(cmailOldMove, "Draw declined", TRUE);
15818 DisplayComment(cmailOldMove - 1, "Draw declined");
15821 DisplayError(_("There is no pending offer on this move"), 0);
15824 /* Not used for offers from chess program */
15831 /* Issue ICS rematch command */
15832 if (appData.icsActive) {
15833 SendToICS(ics_prefix);
15834 SendToICS("rematch\n");
15841 /* Call your opponent's flag (claim a win on time) */
15842 if (appData.icsActive) {
15843 SendToICS(ics_prefix);
15844 SendToICS("flag\n");
15846 switch (gameMode) {
15849 case MachinePlaysWhite:
15852 GameEnds(GameIsDrawn, "Both players ran out of time",
15855 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15857 DisplayError(_("Your opponent is not out of time"), 0);
15860 case MachinePlaysBlack:
15863 GameEnds(GameIsDrawn, "Both players ran out of time",
15866 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15868 DisplayError(_("Your opponent is not out of time"), 0);
15876 ClockClick (int which)
15877 { // [HGM] code moved to back-end from winboard.c
15878 if(which) { // black clock
15879 if (gameMode == EditPosition || gameMode == IcsExamining) {
15880 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15881 SetBlackToPlayEvent();
15882 } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15883 gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15884 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15885 } else if (shiftKey) {
15886 AdjustClock(which, -1);
15887 } else if (gameMode == IcsPlayingWhite ||
15888 gameMode == MachinePlaysBlack) {
15891 } else { // white clock
15892 if (gameMode == EditPosition || gameMode == IcsExamining) {
15893 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15894 SetWhiteToPlayEvent();
15895 } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15896 gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15897 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15898 } else if (shiftKey) {
15899 AdjustClock(which, -1);
15900 } else if (gameMode == IcsPlayingBlack ||
15901 gameMode == MachinePlaysWhite) {
15910 /* Offer draw or accept pending draw offer from opponent */
15912 if (appData.icsActive) {
15913 /* Note: tournament rules require draw offers to be
15914 made after you make your move but before you punch
15915 your clock. Currently ICS doesn't let you do that;
15916 instead, you immediately punch your clock after making
15917 a move, but you can offer a draw at any time. */
15919 SendToICS(ics_prefix);
15920 SendToICS("draw\n");
15921 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15922 } else if (cmailMsgLoaded) {
15923 if (currentMove == cmailOldMove &&
15924 commentList[cmailOldMove] != NULL &&
15925 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15926 "Black offers a draw" : "White offers a draw")) {
15927 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15928 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15929 } else if (currentMove == cmailOldMove + 1) {
15930 char *offer = WhiteOnMove(cmailOldMove) ?
15931 "White offers a draw" : "Black offers a draw";
15932 AppendComment(currentMove, offer, TRUE);
15933 DisplayComment(currentMove - 1, offer);
15934 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15936 DisplayError(_("You must make your move before offering a draw"), 0);
15937 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15939 } else if (first.offeredDraw) {
15940 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15942 if (first.sendDrawOffers) {
15943 SendToProgram("draw\n", &first);
15944 userOfferedDraw = TRUE;
15952 /* Offer Adjourn or accept pending Adjourn offer from opponent */
15954 if (appData.icsActive) {
15955 SendToICS(ics_prefix);
15956 SendToICS("adjourn\n");
15958 /* Currently GNU Chess doesn't offer or accept Adjourns */
15966 /* Offer Abort or accept pending Abort offer from opponent */
15968 if (appData.icsActive) {
15969 SendToICS(ics_prefix);
15970 SendToICS("abort\n");
15972 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15979 /* Resign. You can do this even if it's not your turn. */
15981 if (appData.icsActive) {
15982 SendToICS(ics_prefix);
15983 SendToICS("resign\n");
15985 switch (gameMode) {
15986 case MachinePlaysWhite:
15987 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15989 case MachinePlaysBlack:
15990 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15993 if (cmailMsgLoaded) {
15995 if (WhiteOnMove(cmailOldMove)) {
15996 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15998 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
16000 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
16011 StopObservingEvent ()
16013 /* Stop observing current games */
16014 SendToICS(ics_prefix);
16015 SendToICS("unobserve\n");
16019 StopExaminingEvent ()
16021 /* Stop observing current game */
16022 SendToICS(ics_prefix);
16023 SendToICS("unexamine\n");
16027 ForwardInner (int target)
16029 int limit; int oldSeekGraphUp = seekGraphUp;
16031 if (appData.debugMode)
16032 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
16033 target, currentMove, forwardMostMove);
16035 if (gameMode == EditPosition)
16038 seekGraphUp = FALSE;
16039 MarkTargetSquares(1);
16040 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16042 if (gameMode == PlayFromGameFile && !pausing)
16045 if (gameMode == IcsExamining && pausing)
16046 limit = pauseExamForwardMostMove;
16048 limit = forwardMostMove;
16050 if (target > limit) target = limit;
16052 if (target > 0 && moveList[target - 1][0]) {
16053 int fromX, fromY, toX, toY;
16054 toX = moveList[target - 1][2] - AAA;
16055 toY = moveList[target - 1][3] - ONE;
16056 if (moveList[target - 1][1] == '@') {
16057 if (appData.highlightLastMove) {
16058 SetHighlights(-1, -1, toX, toY);
16061 fromX = moveList[target - 1][0] - AAA;
16062 fromY = moveList[target - 1][1] - ONE;
16063 if (target == currentMove + 1) {
16064 if(moveList[target - 1][4] == ';') { // multi-leg
16065 killX = moveList[target - 1][5] - AAA;
16066 killY = moveList[target - 1][6] - ONE;
16068 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
16069 killX = killY = -1;
16071 if (appData.highlightLastMove) {
16072 SetHighlights(fromX, fromY, toX, toY);
16076 if (gameMode == EditGame || gameMode == AnalyzeMode ||
16077 gameMode == Training || gameMode == PlayFromGameFile ||
16078 gameMode == AnalyzeFile) {
16079 while (currentMove < target) {
16080 if(second.analyzing) SendMoveToProgram(currentMove, &second);
16081 SendMoveToProgram(currentMove++, &first);
16084 currentMove = target;
16087 if (gameMode == EditGame || gameMode == EndOfGame) {
16088 whiteTimeRemaining = timeRemaining[0][currentMove];
16089 blackTimeRemaining = timeRemaining[1][currentMove];
16091 DisplayBothClocks();
16092 DisplayMove(currentMove - 1);
16093 DrawPosition(oldSeekGraphUp, boards[currentMove]);
16094 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16095 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
16096 DisplayComment(currentMove - 1, commentList[currentMove]);
16098 ClearMap(); // [HGM] exclude: invalidate map
16105 if (gameMode == IcsExamining && !pausing) {
16106 SendToICS(ics_prefix);
16107 SendToICS("forward\n");
16109 ForwardInner(currentMove + 1);
16116 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16117 /* to optimze, we temporarily turn off analysis mode while we feed
16118 * the remaining moves to the engine. Otherwise we get analysis output
16121 if (first.analysisSupport) {
16122 SendToProgram("exit\nforce\n", &first);
16123 first.analyzing = FALSE;
16127 if (gameMode == IcsExamining && !pausing) {
16128 SendToICS(ics_prefix);
16129 SendToICS("forward 999999\n");
16131 ForwardInner(forwardMostMove);
16134 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16135 /* we have fed all the moves, so reactivate analysis mode */
16136 SendToProgram("analyze\n", &first);
16137 first.analyzing = TRUE;
16138 /*first.maybeThinking = TRUE;*/
16139 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16144 BackwardInner (int target)
16146 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16148 if (appData.debugMode)
16149 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16150 target, currentMove, forwardMostMove);
16152 if (gameMode == EditPosition) return;
16153 seekGraphUp = FALSE;
16154 MarkTargetSquares(1);
16155 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16156 if (currentMove <= backwardMostMove) {
16158 DrawPosition(full_redraw, boards[currentMove]);
16161 if (gameMode == PlayFromGameFile && !pausing)
16164 if (moveList[target][0]) {
16165 int fromX, fromY, toX, toY;
16166 toX = moveList[target][2] - AAA;
16167 toY = moveList[target][3] - ONE;
16168 if (moveList[target][1] == '@') {
16169 if (appData.highlightLastMove) {
16170 SetHighlights(-1, -1, toX, toY);
16173 fromX = moveList[target][0] - AAA;
16174 fromY = moveList[target][1] - ONE;
16175 if (target == currentMove - 1) {
16176 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16178 if (appData.highlightLastMove) {
16179 SetHighlights(fromX, fromY, toX, toY);
16183 if (gameMode == EditGame || gameMode==AnalyzeMode ||
16184 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16185 while (currentMove > target) {
16186 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16187 // null move cannot be undone. Reload program with move history before it.
16189 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16190 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16192 SendBoard(&first, i);
16193 if(second.analyzing) SendBoard(&second, i);
16194 for(currentMove=i; currentMove<target; currentMove++) {
16195 SendMoveToProgram(currentMove, &first);
16196 if(second.analyzing) SendMoveToProgram(currentMove, &second);
16200 SendToBoth("undo\n");
16204 currentMove = target;
16207 if (gameMode == EditGame || gameMode == EndOfGame) {
16208 whiteTimeRemaining = timeRemaining[0][currentMove];
16209 blackTimeRemaining = timeRemaining[1][currentMove];
16211 DisplayBothClocks();
16212 DisplayMove(currentMove - 1);
16213 DrawPosition(full_redraw, boards[currentMove]);
16214 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16215 // [HGM] PV info: routine tests if comment empty
16216 DisplayComment(currentMove - 1, commentList[currentMove]);
16217 ClearMap(); // [HGM] exclude: invalidate map
16223 if (gameMode == IcsExamining && !pausing) {
16224 SendToICS(ics_prefix);
16225 SendToICS("backward\n");
16227 BackwardInner(currentMove - 1);
16234 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16235 /* to optimize, we temporarily turn off analysis mode while we undo
16236 * all the moves. Otherwise we get analysis output after each undo.
16238 if (first.analysisSupport) {
16239 SendToProgram("exit\nforce\n", &first);
16240 first.analyzing = FALSE;
16244 if (gameMode == IcsExamining && !pausing) {
16245 SendToICS(ics_prefix);
16246 SendToICS("backward 999999\n");
16248 BackwardInner(backwardMostMove);
16251 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16252 /* we have fed all the moves, so reactivate analysis mode */
16253 SendToProgram("analyze\n", &first);
16254 first.analyzing = TRUE;
16255 /*first.maybeThinking = TRUE;*/
16256 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16263 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16264 if (to >= forwardMostMove) to = forwardMostMove;
16265 if (to <= backwardMostMove) to = backwardMostMove;
16266 if (to < currentMove) {
16274 RevertEvent (Boolean annotate)
16276 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16279 if (gameMode != IcsExamining) {
16280 DisplayError(_("You are not examining a game"), 0);
16284 DisplayError(_("You can't revert while pausing"), 0);
16287 SendToICS(ics_prefix);
16288 SendToICS("revert\n");
16292 RetractMoveEvent ()
16294 switch (gameMode) {
16295 case MachinePlaysWhite:
16296 case MachinePlaysBlack:
16297 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16298 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16301 if (forwardMostMove < 2) return;
16302 currentMove = forwardMostMove = forwardMostMove - 2;
16303 whiteTimeRemaining = timeRemaining[0][currentMove];
16304 blackTimeRemaining = timeRemaining[1][currentMove];
16305 DisplayBothClocks();
16306 DisplayMove(currentMove - 1);
16307 ClearHighlights();/*!! could figure this out*/
16308 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16309 SendToProgram("remove\n", &first);
16310 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16313 case BeginningOfGame:
16317 case IcsPlayingWhite:
16318 case IcsPlayingBlack:
16319 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16320 SendToICS(ics_prefix);
16321 SendToICS("takeback 2\n");
16323 SendToICS(ics_prefix);
16324 SendToICS("takeback 1\n");
16333 ChessProgramState *cps;
16335 switch (gameMode) {
16336 case MachinePlaysWhite:
16337 if (!WhiteOnMove(forwardMostMove)) {
16338 DisplayError(_("It is your turn"), 0);
16343 case MachinePlaysBlack:
16344 if (WhiteOnMove(forwardMostMove)) {
16345 DisplayError(_("It is your turn"), 0);
16350 case TwoMachinesPlay:
16351 if (WhiteOnMove(forwardMostMove) ==
16352 (first.twoMachinesColor[0] == 'w')) {
16358 case BeginningOfGame:
16362 SendToProgram("?\n", cps);
16366 TruncateGameEvent ()
16369 if (gameMode != EditGame) return;
16376 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16377 if (forwardMostMove > currentMove) {
16378 if (gameInfo.resultDetails != NULL) {
16379 free(gameInfo.resultDetails);
16380 gameInfo.resultDetails = NULL;
16381 gameInfo.result = GameUnfinished;
16383 forwardMostMove = currentMove;
16384 HistorySet(parseList, backwardMostMove, forwardMostMove,
16392 if (appData.noChessProgram) return;
16393 switch (gameMode) {
16394 case MachinePlaysWhite:
16395 if (WhiteOnMove(forwardMostMove)) {
16396 DisplayError(_("Wait until your turn."), 0);
16400 case BeginningOfGame:
16401 case MachinePlaysBlack:
16402 if (!WhiteOnMove(forwardMostMove)) {
16403 DisplayError(_("Wait until your turn."), 0);
16408 DisplayError(_("No hint available"), 0);
16411 SendToProgram("hint\n", &first);
16412 hintRequested = TRUE;
16416 SaveSelected (FILE *g, int dummy, char *dummy2)
16418 ListGame * lg = (ListGame *) gameList.head;
16422 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16423 DisplayError(_("Game list not loaded or empty"), 0);
16427 creatingBook = TRUE; // suppresses stuff during load game
16429 /* Get list size */
16430 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16431 if(lg->position >= 0) { // selected?
16432 LoadGame(f, nItem, "", TRUE);
16433 SaveGamePGN2(g); // leaves g open
16436 lg = (ListGame *) lg->node.succ;
16440 creatingBook = FALSE;
16448 ListGame * lg = (ListGame *) gameList.head;
16451 static int secondTime = FALSE;
16453 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16454 DisplayError(_("Game list not loaded or empty"), 0);
16458 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16461 DisplayNote(_("Book file exists! Try again for overwrite."));
16465 creatingBook = TRUE;
16466 secondTime = FALSE;
16468 /* Get list size */
16469 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16470 if(lg->position >= 0) {
16471 LoadGame(f, nItem, "", TRUE);
16472 AddGameToBook(TRUE);
16475 lg = (ListGame *) lg->node.succ;
16478 creatingBook = FALSE;
16485 if (appData.noChessProgram) return;
16486 switch (gameMode) {
16487 case MachinePlaysWhite:
16488 if (WhiteOnMove(forwardMostMove)) {
16489 DisplayError(_("Wait until your turn."), 0);
16493 case BeginningOfGame:
16494 case MachinePlaysBlack:
16495 if (!WhiteOnMove(forwardMostMove)) {
16496 DisplayError(_("Wait until your turn."), 0);
16501 EditPositionDone(TRUE);
16503 case TwoMachinesPlay:
16508 SendToProgram("bk\n", &first);
16509 bookOutput[0] = NULLCHAR;
16510 bookRequested = TRUE;
16516 char *tags = PGNTags(&gameInfo);
16517 TagsPopUp(tags, CmailMsg());
16521 /* end button procedures */
16524 PrintPosition (FILE *fp, int move)
16528 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16529 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16530 char c = PieceToChar(boards[move][i][j]);
16531 fputc(c == '?' ? '.' : c, fp);
16532 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16535 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16536 fprintf(fp, "white to play\n");
16538 fprintf(fp, "black to play\n");
16542 PrintOpponents (FILE *fp)
16544 if (gameInfo.white != NULL) {
16545 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16551 /* Find last component of program's own name, using some heuristics */
16553 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16556 int local = (strcmp(host, "localhost") == 0);
16557 while (!local && (p = strchr(prog, ';')) != NULL) {
16559 while (*p == ' ') p++;
16562 if (*prog == '"' || *prog == '\'') {
16563 q = strchr(prog + 1, *prog);
16565 q = strchr(prog, ' ');
16567 if (q == NULL) q = prog + strlen(prog);
16569 while (p >= prog && *p != '/' && *p != '\\') p--;
16571 if(p == prog && *p == '"') p++;
16573 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16574 memcpy(buf, p, q - p);
16575 buf[q - p] = NULLCHAR;
16583 TimeControlTagValue ()
16586 if (!appData.clockMode) {
16587 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16588 } else if (movesPerSession > 0) {
16589 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16590 } else if (timeIncrement == 0) {
16591 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16593 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16595 return StrSave(buf);
16601 /* This routine is used only for certain modes */
16602 VariantClass v = gameInfo.variant;
16603 ChessMove r = GameUnfinished;
16606 if(keepInfo) return;
16608 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16609 r = gameInfo.result;
16610 p = gameInfo.resultDetails;
16611 gameInfo.resultDetails = NULL;
16613 ClearGameInfo(&gameInfo);
16614 gameInfo.variant = v;
16616 switch (gameMode) {
16617 case MachinePlaysWhite:
16618 gameInfo.event = StrSave( appData.pgnEventHeader );
16619 gameInfo.site = StrSave(HostName());
16620 gameInfo.date = PGNDate();
16621 gameInfo.round = StrSave("-");
16622 gameInfo.white = StrSave(first.tidy);
16623 gameInfo.black = StrSave(UserName());
16624 gameInfo.timeControl = TimeControlTagValue();
16627 case MachinePlaysBlack:
16628 gameInfo.event = StrSave( appData.pgnEventHeader );
16629 gameInfo.site = StrSave(HostName());
16630 gameInfo.date = PGNDate();
16631 gameInfo.round = StrSave("-");
16632 gameInfo.white = StrSave(UserName());
16633 gameInfo.black = StrSave(first.tidy);
16634 gameInfo.timeControl = TimeControlTagValue();
16637 case TwoMachinesPlay:
16638 gameInfo.event = StrSave( appData.pgnEventHeader );
16639 gameInfo.site = StrSave(HostName());
16640 gameInfo.date = PGNDate();
16643 snprintf(buf, MSG_SIZ, "%d", roundNr);
16644 gameInfo.round = StrSave(buf);
16646 gameInfo.round = StrSave("-");
16648 if (first.twoMachinesColor[0] == 'w') {
16649 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16650 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16652 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16653 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16655 gameInfo.timeControl = TimeControlTagValue();
16659 gameInfo.event = StrSave("Edited game");
16660 gameInfo.site = StrSave(HostName());
16661 gameInfo.date = PGNDate();
16662 gameInfo.round = StrSave("-");
16663 gameInfo.white = StrSave("-");
16664 gameInfo.black = StrSave("-");
16665 gameInfo.result = r;
16666 gameInfo.resultDetails = p;
16670 gameInfo.event = StrSave("Edited position");
16671 gameInfo.site = StrSave(HostName());
16672 gameInfo.date = PGNDate();
16673 gameInfo.round = StrSave("-");
16674 gameInfo.white = StrSave("-");
16675 gameInfo.black = StrSave("-");
16678 case IcsPlayingWhite:
16679 case IcsPlayingBlack:
16684 case PlayFromGameFile:
16685 gameInfo.event = StrSave("Game from non-PGN file");
16686 gameInfo.site = StrSave(HostName());
16687 gameInfo.date = PGNDate();
16688 gameInfo.round = StrSave("-");
16689 gameInfo.white = StrSave("?");
16690 gameInfo.black = StrSave("?");
16699 ReplaceComment (int index, char *text)
16705 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16706 pvInfoList[index-1].depth == len &&
16707 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16708 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16709 while (*text == '\n') text++;
16710 len = strlen(text);
16711 while (len > 0 && text[len - 1] == '\n') len--;
16713 if (commentList[index] != NULL)
16714 free(commentList[index]);
16717 commentList[index] = NULL;
16720 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16721 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16722 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16723 commentList[index] = (char *) malloc(len + 2);
16724 strncpy(commentList[index], text, len);
16725 commentList[index][len] = '\n';
16726 commentList[index][len + 1] = NULLCHAR;
16728 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16730 commentList[index] = (char *) malloc(len + 7);
16731 safeStrCpy(commentList[index], "{\n", 3);
16732 safeStrCpy(commentList[index]+2, text, len+1);
16733 commentList[index][len+2] = NULLCHAR;
16734 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16735 strcat(commentList[index], "\n}\n");
16740 CrushCRs (char *text)
16748 if (ch == '\r') continue;
16750 } while (ch != '\0');
16754 AppendComment (int index, char *text, Boolean addBraces)
16755 /* addBraces tells if we should add {} */
16760 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16761 if(addBraces == 3) addBraces = 0; else // force appending literally
16762 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16765 while (*text == '\n') text++;
16766 len = strlen(text);
16767 while (len > 0 && text[len - 1] == '\n') len--;
16768 text[len] = NULLCHAR;
16770 if (len == 0) return;
16772 if (commentList[index] != NULL) {
16773 Boolean addClosingBrace = addBraces;
16774 old = commentList[index];
16775 oldlen = strlen(old);
16776 while(commentList[index][oldlen-1] == '\n')
16777 commentList[index][--oldlen] = NULLCHAR;
16778 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16779 safeStrCpy(commentList[index], old, oldlen + len + 6);
16781 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16782 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16783 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16784 while (*text == '\n') { text++; len--; }
16785 commentList[index][--oldlen] = NULLCHAR;
16787 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16788 else strcat(commentList[index], "\n");
16789 strcat(commentList[index], text);
16790 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16791 else strcat(commentList[index], "\n");
16793 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16795 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16796 else commentList[index][0] = NULLCHAR;
16797 strcat(commentList[index], text);
16798 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16799 if(addBraces == TRUE) strcat(commentList[index], "}\n");
16804 FindStr (char * text, char * sub_text)
16806 char * result = strstr( text, sub_text );
16808 if( result != NULL ) {
16809 result += strlen( sub_text );
16815 /* [AS] Try to extract PV info from PGN comment */
16816 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16818 GetInfoFromComment (int index, char * text)
16820 char * sep = text, *p;
16822 if( text != NULL && index > 0 ) {
16825 int time = -1, sec = 0, deci;
16826 char * s_eval = FindStr( text, "[%eval " );
16827 char * s_emt = FindStr( text, "[%emt " );
16829 if( s_eval != NULL || s_emt != NULL ) {
16831 if(0) { // [HGM] this code is not finished, and could actually be detrimental
16836 if( s_eval != NULL ) {
16837 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16841 if( delim != ']' ) {
16846 if( s_emt != NULL ) {
16851 /* We expect something like: [+|-]nnn.nn/dd */
16854 if(*text != '{') return text; // [HGM] braces: must be normal comment
16856 sep = strchr( text, '/' );
16857 if( sep == NULL || sep < (text+4) ) {
16862 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16863 if(p[1] == '(') { // comment starts with PV
16864 p = strchr(p, ')'); // locate end of PV
16865 if(p == NULL || sep < p+5) return text;
16866 // at this point we have something like "{(.*) +0.23/6 ..."
16867 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16868 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16869 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16871 time = -1; sec = -1; deci = -1;
16872 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16873 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16874 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16875 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
16879 if( score_lo < 0 || score_lo >= 100 ) {
16883 if(sec >= 0) time = 600*time + 10*sec; else
16884 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16886 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16888 /* [HGM] PV time: now locate end of PV info */
16889 while( *++sep >= '0' && *sep <= '9'); // strip depth
16891 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16893 while( *++sep >= '0' && *sep <= '9'); // strip seconds
16895 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16896 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16907 pvInfoList[index-1].depth = depth;
16908 pvInfoList[index-1].score = score;
16909 pvInfoList[index-1].time = 10*time; // centi-sec
16910 if(*sep == '}') *sep = 0; else *--sep = '{';
16912 while(*p++ = *sep++)
16915 } // squeeze out space between PV and comment, and return both
16921 SendToProgram (char *message, ChessProgramState *cps)
16923 int count, outCount, error;
16926 if (cps->pr == NoProc) return;
16929 if (appData.debugMode) {
16932 fprintf(debugFP, "%ld >%-6s: %s",
16933 SubtractTimeMarks(&now, &programStartTime),
16934 cps->which, message);
16936 fprintf(serverFP, "%ld >%-6s: %s",
16937 SubtractTimeMarks(&now, &programStartTime),
16938 cps->which, message), fflush(serverFP);
16941 count = strlen(message);
16942 outCount = OutputToProcess(cps->pr, message, count, &error);
16943 if (outCount < count && !exiting
16944 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16945 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16946 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16947 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16948 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16949 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16950 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16951 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16953 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16954 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16955 gameInfo.result = res;
16957 gameInfo.resultDetails = StrSave(buf);
16959 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16960 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16965 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16969 ChessProgramState *cps = (ChessProgramState *)closure;
16971 if (isr != cps->isr) return; /* Killed intentionally */
16974 RemoveInputSource(cps->isr);
16975 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16976 _(cps->which), cps->program);
16977 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16978 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16979 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16980 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16981 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16982 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16984 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16985 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16986 gameInfo.result = res;
16988 gameInfo.resultDetails = StrSave(buf);
16990 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16991 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16993 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16994 _(cps->which), cps->program);
16995 RemoveInputSource(cps->isr);
16997 /* [AS] Program is misbehaving badly... kill it */
16998 if( count == -2 ) {
16999 DestroyChildProcess( cps->pr, 9 );
17003 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
17008 if ((end_str = strchr(message, '\r')) != NULL)
17009 *end_str = NULLCHAR;
17010 if ((end_str = strchr(message, '\n')) != NULL)
17011 *end_str = NULLCHAR;
17013 if (appData.debugMode) {
17014 TimeMark now; int print = 1;
17015 char *quote = ""; char c; int i;
17017 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
17018 char start = message[0];
17019 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
17020 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
17021 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
17022 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
17023 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
17024 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
17025 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
17026 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
17027 sscanf(message, "hint: %c", &c)!=1 &&
17028 sscanf(message, "pong %c", &c)!=1 && start != '#') {
17029 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
17030 print = (appData.engineComments >= 2);
17032 message[0] = start; // restore original message
17036 fprintf(debugFP, "%ld <%-6s: %s%s\n",
17037 SubtractTimeMarks(&now, &programStartTime), cps->which,
17041 fprintf(serverFP, "%ld <%-6s: %s%s\n",
17042 SubtractTimeMarks(&now, &programStartTime), cps->which,
17044 message), fflush(serverFP);
17048 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
17049 if (appData.icsEngineAnalyze) {
17050 if (strstr(message, "whisper") != NULL ||
17051 strstr(message, "kibitz") != NULL ||
17052 strstr(message, "tellics") != NULL) return;
17055 HandleMachineMove(message, cps);
17060 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
17065 if( timeControl_2 > 0 ) {
17066 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
17067 tc = timeControl_2;
17070 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
17071 inc /= cps->timeOdds;
17072 st /= cps->timeOdds;
17074 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
17077 /* Set exact time per move, normally using st command */
17078 if (cps->stKludge) {
17079 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
17081 if (seconds == 0) {
17082 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
17084 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
17087 snprintf(buf, MSG_SIZ, "st %d\n", st);
17090 /* Set conventional or incremental time control, using level command */
17091 if (seconds == 0) {
17092 /* Note old gnuchess bug -- minutes:seconds used to not work.
17093 Fixed in later versions, but still avoid :seconds
17094 when seconds is 0. */
17095 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
17097 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
17098 seconds, inc/1000.);
17101 SendToProgram(buf, cps);
17103 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17104 /* Orthogonally, limit search to given depth */
17106 if (cps->sdKludge) {
17107 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17109 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17111 SendToProgram(buf, cps);
17114 if(cps->nps >= 0) { /* [HGM] nps */
17115 if(cps->supportsNPS == FALSE)
17116 cps->nps = -1; // don't use if engine explicitly says not supported!
17118 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17119 SendToProgram(buf, cps);
17124 ChessProgramState *
17126 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17128 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17129 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17135 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17137 char message[MSG_SIZ];
17140 /* Note: this routine must be called when the clocks are stopped
17141 or when they have *just* been set or switched; otherwise
17142 it will be off by the time since the current tick started.
17144 if (machineWhite) {
17145 time = whiteTimeRemaining / 10;
17146 otime = blackTimeRemaining / 10;
17148 time = blackTimeRemaining / 10;
17149 otime = whiteTimeRemaining / 10;
17151 /* [HGM] translate opponent's time by time-odds factor */
17152 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17154 if (time <= 0) time = 1;
17155 if (otime <= 0) otime = 1;
17157 snprintf(message, MSG_SIZ, "time %ld\n", time);
17158 SendToProgram(message, cps);
17160 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17161 SendToProgram(message, cps);
17165 EngineDefinedVariant (ChessProgramState *cps, int n)
17166 { // return name of n-th unknown variant that engine supports
17167 static char buf[MSG_SIZ];
17168 char *p, *s = cps->variants;
17169 if(!s) return NULL;
17170 do { // parse string from variants feature
17172 p = strchr(s, ',');
17173 if(p) *p = NULLCHAR;
17174 v = StringToVariant(s);
17175 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17176 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17177 if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17178 !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17179 !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17180 !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17181 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17184 if(n < 0) return buf;
17190 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17193 int len = strlen(name);
17196 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17198 sscanf(*p, "%d", &val);
17200 while (**p && **p != ' ')
17202 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17203 SendToProgram(buf, cps);
17210 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17213 int len = strlen(name);
17214 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17216 sscanf(*p, "%d", loc);
17217 while (**p && **p != ' ') (*p)++;
17218 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17219 SendToProgram(buf, cps);
17226 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17229 int len = strlen(name);
17230 if (strncmp((*p), name, len) == 0
17231 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17233 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
17234 FREE(*loc); *loc = malloc(len);
17235 strncpy(*loc, *p, len);
17236 sscanf(*p, "%[^\"]", *loc); // should always fit, because we allocated at least strlen(*p)
17237 while (**p && **p != '\"') (*p)++;
17238 if (**p == '\"') (*p)++;
17239 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17240 SendToProgram(buf, cps);
17247 ParseOption (Option *opt, ChessProgramState *cps)
17248 // [HGM] options: process the string that defines an engine option, and determine
17249 // name, type, default value, and allowed value range
17251 char *p, *q, buf[MSG_SIZ];
17252 int n, min = (-1)<<31, max = 1<<31, def;
17254 opt->target = &opt->value; // OK for spin/slider and checkbox
17255 if(p = strstr(opt->name, " -spin ")) {
17256 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17257 if(max < min) max = min; // enforce consistency
17258 if(def < min) def = min;
17259 if(def > max) def = max;
17264 } else if((p = strstr(opt->name, " -slider "))) {
17265 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17266 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17267 if(max < min) max = min; // enforce consistency
17268 if(def < min) def = min;
17269 if(def > max) def = max;
17273 opt->type = Spin; // Slider;
17274 } else if((p = strstr(opt->name, " -string "))) {
17275 opt->textValue = p+9;
17276 opt->type = TextBox;
17277 opt->target = &opt->textValue;
17278 } else if((p = strstr(opt->name, " -file "))) {
17279 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17280 opt->target = opt->textValue = p+7;
17281 opt->type = FileName; // FileName;
17282 opt->target = &opt->textValue;
17283 } else if((p = strstr(opt->name, " -path "))) {
17284 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17285 opt->target = opt->textValue = p+7;
17286 opt->type = PathName; // PathName;
17287 opt->target = &opt->textValue;
17288 } else if(p = strstr(opt->name, " -check ")) {
17289 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17290 opt->value = (def != 0);
17291 opt->type = CheckBox;
17292 } else if(p = strstr(opt->name, " -combo ")) {
17293 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17294 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17295 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17296 opt->value = n = 0;
17297 while(q = StrStr(q, " /// ")) {
17298 n++; *q = 0; // count choices, and null-terminate each of them
17300 if(*q == '*') { // remember default, which is marked with * prefix
17304 cps->comboList[cps->comboCnt++] = q;
17306 cps->comboList[cps->comboCnt++] = NULL;
17308 opt->type = ComboBox;
17309 } else if(p = strstr(opt->name, " -button")) {
17310 opt->type = Button;
17311 } else if(p = strstr(opt->name, " -save")) {
17312 opt->type = SaveButton;
17313 } else return FALSE;
17314 *p = 0; // terminate option name
17315 // now look if the command-line options define a setting for this engine option.
17316 if(cps->optionSettings && cps->optionSettings[0])
17317 p = strstr(cps->optionSettings, opt->name); else p = NULL;
17318 if(p && (p == cps->optionSettings || p[-1] == ',')) {
17319 snprintf(buf, MSG_SIZ, "option %s", p);
17320 if(p = strstr(buf, ",")) *p = 0;
17321 if(q = strchr(buf, '=')) switch(opt->type) {
17323 for(n=0; n<opt->max; n++)
17324 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17327 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17331 opt->value = atoi(q+1);
17336 SendToProgram(buf, cps);
17338 *(int*) (opt->name + MSG_SIZ - 104) = opt->value; // hide default values somewhere
17339 if(opt->target == &opt->textValue) strncpy(opt->name + MSG_SIZ - 100, opt->textValue, 99);
17344 FeatureDone (ChessProgramState *cps, int val)
17346 DelayedEventCallback cb = GetDelayedEvent();
17347 if ((cb == InitBackEnd3 && cps == &first) ||
17348 (cb == SettingsMenuIfReady && cps == &second) ||
17349 (cb == LoadEngine) ||
17350 (cb == TwoMachinesEventIfReady)) {
17351 CancelDelayedEvent();
17352 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17353 } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17354 cps->initDone = val;
17355 if(val) cps->reload = FALSE, RefreshSettingsDialog(cps, val);
17358 /* Parse feature command from engine */
17360 ParseFeatures (char *args, ChessProgramState *cps)
17368 while (*p == ' ') p++;
17369 if (*p == NULLCHAR) return;
17371 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17372 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17373 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17374 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17375 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17376 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17377 if (BoolFeature(&p, "reuse", &val, cps)) {
17378 /* Engine can disable reuse, but can't enable it if user said no */
17379 if (!val) cps->reuse = FALSE;
17382 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17383 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17384 if (gameMode == TwoMachinesPlay) {
17385 DisplayTwoMachinesTitle();
17391 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17392 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17393 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17394 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17395 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17396 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17397 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17398 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17399 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17400 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17401 if (IntFeature(&p, "done", &val, cps)) {
17402 FeatureDone(cps, val);
17405 /* Added by Tord: */
17406 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17407 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17408 /* End of additions by Tord */
17410 /* [HGM] added features: */
17411 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17412 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17413 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17414 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17415 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17416 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17417 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17418 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17419 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17420 if(cps->nrOptions == 0) { ASSIGN(cps->option[0].name, _("Make Persistent -save")); ParseOption(&(cps->option[cps->nrOptions++]), cps); }
17421 FREE(cps->option[cps->nrOptions].name);
17422 cps->option[cps->nrOptions].name = q; q = NULL;
17423 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17424 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17425 SendToProgram(buf, cps);
17428 if(cps->nrOptions >= MAX_OPTIONS) {
17430 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17431 DisplayError(buf, 0);
17435 /* End of additions by HGM */
17437 /* unknown feature: complain and skip */
17439 while (*q && *q != '=') q++;
17440 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17441 SendToProgram(buf, cps);
17447 while (*p && *p != '\"') p++;
17448 if (*p == '\"') p++;
17450 while (*p && *p != ' ') p++;
17458 PeriodicUpdatesEvent (int newState)
17460 if (newState == appData.periodicUpdates)
17463 appData.periodicUpdates=newState;
17465 /* Display type changes, so update it now */
17466 // DisplayAnalysis();
17468 /* Get the ball rolling again... */
17470 AnalysisPeriodicEvent(1);
17471 StartAnalysisClock();
17476 PonderNextMoveEvent (int newState)
17478 if (newState == appData.ponderNextMove) return;
17479 if (gameMode == EditPosition) EditPositionDone(TRUE);
17481 SendToProgram("hard\n", &first);
17482 if (gameMode == TwoMachinesPlay) {
17483 SendToProgram("hard\n", &second);
17486 SendToProgram("easy\n", &first);
17487 thinkOutput[0] = NULLCHAR;
17488 if (gameMode == TwoMachinesPlay) {
17489 SendToProgram("easy\n", &second);
17492 appData.ponderNextMove = newState;
17496 NewSettingEvent (int option, int *feature, char *command, int value)
17500 if (gameMode == EditPosition) EditPositionDone(TRUE);
17501 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17502 if(feature == NULL || *feature) SendToProgram(buf, &first);
17503 if (gameMode == TwoMachinesPlay) {
17504 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17509 ShowThinkingEvent ()
17510 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17512 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17513 int newState = appData.showThinking
17514 // [HGM] thinking: other features now need thinking output as well
17515 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17517 if (oldState == newState) return;
17518 oldState = newState;
17519 if (gameMode == EditPosition) EditPositionDone(TRUE);
17521 SendToProgram("post\n", &first);
17522 if (gameMode == TwoMachinesPlay) {
17523 SendToProgram("post\n", &second);
17526 SendToProgram("nopost\n", &first);
17527 thinkOutput[0] = NULLCHAR;
17528 if (gameMode == TwoMachinesPlay) {
17529 SendToProgram("nopost\n", &second);
17532 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17536 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17538 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17539 if (pr == NoProc) return;
17540 AskQuestion(title, question, replyPrefix, pr);
17544 TypeInEvent (char firstChar)
17546 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17547 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17548 gameMode == AnalyzeMode || gameMode == EditGame ||
17549 gameMode == EditPosition || gameMode == IcsExamining ||
17550 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17551 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17552 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17553 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
17554 gameMode == Training) PopUpMoveDialog(firstChar);
17558 TypeInDoneEvent (char *move)
17561 int n, fromX, fromY, toX, toY;
17563 ChessMove moveType;
17566 if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17567 EditPositionPasteFEN(move);
17570 // [HGM] movenum: allow move number to be typed in any mode
17571 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17575 // undocumented kludge: allow command-line option to be typed in!
17576 // (potentially fatal, and does not implement the effect of the option.)
17577 // should only be used for options that are values on which future decisions will be made,
17578 // and definitely not on options that would be used during initialization.
17579 if(strstr(move, "!!! -") == move) {
17580 ParseArgsFromString(move+4);
17584 if (gameMode != EditGame && currentMove != forwardMostMove &&
17585 gameMode != Training) {
17586 DisplayMoveError(_("Displayed move is not current"));
17588 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17589 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17590 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17591 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17592 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17593 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17595 DisplayMoveError(_("Could not parse move"));
17601 DisplayMove (int moveNumber)
17603 char message[MSG_SIZ];
17605 char cpThinkOutput[MSG_SIZ];
17607 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17609 if (moveNumber == forwardMostMove - 1 ||
17610 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17612 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17614 if (strchr(cpThinkOutput, '\n')) {
17615 *strchr(cpThinkOutput, '\n') = NULLCHAR;
17618 *cpThinkOutput = NULLCHAR;
17621 /* [AS] Hide thinking from human user */
17622 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17623 *cpThinkOutput = NULLCHAR;
17624 if( thinkOutput[0] != NULLCHAR ) {
17627 for( i=0; i<=hiddenThinkOutputState; i++ ) {
17628 cpThinkOutput[i] = '.';
17630 cpThinkOutput[i] = NULLCHAR;
17631 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17635 if (moveNumber == forwardMostMove - 1 &&
17636 gameInfo.resultDetails != NULL) {
17637 if (gameInfo.resultDetails[0] == NULLCHAR) {
17638 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17640 snprintf(res, MSG_SIZ, " {%s} %s",
17641 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17647 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17648 DisplayMessage(res, cpThinkOutput);
17650 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17651 WhiteOnMove(moveNumber) ? " " : ".. ",
17652 parseList[moveNumber], res);
17653 DisplayMessage(message, cpThinkOutput);
17658 DisplayComment (int moveNumber, char *text)
17660 char title[MSG_SIZ];
17662 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17663 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17665 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17666 WhiteOnMove(moveNumber) ? " " : ".. ",
17667 parseList[moveNumber]);
17669 if (text != NULL && (appData.autoDisplayComment || commentUp))
17670 CommentPopUp(title, text);
17673 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17674 * might be busy thinking or pondering. It can be omitted if your
17675 * gnuchess is configured to stop thinking immediately on any user
17676 * input. However, that gnuchess feature depends on the FIONREAD
17677 * ioctl, which does not work properly on some flavors of Unix.
17680 Attention (ChessProgramState *cps)
17683 if (!cps->useSigint) return;
17684 if (appData.noChessProgram || (cps->pr == NoProc)) return;
17685 switch (gameMode) {
17686 case MachinePlaysWhite:
17687 case MachinePlaysBlack:
17688 case TwoMachinesPlay:
17689 case IcsPlayingWhite:
17690 case IcsPlayingBlack:
17693 /* Skip if we know it isn't thinking */
17694 if (!cps->maybeThinking) return;
17695 if (appData.debugMode)
17696 fprintf(debugFP, "Interrupting %s\n", cps->which);
17697 InterruptChildProcess(cps->pr);
17698 cps->maybeThinking = FALSE;
17703 #endif /*ATTENTION*/
17709 if (whiteTimeRemaining <= 0) {
17712 if (appData.icsActive) {
17713 if (appData.autoCallFlag &&
17714 gameMode == IcsPlayingBlack && !blackFlag) {
17715 SendToICS(ics_prefix);
17716 SendToICS("flag\n");
17720 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17722 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17723 if (appData.autoCallFlag) {
17724 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17731 if (blackTimeRemaining <= 0) {
17734 if (appData.icsActive) {
17735 if (appData.autoCallFlag &&
17736 gameMode == IcsPlayingWhite && !whiteFlag) {
17737 SendToICS(ics_prefix);
17738 SendToICS("flag\n");
17742 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17744 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17745 if (appData.autoCallFlag) {
17746 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17757 CheckTimeControl ()
17759 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17760 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17763 * add time to clocks when time control is achieved ([HGM] now also used for increment)
17765 if ( !WhiteOnMove(forwardMostMove) ) {
17766 /* White made time control */
17767 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17768 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17769 /* [HGM] time odds: correct new time quota for time odds! */
17770 / WhitePlayer()->timeOdds;
17771 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17773 lastBlack -= blackTimeRemaining;
17774 /* Black made time control */
17775 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17776 / WhitePlayer()->other->timeOdds;
17777 lastWhite = whiteTimeRemaining;
17782 DisplayBothClocks ()
17784 int wom = gameMode == EditPosition ?
17785 !blackPlaysFirst : WhiteOnMove(currentMove);
17786 DisplayWhiteClock(whiteTimeRemaining, wom);
17787 DisplayBlackClock(blackTimeRemaining, !wom);
17791 /* Timekeeping seems to be a portability nightmare. I think everyone
17792 has ftime(), but I'm really not sure, so I'm including some ifdefs
17793 to use other calls if you don't. Clocks will be less accurate if
17794 you have neither ftime nor gettimeofday.
17797 /* VS 2008 requires the #include outside of the function */
17798 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17799 #include <sys/timeb.h>
17802 /* Get the current time as a TimeMark */
17804 GetTimeMark (TimeMark *tm)
17806 #if HAVE_GETTIMEOFDAY
17808 struct timeval timeVal;
17809 struct timezone timeZone;
17811 gettimeofday(&timeVal, &timeZone);
17812 tm->sec = (long) timeVal.tv_sec;
17813 tm->ms = (int) (timeVal.tv_usec / 1000L);
17815 #else /*!HAVE_GETTIMEOFDAY*/
17818 // include <sys/timeb.h> / moved to just above start of function
17819 struct timeb timeB;
17822 tm->sec = (long) timeB.time;
17823 tm->ms = (int) timeB.millitm;
17825 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17826 tm->sec = (long) time(NULL);
17832 /* Return the difference in milliseconds between two
17833 time marks. We assume the difference will fit in a long!
17836 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17838 return 1000L*(tm2->sec - tm1->sec) +
17839 (long) (tm2->ms - tm1->ms);
17844 * Code to manage the game clocks.
17846 * In tournament play, black starts the clock and then white makes a move.
17847 * We give the human user a slight advantage if he is playing white---the
17848 * clocks don't run until he makes his first move, so it takes zero time.
17849 * Also, we don't account for network lag, so we could get out of sync
17850 * with GNU Chess's clock -- but then, referees are always right.
17853 static TimeMark tickStartTM;
17854 static long intendedTickLength;
17857 NextTickLength (long timeRemaining)
17859 long nominalTickLength, nextTickLength;
17861 if (timeRemaining > 0L && timeRemaining <= 10000L)
17862 nominalTickLength = 100L;
17864 nominalTickLength = 1000L;
17865 nextTickLength = timeRemaining % nominalTickLength;
17866 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17868 return nextTickLength;
17871 /* Adjust clock one minute up or down */
17873 AdjustClock (Boolean which, int dir)
17875 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17876 if(which) blackTimeRemaining += 60000*dir;
17877 else whiteTimeRemaining += 60000*dir;
17878 DisplayBothClocks();
17879 adjustedClock = TRUE;
17882 /* Stop clocks and reset to a fresh time control */
17886 (void) StopClockTimer();
17887 if (appData.icsActive) {
17888 whiteTimeRemaining = blackTimeRemaining = 0;
17889 } else if (searchTime) {
17890 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17891 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17892 } else { /* [HGM] correct new time quote for time odds */
17893 whiteTC = blackTC = fullTimeControlString;
17894 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17895 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17897 if (whiteFlag || blackFlag) {
17899 whiteFlag = blackFlag = FALSE;
17901 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17902 DisplayBothClocks();
17903 adjustedClock = FALSE;
17906 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17908 static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness...
17910 /* Decrement running clock by amount of time that has passed */
17915 long lastTickLength, fudge;
17918 if (!appData.clockMode) return;
17919 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17923 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17925 /* Fudge if we woke up a little too soon */
17926 fudge = intendedTickLength - lastTickLength;
17927 if (fudge < 0 || fudge > FUDGE) fudge = 0;
17929 if (WhiteOnMove(forwardMostMove)) {
17930 if(whiteNPS >= 0) lastTickLength = 0;
17931 tRemaining = whiteTimeRemaining -= lastTickLength;
17932 if( tRemaining < 0 && !appData.icsActive) {
17933 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17934 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17935 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17936 lastWhite= tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17939 if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining;
17940 DisplayWhiteClock(whiteTimeRemaining - fudge,
17941 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17944 if(blackNPS >= 0) lastTickLength = 0;
17945 tRemaining = blackTimeRemaining -= lastTickLength;
17946 if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17947 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17949 blackStartMove = forwardMostMove;
17950 lastBlack = tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17953 if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining;
17954 DisplayBlackClock(blackTimeRemaining - fudge,
17955 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17958 if (CheckFlags()) return;
17960 if(twoBoards) { // count down secondary board's clocks as well
17961 activePartnerTime -= lastTickLength;
17963 if(activePartner == 'W')
17964 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17966 DisplayBlackClock(activePartnerTime, TRUE);
17971 intendedTickLength = NextTickLength( tRemaining - fudge) + fudge;
17972 StartClockTimer(intendedTickLength);
17974 /* if the time remaining has fallen below the alarm threshold, sound the
17975 * alarm. if the alarm has sounded and (due to a takeback or time control
17976 * with increment) the time remaining has increased to a level above the
17977 * threshold, reset the alarm so it can sound again.
17980 if (appData.icsActive && appData.icsAlarm) {
17982 /* make sure we are dealing with the user's clock */
17983 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17984 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17987 if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) {
17988 alarmSounded = FALSE;
17989 } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) {
17991 alarmSounded = TRUE;
17997 /* A player has just moved, so stop the previously running
17998 clock and (if in clock mode) start the other one.
17999 We redisplay both clocks in case we're in ICS mode, because
18000 ICS gives us an update to both clocks after every move.
18001 Note that this routine is called *after* forwardMostMove
18002 is updated, so the last fractional tick must be subtracted
18003 from the color that is *not* on move now.
18006 SwitchClocks (int newMoveNr)
18008 long lastTickLength;
18010 int flagged = FALSE;
18014 if (StopClockTimer() && appData.clockMode) {
18015 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18016 if (!WhiteOnMove(forwardMostMove)) {
18017 if(blackNPS >= 0) lastTickLength = 0;
18018 blackTimeRemaining -= lastTickLength;
18019 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18020 // if(pvInfoList[forwardMostMove].time == -1)
18021 pvInfoList[forwardMostMove].time = // use GUI time
18022 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
18024 if(whiteNPS >= 0) lastTickLength = 0;
18025 whiteTimeRemaining -= lastTickLength;
18026 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18027 // if(pvInfoList[forwardMostMove].time == -1)
18028 pvInfoList[forwardMostMove].time =
18029 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
18031 flagged = CheckFlags();
18033 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
18034 CheckTimeControl();
18036 if (flagged || !appData.clockMode) return;
18038 switch (gameMode) {
18039 case MachinePlaysBlack:
18040 case MachinePlaysWhite:
18041 case BeginningOfGame:
18042 if (pausing) return;
18046 case PlayFromGameFile:
18054 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
18055 if(WhiteOnMove(forwardMostMove))
18056 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
18057 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
18061 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18062 whiteTimeRemaining : blackTimeRemaining);
18063 StartClockTimer(intendedTickLength);
18067 /* Stop both clocks */
18071 long lastTickLength;
18074 if (!StopClockTimer()) return;
18075 if (!appData.clockMode) return;
18079 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18080 if (WhiteOnMove(forwardMostMove)) {
18081 if(whiteNPS >= 0) lastTickLength = 0;
18082 whiteTimeRemaining -= lastTickLength;
18083 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
18085 if(blackNPS >= 0) lastTickLength = 0;
18086 blackTimeRemaining -= lastTickLength;
18087 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
18092 /* Start clock of player on move. Time may have been reset, so
18093 if clock is already running, stop and restart it. */
18097 (void) StopClockTimer(); /* in case it was running already */
18098 DisplayBothClocks();
18099 if (CheckFlags()) return;
18101 if (!appData.clockMode) return;
18102 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
18104 GetTimeMark(&tickStartTM);
18105 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18106 whiteTimeRemaining : blackTimeRemaining);
18108 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
18109 whiteNPS = blackNPS = -1;
18110 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
18111 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
18112 whiteNPS = first.nps;
18113 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18114 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18115 blackNPS = first.nps;
18116 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18117 whiteNPS = second.nps;
18118 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18119 blackNPS = second.nps;
18120 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18122 StartClockTimer(intendedTickLength);
18126 TimeString (long ms)
18128 long second, minute, hour, day;
18130 static char buf[40], moveTime[8];
18132 if (ms > 0 && ms <= 9900) {
18133 /* convert milliseconds to tenths, rounding up */
18134 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18136 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18140 /* convert milliseconds to seconds, rounding up */
18141 /* use floating point to avoid strangeness of integer division
18142 with negative dividends on many machines */
18143 second = (long) floor(((double) (ms + 999L)) / 1000.0);
18150 day = second / (60 * 60 * 24);
18151 second = second % (60 * 60 * 24);
18152 hour = second / (60 * 60);
18153 second = second % (60 * 60);
18154 minute = second / 60;
18155 second = second % 60;
18157 if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time
18158 else *moveTime = NULLCHAR;
18161 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ",
18162 sign, day, hour, minute, second, moveTime);
18164 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime);
18166 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime);
18173 * This is necessary because some C libraries aren't ANSI C compliant yet.
18176 StrStr (char *string, char *match)
18180 length = strlen(match);
18182 for (i = strlen(string) - length; i >= 0; i--, string++)
18183 if (!strncmp(match, string, length))
18190 StrCaseStr (char *string, char *match)
18194 length = strlen(match);
18196 for (i = strlen(string) - length; i >= 0; i--, string++) {
18197 for (j = 0; j < length; j++) {
18198 if (ToLower(match[j]) != ToLower(string[j]))
18201 if (j == length) return string;
18209 StrCaseCmp (char *s1, char *s2)
18214 c1 = ToLower(*s1++);
18215 c2 = ToLower(*s2++);
18216 if (c1 > c2) return 1;
18217 if (c1 < c2) return -1;
18218 if (c1 == NULLCHAR) return 0;
18226 return isupper(c) ? tolower(c) : c;
18233 return islower(c) ? toupper(c) : c;
18235 #endif /* !_amigados */
18242 if ((ret = (char *) malloc(strlen(s) + 1)))
18244 safeStrCpy(ret, s, strlen(s)+1);
18250 StrSavePtr (char *s, char **savePtr)
18255 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18256 safeStrCpy(*savePtr, s, strlen(s)+1);
18268 clock = time((time_t *)NULL);
18269 tm = localtime(&clock);
18270 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18271 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18272 return StrSave(buf);
18277 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18279 int i, j, fromX, fromY, toX, toY;
18280 int whiteToPlay, haveRights = nrCastlingRights;
18286 whiteToPlay = (gameMode == EditPosition) ?
18287 !blackPlaysFirst : (move % 2 == 0);
18290 /* Piece placement data */
18291 for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18292 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18294 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18295 if (boards[move][i][j] == EmptySquare) {
18297 } else { ChessSquare piece = boards[move][i][j];
18298 if (emptycount > 0) {
18299 if(emptycount<10) /* [HGM] can be >= 10 */
18300 *p++ = '0' + emptycount;
18301 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18304 if(PieceToChar(piece) == '+') {
18305 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18307 piece = (ChessSquare)(CHUDEMOTED(piece));
18309 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18310 if(*p = PieceSuffix(piece)) p++;
18312 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18313 p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18318 if (emptycount > 0) {
18319 if(emptycount<10) /* [HGM] can be >= 10 */
18320 *p++ = '0' + emptycount;
18321 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18328 /* [HGM] print Crazyhouse or Shogi holdings */
18329 if( gameInfo.holdingsWidth ) {
18330 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18332 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18333 piece = boards[move][i][BOARD_WIDTH-1];
18334 if( piece != EmptySquare )
18335 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18336 *p++ = PieceToChar(piece);
18338 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18339 piece = boards[move][BOARD_HEIGHT-i-1][0];
18340 if( piece != EmptySquare )
18341 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18342 *p++ = PieceToChar(piece);
18345 if( q == p ) *p++ = '-';
18351 *p++ = whiteToPlay ? 'w' : 'b';
18354 if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18355 haveRights = 0; q = p;
18356 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18357 piece = boards[move][0][i];
18358 if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18359 if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18362 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18363 piece = boards[move][BOARD_HEIGHT-1][i];
18364 if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18365 if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18368 if(p == q) *p++ = '-';
18372 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18375 if(q != overrideCastling+1) p[-1] = ' '; else --p;
18378 int handW=0, handB=0;
18379 if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18380 for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18381 for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18384 if(appData.fischerCastling) {
18385 if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18386 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18387 if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18389 /* [HGM] write directly from rights */
18390 if(boards[move][CASTLING][2] != NoRights &&
18391 boards[move][CASTLING][0] != NoRights )
18392 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18393 if(boards[move][CASTLING][2] != NoRights &&
18394 boards[move][CASTLING][1] != NoRights )
18395 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18398 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18399 if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18401 if(boards[move][CASTLING][5] != NoRights &&
18402 boards[move][CASTLING][3] != NoRights )
18403 *p++ = boards[move][CASTLING][3] + AAA;
18404 if(boards[move][CASTLING][5] != NoRights &&
18405 boards[move][CASTLING][4] != NoRights )
18406 *p++ = boards[move][CASTLING][4] + AAA;
18410 /* [HGM] write true castling rights */
18411 if( nrCastlingRights == 6 ) {
18413 if(boards[move][CASTLING][0] != NoRights &&
18414 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
18415 q = (boards[move][CASTLING][1] != NoRights &&
18416 boards[move][CASTLING][2] != NoRights );
18417 if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18418 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18419 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18420 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18424 if(boards[move][CASTLING][3] != NoRights &&
18425 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
18426 q = (boards[move][CASTLING][4] != NoRights &&
18427 boards[move][CASTLING][5] != NoRights );
18429 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18430 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18431 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18436 if (q == p) *p++ = '-'; /* No castling rights */
18440 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
18441 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18442 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18443 /* En passant target square */
18444 if (move > backwardMostMove) {
18445 fromX = moveList[move - 1][0] - AAA;
18446 fromY = moveList[move - 1][1] - ONE;
18447 toX = moveList[move - 1][2] - AAA;
18448 toY = moveList[move - 1][3] - ONE;
18449 if ((whiteToPlay ? toY < fromY - 1 : toY > fromY + 1) &&
18450 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) ) {
18451 /* 2-square pawn move just happened */
18452 *p++ = (3*toX + 5*fromX + 4)/8 + AAA;
18453 *p++ = (3*toY + 5*fromY + 4)/8 + ONE;
18454 if(gameInfo.variant == VariantBerolina) {
18461 } else if(move == backwardMostMove) {
18462 // [HGM] perhaps we should always do it like this, and forget the above?
18463 if((signed char)boards[move][EP_STATUS] >= 0) {
18464 *p++ = boards[move][EP_STATUS] + AAA;
18465 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18476 i = boards[move][CHECK_COUNT];
18478 sprintf(p, "%d+%d ", i&255, i>>8);
18483 { int i = 0, j=move;
18485 /* [HGM] find reversible plies */
18486 if (appData.debugMode) { int k;
18487 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18488 for(k=backwardMostMove; k<=forwardMostMove; k++)
18489 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18493 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18494 if( j == backwardMostMove ) i += initialRulePlies;
18495 sprintf(p, "%d ", i);
18496 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18498 /* Fullmove number */
18499 sprintf(p, "%d", (move / 2) + 1);
18500 } else *--p = NULLCHAR;
18502 return StrSave(buf);
18506 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18508 int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18510 int emptycount, virgin[BOARD_FILES];
18511 ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18515 for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) board[BOARD_HEIGHT-i][j] = DarkSquare;
18517 /* Piece placement data */
18518 for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18521 if (*p == '/' || *p == ' ' || *p == '[' ) {
18523 emptycount = gameInfo.boardWidth - j;
18524 while (emptycount--)
18525 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18526 if (*p == '/') p++;
18527 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18528 for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18529 for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18531 appData.NrRanks = gameInfo.boardHeight - i; i=0;
18534 #if(BOARD_FILES >= 10)*0
18535 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18536 p++; emptycount=10;
18537 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18538 while (emptycount--)
18539 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18541 } else if (*p == '*') {
18542 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18543 } else if (isdigit(*p)) {
18544 emptycount = *p++ - '0';
18545 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18546 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18547 while (emptycount--)
18548 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18549 } else if (*p == '<') {
18550 if(i == BOARD_HEIGHT-1) shuffle = 1;
18551 else if (i != 0 || !shuffle) return FALSE;
18553 } else if (shuffle && *p == '>') {
18554 p++; // for now ignore closing shuffle range, and assume rank-end
18555 } else if (*p == '?') {
18556 if (j >= gameInfo.boardWidth) return FALSE;
18557 if (i != 0 && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18558 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18559 } else if (*p == '+' || isalpha(*p)) {
18560 char *q, *s = SUFFIXES;
18561 if (j >= gameInfo.boardWidth) return FALSE;
18564 if(q = strchr(s, p[1])) p++;
18565 piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18566 if(piece == EmptySquare) return FALSE; /* unknown piece */
18567 piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18568 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18571 if(q = strchr(s, *p)) p++;
18572 piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18575 if(piece==EmptySquare) return FALSE; /* unknown piece */
18576 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18577 piece = (ChessSquare) (PROMOTED(piece));
18578 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18581 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18582 if(piece == king) wKingRank = i;
18583 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18589 while (*p == '/' || *p == ' ') p++;
18591 if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18593 /* [HGM] by default clear Crazyhouse holdings, if present */
18594 if(gameInfo.holdingsWidth) {
18595 for(i=0; i<BOARD_HEIGHT; i++) {
18596 board[i][0] = EmptySquare; /* black holdings */
18597 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18598 board[i][1] = (ChessSquare) 0; /* black counts */
18599 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18603 /* [HGM] look for Crazyhouse holdings here */
18604 while(*p==' ') p++;
18605 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18606 int swap=0, wcnt=0, bcnt=0;
18608 if(*p == '<') swap++, p++;
18609 if(*p == '-' ) p++; /* empty holdings */ else {
18610 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18611 /* if we would allow FEN reading to set board size, we would */
18612 /* have to add holdings and shift the board read so far here */
18613 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18615 if((int) piece >= (int) BlackPawn ) {
18616 i = (int)piece - (int)BlackPawn;
18617 i = PieceToNumber((ChessSquare)i);
18618 if( i >= gameInfo.holdingsSize ) return FALSE;
18619 board[handSize-1-i][0] = piece; /* black holdings */
18620 board[handSize-1-i][1]++; /* black counts */
18623 i = (int)piece - (int)WhitePawn;
18624 i = PieceToNumber((ChessSquare)i);
18625 if( i >= gameInfo.holdingsSize ) return FALSE;
18626 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
18627 board[i][BOARD_WIDTH-2]++; /* black holdings */
18631 if(subst) { // substitute back-rank question marks by holdings pieces
18632 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18633 int k, m, n = bcnt + 1;
18634 if(board[0][j] == ClearBoard) {
18635 if(!wcnt) return FALSE;
18637 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18638 board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18639 if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18643 if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18644 if(!bcnt) return FALSE;
18645 if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18646 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18647 board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18648 if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18659 if(subst) return FALSE; // substitution requested, but no holdings
18661 while(*p == ' ') p++;
18665 if(appData.colorNickNames) {
18666 if( c == appData.colorNickNames[0] ) c = 'w'; else
18667 if( c == appData.colorNickNames[1] ) c = 'b';
18671 *blackPlaysFirst = FALSE;
18674 *blackPlaysFirst = TRUE;
18680 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18681 /* return the extra info in global variiables */
18683 while(*p==' ') p++;
18685 if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18686 if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18687 if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18690 /* set defaults in case FEN is incomplete */
18691 board[EP_STATUS] = EP_UNKNOWN;
18692 board[TOUCHED_W] = board[TOUCHED_B] = 0;
18693 for(i=0; i<nrCastlingRights; i++ ) {
18694 board[CASTLING][i] =
18695 appData.fischerCastling ? NoRights : initialRights[i];
18696 } /* assume possible unless obviously impossible */
18697 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18698 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18699 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18700 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18701 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18702 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18703 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18704 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18707 if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18710 while(isalpha(*p)) {
18711 if(isupper(*p)) w |= 1 << (*p++ - 'A');
18712 if(islower(*p)) b |= 1 << (*p++ - 'a');
18716 board[TOUCHED_W] = ~w;
18717 board[TOUCHED_B] = ~b;
18718 while(*p == ' ') p++;
18722 if(nrCastlingRights) {
18724 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18725 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18726 /* castling indicator present, so default becomes no castlings */
18727 for(i=0; i<nrCastlingRights; i++ ) {
18728 board[CASTLING][i] = NoRights;
18731 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18732 (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18733 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18734 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
18735 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18737 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18738 if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18739 if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18741 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18742 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18743 if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18744 && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18745 if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18746 && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18749 for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18750 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18751 board[CASTLING][2] = whiteKingFile;
18752 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18753 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18754 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18757 for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18758 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18759 board[CASTLING][2] = whiteKingFile;
18760 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18761 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18762 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18765 for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18766 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18767 board[CASTLING][5] = blackKingFile;
18768 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18769 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18770 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18773 for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18774 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18775 board[CASTLING][5] = blackKingFile;
18776 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18777 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18778 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18781 default: /* FRC castlings */
18782 if(c >= 'a') { /* black rights */
18783 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18784 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18785 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18786 if(i == BOARD_RGHT) break;
18787 board[CASTLING][5] = i;
18789 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
18790 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
18792 board[CASTLING][3] = c;
18794 board[CASTLING][4] = c;
18795 } else { /* white rights */
18796 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18797 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18798 if(board[0][i] == WhiteKing) break;
18799 if(i == BOARD_RGHT) break;
18800 board[CASTLING][2] = i;
18801 c -= AAA - 'a' + 'A';
18802 if(board[0][c] >= WhiteKing) break;
18804 board[CASTLING][0] = c;
18806 board[CASTLING][1] = c;
18810 for(i=0; i<nrCastlingRights; i++)
18811 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18812 if(gameInfo.variant == VariantSChess)
18813 for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18814 if(fischer && shuffle) appData.fischerCastling = TRUE;
18815 if (appData.debugMode) {
18816 fprintf(debugFP, "FEN castling rights:");
18817 for(i=0; i<nrCastlingRights; i++)
18818 fprintf(debugFP, " %d", board[CASTLING][i]);
18819 fprintf(debugFP, "\n");
18822 while(*p==' ') p++;
18825 if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18827 /* read e.p. field in games that know e.p. capture */
18828 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
18829 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18830 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18832 p++; board[EP_STATUS] = EP_NONE;
18834 int d, r, c = *p - AAA;
18836 if(c >= BOARD_LEFT && c < BOARD_RGHT) {
18838 board[EP_STATUS] = board[EP_FILE] = c; r = 0;
18839 if(*p >= '0' && *p <='9') r = board[EP_RANK] = *p++ - ONE;
18840 d = (r < BOARD_HEIGHT << 1 ? 1 : -1); // assume double-push (P next to e.p. square nearer center)
18841 if(board[r+d][c] == EmptySquare) d *= 2; // but if no Pawn there, triple push
18842 board[LAST_TO] = 256*(r + d) + c;
18844 if(c >= BOARD_LEFT && c < BOARD_RGHT) { // mover explicitly mentioned
18845 if(*p >= '0' && *p <='9') r = board[EP_RANK] = *p++ - ONE;
18846 board[LAST_TO] = 256*r + c;
18847 if(!(board[EP_RANK]-r & 1)) board[EP_RANK] |= 128;
18853 while(*p == ' ') p++;
18855 board[CHECK_COUNT] = 0; // [HGM] 3check: check-count field
18856 if(sscanf(p, "%d+%d", &i, &j) == 2) {
18857 board[CHECK_COUNT] = i + 256*j;
18858 while(*p && *p != ' ') p++;
18861 c = sscanf(p, "%d%*d +%d+%d", &i, &j, &k);
18863 FENrulePlies = i; /* 50-move ply counter */
18864 /* (The move number is still ignored) */
18865 if(c == 3 && !board[CHECK_COUNT]) board[CHECK_COUNT] = (3 - j) + 256*(3 - k); // SCIDB-style check count
18872 EditPositionPasteFEN (char *fen)
18875 Board initial_position;
18877 if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18878 DisplayError(_("Bad FEN position in clipboard"), 0);
18881 int savedBlackPlaysFirst = blackPlaysFirst;
18882 EditPositionEvent();
18883 blackPlaysFirst = savedBlackPlaysFirst;
18884 CopyBoard(boards[0], initial_position);
18885 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18886 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18887 DisplayBothClocks();
18888 DrawPosition(FALSE, boards[currentMove]);
18893 static char cseq[12] = "\\ ";
18896 set_cont_sequence (char *new_seq)
18901 // handle bad attempts to set the sequence
18903 return 0; // acceptable error - no debug
18905 len = strlen(new_seq);
18906 ret = (len > 0) && (len < sizeof(cseq));
18908 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18909 else if (appData.debugMode)
18910 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18915 reformat a source message so words don't cross the width boundary. internal
18916 newlines are not removed. returns the wrapped size (no null character unless
18917 included in source message). If dest is NULL, only calculate the size required
18918 for the dest buffer. lp argument indicats line position upon entry, and it's
18919 passed back upon exit.
18922 wrap (char *dest, char *src, int count, int width, int *lp)
18924 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18926 cseq_len = strlen(cseq);
18927 old_line = line = *lp;
18928 ansi = len = clen = 0;
18930 for (i=0; i < count; i++)
18932 if (src[i] == '\033')
18935 // if we hit the width, back up
18936 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18938 // store i & len in case the word is too long
18939 old_i = i, old_len = len;
18941 // find the end of the last word
18942 while (i && src[i] != ' ' && src[i] != '\n')
18948 // word too long? restore i & len before splitting it
18949 if ((old_i-i+clen) >= width)
18956 if (i && src[i-1] == ' ')
18959 if (src[i] != ' ' && src[i] != '\n')
18966 // now append the newline and continuation sequence
18971 strncpy(dest+len, cseq, cseq_len);
18979 dest[len] = src[i];
18983 if (src[i] == '\n')
18988 if (dest && appData.debugMode)
18990 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18991 count, width, line, len, *lp);
18992 show_bytes(debugFP, src, count);
18993 fprintf(debugFP, "\ndest: ");
18994 show_bytes(debugFP, dest, len);
18995 fprintf(debugFP, "\n");
18997 *lp = dest ? line : old_line;
19002 // [HGM] vari: routines for shelving variations
19003 Boolean modeRestore = FALSE;
19006 PushInner (int firstMove, int lastMove)
19008 int i, j, nrMoves = lastMove - firstMove;
19010 // push current tail of game on stack
19011 savedResult[storedGames] = gameInfo.result;
19012 savedDetails[storedGames] = gameInfo.resultDetails;
19013 gameInfo.resultDetails = NULL;
19014 savedFirst[storedGames] = firstMove;
19015 savedLast [storedGames] = lastMove;
19016 savedFramePtr[storedGames] = framePtr;
19017 framePtr -= nrMoves; // reserve space for the boards
19018 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
19019 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
19020 for(j=0; j<MOVE_LEN; j++)
19021 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
19022 for(j=0; j<2*MOVE_LEN; j++)
19023 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
19024 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
19025 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
19026 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
19027 pvInfoList[firstMove+i-1].depth = 0;
19028 commentList[framePtr+i] = commentList[firstMove+i];
19029 commentList[firstMove+i] = NULL;
19033 forwardMostMove = firstMove; // truncate game so we can start variation
19037 PushTail (int firstMove, int lastMove)
19039 if(appData.icsActive) { // only in local mode
19040 forwardMostMove = currentMove; // mimic old ICS behavior
19043 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
19045 PushInner(firstMove, lastMove);
19046 if(storedGames == 1) GreyRevert(FALSE);
19047 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
19051 PopInner (Boolean annotate)
19054 char buf[8000], moveBuf[20];
19056 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
19057 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
19058 nrMoves = savedLast[storedGames] - currentMove;
19061 if(!WhiteOnMove(currentMove))
19062 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
19063 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
19064 for(i=currentMove; i<forwardMostMove; i++) {
19066 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
19067 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
19068 strcat(buf, moveBuf);
19069 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
19070 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
19074 for(i=1; i<=nrMoves; i++) { // copy last variation back
19075 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
19076 for(j=0; j<MOVE_LEN; j++)
19077 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
19078 for(j=0; j<2*MOVE_LEN; j++)
19079 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
19080 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
19081 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
19082 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
19083 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
19084 commentList[currentMove+i] = commentList[framePtr+i];
19085 commentList[framePtr+i] = NULL;
19087 if(annotate) AppendComment(currentMove+1, buf, FALSE);
19088 framePtr = savedFramePtr[storedGames];
19089 gameInfo.result = savedResult[storedGames];
19090 if(gameInfo.resultDetails != NULL) {
19091 free(gameInfo.resultDetails);
19093 gameInfo.resultDetails = savedDetails[storedGames];
19094 forwardMostMove = currentMove + nrMoves;
19098 PopTail (Boolean annotate)
19100 if(appData.icsActive) return FALSE; // only in local mode
19101 if(!storedGames) return FALSE; // sanity
19102 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
19104 PopInner(annotate);
19105 if(currentMove < forwardMostMove) ForwardEvent(); else
19106 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
19108 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
19114 { // remove all shelved variations
19116 for(i=0; i<storedGames; i++) {
19117 if(savedDetails[i])
19118 free(savedDetails[i]);
19119 savedDetails[i] = NULL;
19121 for(i=framePtr; i<MAX_MOVES; i++) {
19122 if(commentList[i]) free(commentList[i]);
19123 commentList[i] = NULL;
19125 framePtr = MAX_MOVES-1;
19130 LoadVariation (int index, char *text)
19131 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
19132 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
19133 int level = 0, move;
19135 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
19136 // first find outermost bracketing variation
19137 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
19138 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
19139 if(*p == '{') wait = '}'; else
19140 if(*p == '[') wait = ']'; else
19141 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
19142 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
19144 if(*p == wait) wait = NULLCHAR; // closing ]} found
19147 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19148 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19149 end[1] = NULLCHAR; // clip off comment beyond variation
19150 ToNrEvent(currentMove-1);
19151 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19152 // kludge: use ParsePV() to append variation to game
19153 move = currentMove;
19154 ParsePV(start, TRUE, TRUE);
19155 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19156 ClearPremoveHighlights();
19158 ToNrEvent(currentMove+1);
19161 int transparency[2];
19166 #define BUF_SIZ (2*MSG_SIZ)
19167 char *p, *q, buf[BUF_SIZ];
19168 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19169 snprintf(buf, BUF_SIZ, "-theme %s", engineLine);
19170 ParseArgsFromString(buf);
19171 ActivateTheme(TRUE); // also redo colors
19175 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19178 q = appData.themeNames;
19179 snprintf(buf, BUF_SIZ, "\"%s\"", nickName);
19180 if(appData.useBitmaps) {
19181 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt true -lbtf \"%s\"",
19182 Shorten(appData.liteBackTextureFile));
19183 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dbtf \"%s\" -lbtm %d -dbtm %d",
19184 Shorten(appData.darkBackTextureFile),
19185 appData.liteBackTextureMode,
19186 appData.darkBackTextureMode );
19188 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt false");
19190 if(!appData.useBitmaps || transparency[0]) {
19191 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -lsc %s", Col2Text(2) ); // lightSquareColor
19193 if(!appData.useBitmaps || transparency[1]) {
19194 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dsc %s", Col2Text(3) ); // darkSquareColor
19196 if(appData.useBorder) {
19197 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub true -border \"%s\"",
19200 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub false");
19202 if(appData.useFont) {
19203 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19204 appData.renderPiecesWithFont,
19205 appData.fontToPieceTable,
19206 Col2Text(9), // appData.fontBackColorWhite
19207 Col2Text(10) ); // appData.fontForeColorBlack
19209 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf false");
19210 if(appData.pieceDirectory[0]) {
19211 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -pid \"%s\"", Shorten(appData.pieceDirectory));
19212 if(appData.trueColors != 2) // 2 is a kludge to suppress this in WinBoard
19213 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -trueColors %s", appData.trueColors ? "true" : "false");
19215 if(!appData.pieceDirectory[0] || !appData.trueColors)
19216 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -wpc %s -bpc %s",
19217 Col2Text(0), // whitePieceColor
19218 Col2Text(1) ); // blackPieceColor
19220 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19221 Col2Text(4), // highlightSquareColor
19222 Col2Text(5) ); // premoveHighlightColor
19223 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19224 if(insert != q) insert[-1] = NULLCHAR;
19225 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19228 ActivateTheme(FALSE);