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 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 int flock(int f, int code);
63 # define EGBB_NAME "egbbdll64.dll"
65 # define EGBB_NAME "egbbdll.dll"
70 # include <sys/file.h>
75 # define EGBB_NAME "egbbso64.so"
77 # define EGBB_NAME "egbbso.so"
79 // kludge to allow Windows code in back-end by converting it to corresponding Linux code
81 # define HMODULE void *
82 # define LoadLibrary(x) dlopen(x, RTLD_LAZY)
83 # define GetProcAddress dlsym
93 #include <sys/types.h>
102 #else /* not STDC_HEADERS */
105 # else /* not HAVE_STRING_H */
106 # include <strings.h>
107 # endif /* not HAVE_STRING_H */
108 #endif /* not STDC_HEADERS */
111 # include <sys/fcntl.h>
112 #else /* not HAVE_SYS_FCNTL_H */
115 # endif /* HAVE_FCNTL_H */
116 #endif /* not HAVE_SYS_FCNTL_H */
118 #if TIME_WITH_SYS_TIME
119 # include <sys/time.h>
123 # include <sys/time.h>
129 #if defined(_amigados) && !defined(__GNUC__)
134 extern int gettimeofday(struct timeval *, struct timezone *);
142 #include "frontend.h"
149 #include "backendz.h"
150 #include "evalgraph.h"
151 #include "engineoutput.h"
155 # define _(s) gettext (s)
156 # define N_(s) gettext_noop (s)
157 # define T_(s) gettext(s)
170 int establish P((void));
171 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
172 char *buf, int count, int error));
173 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
174 char *buf, int count, int error));
175 void SendToICS P((char *s));
176 void SendToICSDelayed P((char *s, long msdelay));
177 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
178 void HandleMachineMove P((char *message, ChessProgramState *cps));
179 int AutoPlayOneMove P((void));
180 int LoadGameOneMove P((ChessMove readAhead));
181 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
182 int LoadPositionFromFile P((char *filename, int n, char *title));
183 int SavePositionToFile P((char *filename));
184 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
185 void ShowMove P((int fromX, int fromY, int toX, int toY));
186 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
187 /*char*/int promoChar));
188 void BackwardInner P((int target));
189 void ForwardInner P((int target));
190 int Adjudicate P((ChessProgramState *cps));
191 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
192 void EditPositionDone P((Boolean fakeRights));
193 void PrintOpponents P((FILE *fp));
194 void PrintPosition P((FILE *fp, int move));
195 void StartChessProgram P((ChessProgramState *cps));
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 */
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
272 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES]; /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 ChessSquare pieceSweep = EmptySquare;
293 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
294 int promoDefaultAltered;
295 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
296 static int initPing = -1;
298 /* States for ics_getting_history */
300 #define H_REQUESTED 1
301 #define H_GOT_REQ_HEADER 2
302 #define H_GOT_UNREQ_HEADER 3
303 #define H_GETTING_MOVES 4
304 #define H_GOT_UNWANTED_HEADER 5
306 /* whosays values for GameEnds */
315 /* Maximum number of games in a cmail message */
316 #define CMAIL_MAX_GAMES 20
318 /* Different types of move when calling RegisterMove */
320 #define CMAIL_RESIGN 1
322 #define CMAIL_ACCEPT 3
324 /* Different types of result to remember for each game */
325 #define CMAIL_NOT_RESULT 0
326 #define CMAIL_OLD_RESULT 1
327 #define CMAIL_NEW_RESULT 2
329 /* Telnet protocol constants */
340 safeStrCpy (char *dst, const char *src, size_t count)
343 assert( dst != NULL );
344 assert( src != NULL );
347 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
348 if( i == count && dst[count-1] != NULLCHAR)
350 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
351 if(appData.debugMode)
352 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
358 /* Some compiler can't cast u64 to double
359 * This function do the job for us:
361 * We use the highest bit for cast, this only
362 * works if the highest bit is not
363 * in use (This should not happen)
365 * We used this for all compiler
368 u64ToDouble (u64 value)
371 u64 tmp = value & u64Const(0x7fffffffffffffff);
372 r = (double)(s64)tmp;
373 if (value & u64Const(0x8000000000000000))
374 r += 9.2233720368547758080e18; /* 2^63 */
378 /* Fake up flags for now, as we aren't keeping track of castling
379 availability yet. [HGM] Change of logic: the flag now only
380 indicates the type of castlings allowed by the rule of the game.
381 The actual rights themselves are maintained in the array
382 castlingRights, as part of the game history, and are not probed
388 int flags = F_ALL_CASTLE_OK;
389 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
390 switch (gameInfo.variant) {
392 flags &= ~F_ALL_CASTLE_OK;
393 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
394 flags |= F_IGNORE_CHECK;
396 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
399 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
401 case VariantKriegspiel:
402 flags |= F_KRIEGSPIEL_CAPTURE;
404 case VariantCapaRandom:
405 case VariantFischeRandom:
406 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
407 case VariantNoCastle:
408 case VariantShatranj:
413 flags &= ~F_ALL_CASTLE_OK;
418 if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
422 FILE *gameFileFP, *debugFP, *serverFP;
423 char *currentDebugFile; // [HGM] debug split: to remember name
426 [AS] Note: sometimes, the sscanf() function is used to parse the input
427 into a fixed-size buffer. Because of this, we must be prepared to
428 receive strings as long as the size of the input buffer, which is currently
429 set to 4K for Windows and 8K for the rest.
430 So, we must either allocate sufficiently large buffers here, or
431 reduce the size of the input buffer in the input reading part.
434 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
435 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
436 char thinkOutput1[MSG_SIZ*10];
438 ChessProgramState first, second, pairing;
440 /* premove variables */
443 int premoveFromX = 0;
444 int premoveFromY = 0;
445 int premovePromoChar = 0;
447 Boolean alarmSounded;
448 /* end premove variables */
450 char *ics_prefix = "$";
451 enum ICS_TYPE ics_type = ICS_GENERIC;
453 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
454 int pauseExamForwardMostMove = 0;
455 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
456 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
457 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
458 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
459 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
460 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
461 int whiteFlag = FALSE, blackFlag = FALSE;
462 int userOfferedDraw = FALSE;
463 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
464 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
465 int cmailMoveType[CMAIL_MAX_GAMES];
466 long ics_clock_paused = 0;
467 ProcRef icsPR = NoProc, cmailPR = NoProc;
468 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
469 GameMode gameMode = BeginningOfGame;
470 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
471 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
472 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
473 int hiddenThinkOutputState = 0; /* [AS] */
474 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
475 int adjudicateLossPlies = 6;
476 char white_holding[64], black_holding[64];
477 TimeMark lastNodeCountTime;
478 long lastNodeCount=0;
479 int shiftKey, controlKey; // [HGM] set by mouse handler
481 int have_sent_ICS_logon = 0;
483 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
484 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
485 Boolean adjustedClock;
486 long timeControl_2; /* [AS] Allow separate time controls */
487 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
488 long timeRemaining[2][MAX_MOVES];
489 int matchGame = 0, nextGame = 0, roundNr = 0;
490 Boolean waitingForGame = FALSE, startingEngine = FALSE;
491 TimeMark programStartTime, pauseStart;
492 char ics_handle[MSG_SIZ];
493 int have_set_title = 0;
495 /* animateTraining preserves the state of appData.animate
496 * when Training mode is activated. This allows the
497 * response to be animated when appData.animate == TRUE and
498 * appData.animateDragging == TRUE.
500 Boolean animateTraining;
506 Board boards[MAX_MOVES];
507 /* [HGM] Following 7 needed for accurate legality tests: */
508 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
509 signed char initialRights[BOARD_FILES];
510 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
511 int initialRulePlies, FENrulePlies;
512 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
514 Boolean shuffleOpenings;
515 int mute; // mute all sounds
517 // [HGM] vari: next 12 to save and restore variations
518 #define MAX_VARIATIONS 10
519 int framePtr = MAX_MOVES-1; // points to free stack entry
521 int savedFirst[MAX_VARIATIONS];
522 int savedLast[MAX_VARIATIONS];
523 int savedFramePtr[MAX_VARIATIONS];
524 char *savedDetails[MAX_VARIATIONS];
525 ChessMove savedResult[MAX_VARIATIONS];
527 void PushTail P((int firstMove, int lastMove));
528 Boolean PopTail P((Boolean annotate));
529 void PushInner P((int firstMove, int lastMove));
530 void PopInner P((Boolean annotate));
531 void CleanupTail P((void));
533 ChessSquare FIDEArray[2][BOARD_FILES] = {
534 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
535 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
536 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
537 BlackKing, BlackBishop, BlackKnight, BlackRook }
540 ChessSquare twoKingsArray[2][BOARD_FILES] = {
541 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
542 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
543 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
544 BlackKing, BlackKing, BlackKnight, BlackRook }
547 ChessSquare KnightmateArray[2][BOARD_FILES] = {
548 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
549 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
550 { BlackRook, BlackMan, BlackBishop, BlackQueen,
551 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
554 ChessSquare SpartanArray[2][BOARD_FILES] = {
555 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
556 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
557 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
558 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
561 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
562 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
563 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
564 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
565 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
568 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
569 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
570 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
571 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
572 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
575 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
576 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
577 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
578 { BlackRook, BlackKnight, BlackMan, BlackFerz,
579 BlackKing, BlackMan, BlackKnight, BlackRook }
582 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
583 { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
584 WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
585 { BlackRook, BlackKnight, BlackMan, BlackFerz,
586 BlackKing, BlackMan, BlackKnight, BlackRook }
589 ChessSquare lionArray[2][BOARD_FILES] = {
590 { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
591 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
592 { BlackRook, BlackLion, BlackBishop, BlackQueen,
593 BlackKing, BlackBishop, BlackKnight, BlackRook }
597 #if (BOARD_FILES>=10)
598 ChessSquare ShogiArray[2][BOARD_FILES] = {
599 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
600 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
601 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
602 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
605 ChessSquare XiangqiArray[2][BOARD_FILES] = {
606 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
607 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
608 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
609 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
612 ChessSquare CapablancaArray[2][BOARD_FILES] = {
613 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
614 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
615 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
616 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
619 ChessSquare GreatArray[2][BOARD_FILES] = {
620 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
621 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
622 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
623 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
626 ChessSquare JanusArray[2][BOARD_FILES] = {
627 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
628 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
629 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
630 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
633 ChessSquare GrandArray[2][BOARD_FILES] = {
634 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
635 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
636 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
637 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
640 ChessSquare ChuChessArray[2][BOARD_FILES] = {
641 { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
642 WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
643 { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
644 BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
648 ChessSquare GothicArray[2][BOARD_FILES] = {
649 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
650 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
651 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
652 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
655 #define GothicArray CapablancaArray
659 ChessSquare FalconArray[2][BOARD_FILES] = {
660 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
661 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
662 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
663 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
666 #define FalconArray CapablancaArray
669 #else // !(BOARD_FILES>=10)
670 #define XiangqiPosition FIDEArray
671 #define CapablancaArray FIDEArray
672 #define GothicArray FIDEArray
673 #define GreatArray FIDEArray
674 #endif // !(BOARD_FILES>=10)
676 #if (BOARD_FILES>=12)
677 ChessSquare CourierArray[2][BOARD_FILES] = {
678 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
679 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
680 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
681 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
683 ChessSquare ChuArray[6][BOARD_FILES] = {
684 { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
685 WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
686 { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
687 BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
688 { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
689 WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
690 { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
691 BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
692 { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
693 WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
694 { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
695 BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
697 #else // !(BOARD_FILES>=12)
698 #define CourierArray CapablancaArray
699 #define ChuArray CapablancaArray
700 #endif // !(BOARD_FILES>=12)
703 Board initialPosition;
706 /* Convert str to a rating. Checks for special cases of "----",
708 "++++", etc. Also strips ()'s */
710 string_to_rating (char *str)
712 while(*str && !isdigit(*str)) ++str;
714 return 0; /* One of the special "no rating" cases */
722 /* Init programStats */
723 programStats.movelist[0] = 0;
724 programStats.depth = 0;
725 programStats.nr_moves = 0;
726 programStats.moves_left = 0;
727 programStats.nodes = 0;
728 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
729 programStats.score = 0;
730 programStats.got_only_move = 0;
731 programStats.got_fail = 0;
732 programStats.line_is_book = 0;
737 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
738 if (appData.firstPlaysBlack) {
739 first.twoMachinesColor = "black\n";
740 second.twoMachinesColor = "white\n";
742 first.twoMachinesColor = "white\n";
743 second.twoMachinesColor = "black\n";
746 first.other = &second;
747 second.other = &first;
750 if(appData.timeOddsMode) {
751 norm = appData.timeOdds[0];
752 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
754 first.timeOdds = appData.timeOdds[0]/norm;
755 second.timeOdds = appData.timeOdds[1]/norm;
758 if(programVersion) free(programVersion);
759 if (appData.noChessProgram) {
760 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
761 sprintf(programVersion, "%s", PACKAGE_STRING);
763 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
764 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
765 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
770 UnloadEngine (ChessProgramState *cps)
772 /* Kill off first chess program */
773 if (cps->isr != NULL)
774 RemoveInputSource(cps->isr);
777 if (cps->pr != NoProc) {
779 DoSleep( appData.delayBeforeQuit );
780 SendToProgram("quit\n", cps);
781 DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
784 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
788 ClearOptions (ChessProgramState *cps)
791 cps->nrOptions = cps->comboCnt = 0;
792 for(i=0; i<MAX_OPTIONS; i++) {
793 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
794 cps->option[i].textValue = 0;
798 char *engineNames[] = {
799 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
800 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
802 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
803 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
808 InitEngine (ChessProgramState *cps, int n)
809 { // [HGM] all engine initialiation put in a function that does one engine
813 cps->which = engineNames[n];
814 cps->maybeThinking = FALSE;
818 cps->sendDrawOffers = 1;
820 cps->program = appData.chessProgram[n];
821 cps->host = appData.host[n];
822 cps->dir = appData.directory[n];
823 cps->initString = appData.engInitString[n];
824 cps->computerString = appData.computerString[n];
825 cps->useSigint = TRUE;
826 cps->useSigterm = TRUE;
827 cps->reuse = appData.reuse[n];
828 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
829 cps->useSetboard = FALSE;
831 cps->usePing = FALSE;
834 cps->usePlayother = FALSE;
835 cps->useColors = TRUE;
836 cps->useUsermove = FALSE;
837 cps->sendICS = FALSE;
838 cps->sendName = appData.icsActive;
839 cps->sdKludge = FALSE;
840 cps->stKludge = FALSE;
841 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
842 TidyProgramName(cps->program, cps->host, cps->tidy);
844 ASSIGN(cps->variants, appData.variant);
845 cps->analysisSupport = 2; /* detect */
846 cps->analyzing = FALSE;
847 cps->initDone = FALSE;
850 /* New features added by Tord: */
851 cps->useFEN960 = FALSE;
852 cps->useOOCastle = TRUE;
853 /* End of new features added by Tord. */
854 cps->fenOverride = appData.fenOverride[n];
856 /* [HGM] time odds: set factor for each machine */
857 cps->timeOdds = appData.timeOdds[n];
859 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
860 cps->accumulateTC = appData.accumulateTC[n];
861 cps->maxNrOfSessions = 1;
866 cps->drawDepth = appData.drawDepth[n];
867 cps->supportsNPS = UNKNOWN;
868 cps->memSize = FALSE;
869 cps->maxCores = FALSE;
870 ASSIGN(cps->egtFormats, "");
873 cps->optionSettings = appData.engOptions[n];
875 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
876 cps->isUCI = appData.isUCI[n]; /* [AS] */
877 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
880 if (appData.protocolVersion[n] > PROTOVER
881 || appData.protocolVersion[n] < 1)
886 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
887 appData.protocolVersion[n]);
888 if( (len >= MSG_SIZ) && appData.debugMode )
889 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
891 DisplayFatalError(buf, 0, 2);
895 cps->protocolVersion = appData.protocolVersion[n];
898 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
899 ParseFeatures(appData.featureDefaults, cps);
902 ChessProgramState *savCps;
910 if(WaitForEngine(savCps, LoadEngine)) return;
911 CommonEngineInit(); // recalculate time odds
912 if(gameInfo.variant != StringToVariant(appData.variant)) {
913 // we changed variant when loading the engine; this forces us to reset
914 Reset(TRUE, savCps != &first);
915 oldMode = BeginningOfGame; // to prevent restoring old mode
917 InitChessProgram(savCps, FALSE);
918 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
919 DisplayMessage("", "");
920 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
921 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
924 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
928 ReplaceEngine (ChessProgramState *cps, int n)
930 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
932 if(oldMode != BeginningOfGame) EditGameEvent();
935 appData.noChessProgram = FALSE;
936 appData.clockMode = TRUE;
939 if(n) return; // only startup first engine immediately; second can wait
940 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
944 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
945 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
947 static char resetOptions[] =
948 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
949 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
950 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
951 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
954 FloatToFront(char **list, char *engineLine)
956 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
958 if(appData.recentEngines <= 0) return;
959 TidyProgramName(engineLine, "localhost", tidy+1);
960 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
961 strncpy(buf+1, *list, MSG_SIZ-50);
962 if(p = strstr(buf, tidy)) { // tidy name appears in list
963 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
964 while(*p++ = *++q); // squeeze out
966 strcat(tidy, buf+1); // put list behind tidy name
967 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
968 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
969 ASSIGN(*list, tidy+1);
972 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
975 Load (ChessProgramState *cps, int i)
977 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
978 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
979 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
980 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
981 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
982 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
983 appData.firstProtocolVersion = PROTOVER;
984 ParseArgsFromString(buf);
986 ReplaceEngine(cps, i);
987 FloatToFront(&appData.recentEngineList, engineLine);
991 while(q = strchr(p, SLASH)) p = q+1;
992 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
993 if(engineDir[0] != NULLCHAR) {
994 ASSIGN(appData.directory[i], engineDir); p = engineName;
995 } else if(p != engineName) { // derive directory from engine path, when not given
997 ASSIGN(appData.directory[i], engineName);
999 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1000 } else { ASSIGN(appData.directory[i], "."); }
1001 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1003 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1004 snprintf(command, MSG_SIZ, "%s %s", p, params);
1007 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1008 ASSIGN(appData.chessProgram[i], p);
1009 appData.isUCI[i] = isUCI;
1010 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1011 appData.hasOwnBookUCI[i] = hasBook;
1012 if(!nickName[0]) useNick = FALSE;
1013 if(useNick) ASSIGN(appData.pgnName[i], nickName);
1017 q = firstChessProgramNames;
1018 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1019 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1020 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1021 quote, p, quote, appData.directory[i],
1022 useNick ? " -fn \"" : "",
1023 useNick ? nickName : "",
1024 useNick ? "\"" : "",
1025 v1 ? " -firstProtocolVersion 1" : "",
1026 hasBook ? "" : " -fNoOwnBookUCI",
1027 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1028 storeVariant ? " -variant " : "",
1029 storeVariant ? VariantName(gameInfo.variant) : "");
1030 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1031 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1032 if(insert != q) insert[-1] = NULLCHAR;
1033 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1035 FloatToFront(&appData.recentEngineList, buf);
1037 ReplaceEngine(cps, i);
1043 int matched, min, sec;
1045 * Parse timeControl resource
1047 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1048 appData.movesPerSession)) {
1050 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1051 DisplayFatalError(buf, 0, 2);
1055 * Parse searchTime resource
1057 if (*appData.searchTime != NULLCHAR) {
1058 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1060 searchTime = min * 60;
1061 } else if (matched == 2) {
1062 searchTime = min * 60 + sec;
1065 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1066 DisplayFatalError(buf, 0, 2);
1075 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1076 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1078 GetTimeMark(&programStartTime);
1079 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1080 appData.seedBase = random() + (random()<<15);
1081 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1083 ClearProgramStats();
1084 programStats.ok_to_send = 1;
1085 programStats.seen_stat = 0;
1088 * Initialize game list
1094 * Internet chess server status
1096 if (appData.icsActive) {
1097 appData.matchMode = FALSE;
1098 appData.matchGames = 0;
1100 appData.noChessProgram = !appData.zippyPlay;
1102 appData.zippyPlay = FALSE;
1103 appData.zippyTalk = FALSE;
1104 appData.noChessProgram = TRUE;
1106 if (*appData.icsHelper != NULLCHAR) {
1107 appData.useTelnet = TRUE;
1108 appData.telnetProgram = appData.icsHelper;
1111 appData.zippyTalk = appData.zippyPlay = FALSE;
1114 /* [AS] Initialize pv info list [HGM] and game state */
1118 for( i=0; i<=framePtr; i++ ) {
1119 pvInfoList[i].depth = -1;
1120 boards[i][EP_STATUS] = EP_NONE;
1121 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1127 /* [AS] Adjudication threshold */
1128 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1130 InitEngine(&first, 0);
1131 InitEngine(&second, 1);
1134 pairing.which = "pairing"; // pairing engine
1135 pairing.pr = NoProc;
1137 pairing.program = appData.pairingEngine;
1138 pairing.host = "localhost";
1141 if (appData.icsActive) {
1142 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1143 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1144 appData.clockMode = FALSE;
1145 first.sendTime = second.sendTime = 0;
1149 /* Override some settings from environment variables, for backward
1150 compatibility. Unfortunately it's not feasible to have the env
1151 vars just set defaults, at least in xboard. Ugh.
1153 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1158 if (!appData.icsActive) {
1162 /* Check for variants that are supported only in ICS mode,
1163 or not at all. Some that are accepted here nevertheless
1164 have bugs; see comments below.
1166 VariantClass variant = StringToVariant(appData.variant);
1168 case VariantBughouse: /* need four players and two boards */
1169 case VariantKriegspiel: /* need to hide pieces and move details */
1170 /* case VariantFischeRandom: (Fabien: moved below) */
1171 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1172 if( (len >= MSG_SIZ) && appData.debugMode )
1173 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1175 DisplayFatalError(buf, 0, 2);
1178 case VariantUnknown:
1179 case VariantLoadable:
1189 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1190 if( (len >= MSG_SIZ) && appData.debugMode )
1191 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1193 DisplayFatalError(buf, 0, 2);
1196 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1197 case VariantFairy: /* [HGM] TestLegality definitely off! */
1198 case VariantGothic: /* [HGM] should work */
1199 case VariantCapablanca: /* [HGM] should work */
1200 case VariantCourier: /* [HGM] initial forced moves not implemented */
1201 case VariantShogi: /* [HGM] could still mate with pawn drop */
1202 case VariantChu: /* [HGM] experimental */
1203 case VariantKnightmate: /* [HGM] should work */
1204 case VariantCylinder: /* [HGM] untested */
1205 case VariantFalcon: /* [HGM] untested */
1206 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1207 offboard interposition not understood */
1208 case VariantNormal: /* definitely works! */
1209 case VariantWildCastle: /* pieces not automatically shuffled */
1210 case VariantNoCastle: /* pieces not automatically shuffled */
1211 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1212 case VariantLosers: /* should work except for win condition,
1213 and doesn't know captures are mandatory */
1214 case VariantSuicide: /* should work except for win condition,
1215 and doesn't know captures are mandatory */
1216 case VariantGiveaway: /* should work except for win condition,
1217 and doesn't know captures are mandatory */
1218 case VariantTwoKings: /* should work */
1219 case VariantAtomic: /* should work except for win condition */
1220 case Variant3Check: /* should work except for win condition */
1221 case VariantShatranj: /* should work except for all win conditions */
1222 case VariantMakruk: /* should work except for draw countdown */
1223 case VariantASEAN : /* should work except for draw countdown */
1224 case VariantBerolina: /* might work if TestLegality is off */
1225 case VariantCapaRandom: /* should work */
1226 case VariantJanus: /* should work */
1227 case VariantSuper: /* experimental */
1228 case VariantGreat: /* experimental, requires legality testing to be off */
1229 case VariantSChess: /* S-Chess, should work */
1230 case VariantGrand: /* should work */
1231 case VariantSpartan: /* should work */
1232 case VariantLion: /* should work */
1233 case VariantChuChess: /* should work */
1241 NextIntegerFromString (char ** str, long * value)
1246 while( *s == ' ' || *s == '\t' ) {
1252 if( *s >= '0' && *s <= '9' ) {
1253 while( *s >= '0' && *s <= '9' ) {
1254 *value = *value * 10 + (*s - '0');
1267 NextTimeControlFromString (char ** str, long * value)
1270 int result = NextIntegerFromString( str, &temp );
1273 *value = temp * 60; /* Minutes */
1274 if( **str == ':' ) {
1276 result = NextIntegerFromString( str, &temp );
1277 *value += temp; /* Seconds */
1285 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1286 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1287 int result = -1, type = 0; long temp, temp2;
1289 if(**str != ':') return -1; // old params remain in force!
1291 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1292 if( NextIntegerFromString( str, &temp ) ) return -1;
1293 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1296 /* time only: incremental or sudden-death time control */
1297 if(**str == '+') { /* increment follows; read it */
1299 if(**str == '!') type = *(*str)++; // Bronstein TC
1300 if(result = NextIntegerFromString( str, &temp2)) return -1;
1301 *inc = temp2 * 1000;
1302 if(**str == '.') { // read fraction of increment
1303 char *start = ++(*str);
1304 if(result = NextIntegerFromString( str, &temp2)) return -1;
1306 while(start++ < *str) temp2 /= 10;
1310 *moves = 0; *tc = temp * 1000; *incType = type;
1314 (*str)++; /* classical time control */
1315 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1327 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1328 { /* [HGM] get time to add from the multi-session time-control string */
1329 int incType, moves=1; /* kludge to force reading of first session */
1330 long time, increment;
1333 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1335 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1336 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1337 if(movenr == -1) return time; /* last move before new session */
1338 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1339 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1340 if(!moves) return increment; /* current session is incremental */
1341 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1342 } while(movenr >= -1); /* try again for next session */
1344 return 0; // no new time quota on this move
1348 ParseTimeControl (char *tc, float ti, int mps)
1352 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1355 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1356 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1357 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1361 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1363 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1366 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1368 snprintf(buf, MSG_SIZ, ":%s", mytc);
1370 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1372 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1377 /* Parse second time control */
1380 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1388 timeControl_2 = tc2 * 1000;
1398 timeControl = tc1 * 1000;
1401 timeIncrement = ti * 1000; /* convert to ms */
1402 movesPerSession = 0;
1405 movesPerSession = mps;
1413 if (appData.debugMode) {
1414 # ifdef __GIT_VERSION
1415 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1417 fprintf(debugFP, "Version: %s\n", programVersion);
1420 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1422 set_cont_sequence(appData.wrapContSeq);
1423 if (appData.matchGames > 0) {
1424 appData.matchMode = TRUE;
1425 } else if (appData.matchMode) {
1426 appData.matchGames = 1;
1428 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1429 appData.matchGames = appData.sameColorGames;
1430 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1431 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1432 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1435 if (appData.noChessProgram || first.protocolVersion == 1) {
1438 /* kludge: allow timeout for initial "feature" commands */
1440 DisplayMessage("", _("Starting chess program"));
1441 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1446 CalculateIndex (int index, int gameNr)
1447 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1449 if(index > 0) return index; // fixed nmber
1450 if(index == 0) return 1;
1451 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1452 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1457 LoadGameOrPosition (int gameNr)
1458 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1459 if (*appData.loadGameFile != NULLCHAR) {
1460 if (!LoadGameFromFile(appData.loadGameFile,
1461 CalculateIndex(appData.loadGameIndex, gameNr),
1462 appData.loadGameFile, FALSE)) {
1463 DisplayFatalError(_("Bad game file"), 0, 1);
1466 } else if (*appData.loadPositionFile != NULLCHAR) {
1467 if (!LoadPositionFromFile(appData.loadPositionFile,
1468 CalculateIndex(appData.loadPositionIndex, gameNr),
1469 appData.loadPositionFile)) {
1470 DisplayFatalError(_("Bad position file"), 0, 1);
1478 ReserveGame (int gameNr, char resChar)
1480 FILE *tf = fopen(appData.tourneyFile, "r+");
1481 char *p, *q, c, buf[MSG_SIZ];
1482 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1483 safeStrCpy(buf, lastMsg, MSG_SIZ);
1484 DisplayMessage(_("Pick new game"), "");
1485 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1486 ParseArgsFromFile(tf);
1487 p = q = appData.results;
1488 if(appData.debugMode) {
1489 char *r = appData.participants;
1490 fprintf(debugFP, "results = '%s'\n", p);
1491 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1492 fprintf(debugFP, "\n");
1494 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1496 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1497 safeStrCpy(q, p, strlen(p) + 2);
1498 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1499 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1500 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1501 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1504 fseek(tf, -(strlen(p)+4), SEEK_END);
1506 if(c != '"') // depending on DOS or Unix line endings we can be one off
1507 fseek(tf, -(strlen(p)+2), SEEK_END);
1508 else fseek(tf, -(strlen(p)+3), SEEK_END);
1509 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1510 DisplayMessage(buf, "");
1511 free(p); appData.results = q;
1512 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1513 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1514 int round = appData.defaultMatchGames * appData.tourneyType;
1515 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1516 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1517 UnloadEngine(&first); // next game belongs to other pairing;
1518 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1520 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1524 MatchEvent (int mode)
1525 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1527 if(matchMode) { // already in match mode: switch it off
1529 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1532 // if(gameMode != BeginningOfGame) {
1533 // DisplayError(_("You can only start a match from the initial position."), 0);
1537 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1538 /* Set up machine vs. machine match */
1540 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1541 if(appData.tourneyFile[0]) {
1543 if(nextGame > appData.matchGames) {
1545 if(strchr(appData.results, '*') == NULL) {
1547 appData.tourneyCycles++;
1548 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1550 NextTourneyGame(-1, &dummy);
1552 if(nextGame <= appData.matchGames) {
1553 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1555 ScheduleDelayedEvent(NextMatchGame, 10000);
1560 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1561 DisplayError(buf, 0);
1562 appData.tourneyFile[0] = 0;
1566 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1567 DisplayFatalError(_("Can't have a match with no chess programs"),
1572 matchGame = roundNr = 1;
1573 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1577 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1580 InitBackEnd3 P((void))
1582 GameMode initialMode;
1586 if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode && // mode involves only first engine
1587 !strcmp(appData.variant, "normal") && // no explicit variant request
1588 appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 && // no size overrides requested
1589 !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") && // but 'normal' won't work with engine
1590 !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1591 char c, *q = first.variants, *p = strchr(q, ',');
1592 if(p) *p = NULLCHAR;
1593 if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1595 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1596 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1597 ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1598 Reset(TRUE, FALSE); // and re-initialize
1603 InitChessProgram(&first, startedFromSetupPosition);
1605 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1606 free(programVersion);
1607 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1608 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1609 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1612 if (appData.icsActive) {
1614 /* [DM] Make a console window if needed [HGM] merged ifs */
1620 if (*appData.icsCommPort != NULLCHAR)
1621 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1622 appData.icsCommPort);
1624 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1625 appData.icsHost, appData.icsPort);
1627 if( (len >= MSG_SIZ) && appData.debugMode )
1628 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1630 DisplayFatalError(buf, err, 1);
1635 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1637 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1638 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1639 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1640 } else if (appData.noChessProgram) {
1646 if (*appData.cmailGameName != NULLCHAR) {
1648 OpenLoopback(&cmailPR);
1650 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1654 DisplayMessage("", "");
1655 if (StrCaseCmp(appData.initialMode, "") == 0) {
1656 initialMode = BeginningOfGame;
1657 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1658 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1659 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1660 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1663 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1664 initialMode = TwoMachinesPlay;
1665 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1666 initialMode = AnalyzeFile;
1667 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1668 initialMode = AnalyzeMode;
1669 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1670 initialMode = MachinePlaysWhite;
1671 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1672 initialMode = MachinePlaysBlack;
1673 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1674 initialMode = EditGame;
1675 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1676 initialMode = EditPosition;
1677 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1678 initialMode = Training;
1680 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1681 if( (len >= MSG_SIZ) && appData.debugMode )
1682 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1684 DisplayFatalError(buf, 0, 2);
1688 if (appData.matchMode) {
1689 if(appData.tourneyFile[0]) { // start tourney from command line
1691 if(f = fopen(appData.tourneyFile, "r")) {
1692 ParseArgsFromFile(f); // make sure tourney parmeters re known
1694 appData.clockMode = TRUE;
1696 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1699 } else if (*appData.cmailGameName != NULLCHAR) {
1700 /* Set up cmail mode */
1701 ReloadCmailMsgEvent(TRUE);
1703 /* Set up other modes */
1704 if (initialMode == AnalyzeFile) {
1705 if (*appData.loadGameFile == NULLCHAR) {
1706 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1710 if (*appData.loadGameFile != NULLCHAR) {
1711 (void) LoadGameFromFile(appData.loadGameFile,
1712 appData.loadGameIndex,
1713 appData.loadGameFile, TRUE);
1714 } else if (*appData.loadPositionFile != NULLCHAR) {
1715 (void) LoadPositionFromFile(appData.loadPositionFile,
1716 appData.loadPositionIndex,
1717 appData.loadPositionFile);
1718 /* [HGM] try to make self-starting even after FEN load */
1719 /* to allow automatic setup of fairy variants with wtm */
1720 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1721 gameMode = BeginningOfGame;
1722 setboardSpoiledMachineBlack = 1;
1724 /* [HGM] loadPos: make that every new game uses the setup */
1725 /* from file as long as we do not switch variant */
1726 if(!blackPlaysFirst) {
1727 startedFromPositionFile = TRUE;
1728 CopyBoard(filePosition, boards[0]);
1731 if (initialMode == AnalyzeMode) {
1732 if (appData.noChessProgram) {
1733 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1736 if (appData.icsActive) {
1737 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1741 } else if (initialMode == AnalyzeFile) {
1742 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1743 ShowThinkingEvent();
1745 AnalysisPeriodicEvent(1);
1746 } else if (initialMode == MachinePlaysWhite) {
1747 if (appData.noChessProgram) {
1748 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1752 if (appData.icsActive) {
1753 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1757 MachineWhiteEvent();
1758 } else if (initialMode == MachinePlaysBlack) {
1759 if (appData.noChessProgram) {
1760 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1764 if (appData.icsActive) {
1765 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1769 MachineBlackEvent();
1770 } else if (initialMode == TwoMachinesPlay) {
1771 if (appData.noChessProgram) {
1772 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1776 if (appData.icsActive) {
1777 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1782 } else if (initialMode == EditGame) {
1784 } else if (initialMode == EditPosition) {
1785 EditPositionEvent();
1786 } else if (initialMode == Training) {
1787 if (*appData.loadGameFile == NULLCHAR) {
1788 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1797 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1799 DisplayBook(current+1);
1801 MoveHistorySet( movelist, first, last, current, pvInfoList );
1803 EvalGraphSet( first, last, current, pvInfoList );
1805 MakeEngineOutputTitle();
1809 * Establish will establish a contact to a remote host.port.
1810 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1811 * used to talk to the host.
1812 * Returns 0 if okay, error code if not.
1819 if (*appData.icsCommPort != NULLCHAR) {
1820 /* Talk to the host through a serial comm port */
1821 return OpenCommPort(appData.icsCommPort, &icsPR);
1823 } else if (*appData.gateway != NULLCHAR) {
1824 if (*appData.remoteShell == NULLCHAR) {
1825 /* Use the rcmd protocol to run telnet program on a gateway host */
1826 snprintf(buf, sizeof(buf), "%s %s %s",
1827 appData.telnetProgram, appData.icsHost, appData.icsPort);
1828 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1831 /* Use the rsh program to run telnet program on a gateway host */
1832 if (*appData.remoteUser == NULLCHAR) {
1833 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1834 appData.gateway, appData.telnetProgram,
1835 appData.icsHost, appData.icsPort);
1837 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1838 appData.remoteShell, appData.gateway,
1839 appData.remoteUser, appData.telnetProgram,
1840 appData.icsHost, appData.icsPort);
1842 return StartChildProcess(buf, "", &icsPR);
1845 } else if (appData.useTelnet) {
1846 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1849 /* TCP socket interface differs somewhat between
1850 Unix and NT; handle details in the front end.
1852 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1857 EscapeExpand (char *p, char *q)
1858 { // [HGM] initstring: routine to shape up string arguments
1859 while(*p++ = *q++) if(p[-1] == '\\')
1861 case 'n': p[-1] = '\n'; break;
1862 case 'r': p[-1] = '\r'; break;
1863 case 't': p[-1] = '\t'; break;
1864 case '\\': p[-1] = '\\'; break;
1865 case 0: *p = 0; return;
1866 default: p[-1] = q[-1]; break;
1871 show_bytes (FILE *fp, char *buf, int count)
1874 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1875 fprintf(fp, "\\%03o", *buf & 0xff);
1884 /* Returns an errno value */
1886 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1888 char buf[8192], *p, *q, *buflim;
1889 int left, newcount, outcount;
1891 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1892 *appData.gateway != NULLCHAR) {
1893 if (appData.debugMode) {
1894 fprintf(debugFP, ">ICS: ");
1895 show_bytes(debugFP, message, count);
1896 fprintf(debugFP, "\n");
1898 return OutputToProcess(pr, message, count, outError);
1901 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1908 if (appData.debugMode) {
1909 fprintf(debugFP, ">ICS: ");
1910 show_bytes(debugFP, buf, newcount);
1911 fprintf(debugFP, "\n");
1913 outcount = OutputToProcess(pr, buf, newcount, outError);
1914 if (outcount < newcount) return -1; /* to be sure */
1921 } else if (((unsigned char) *p) == TN_IAC) {
1922 *q++ = (char) TN_IAC;
1929 if (appData.debugMode) {
1930 fprintf(debugFP, ">ICS: ");
1931 show_bytes(debugFP, buf, newcount);
1932 fprintf(debugFP, "\n");
1934 outcount = OutputToProcess(pr, buf, newcount, outError);
1935 if (outcount < newcount) return -1; /* to be sure */
1940 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1942 int outError, outCount;
1943 static int gotEof = 0;
1946 /* Pass data read from player on to ICS */
1949 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1950 if (outCount < count) {
1951 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1953 if(have_sent_ICS_logon == 2) {
1954 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1955 fprintf(ini, "%s", message);
1956 have_sent_ICS_logon = 3;
1958 have_sent_ICS_logon = 1;
1959 } else if(have_sent_ICS_logon == 3) {
1960 fprintf(ini, "%s", message);
1962 have_sent_ICS_logon = 1;
1964 } else if (count < 0) {
1965 RemoveInputSource(isr);
1966 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1967 } else if (gotEof++ > 0) {
1968 RemoveInputSource(isr);
1969 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1975 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1976 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1977 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1978 SendToICS("date\n");
1979 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1982 /* added routine for printf style output to ics */
1984 ics_printf (char *format, ...)
1986 char buffer[MSG_SIZ];
1989 va_start(args, format);
1990 vsnprintf(buffer, sizeof(buffer), format, args);
1991 buffer[sizeof(buffer)-1] = '\0';
1999 int count, outCount, outError;
2001 if (icsPR == NoProc) return;
2004 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2005 if (outCount < count) {
2006 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2010 /* This is used for sending logon scripts to the ICS. Sending
2011 without a delay causes problems when using timestamp on ICC
2012 (at least on my machine). */
2014 SendToICSDelayed (char *s, long msdelay)
2016 int count, outCount, outError;
2018 if (icsPR == NoProc) return;
2021 if (appData.debugMode) {
2022 fprintf(debugFP, ">ICS: ");
2023 show_bytes(debugFP, s, count);
2024 fprintf(debugFP, "\n");
2026 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2028 if (outCount < count) {
2029 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2034 /* Remove all highlighting escape sequences in s
2035 Also deletes any suffix starting with '('
2038 StripHighlightAndTitle (char *s)
2040 static char retbuf[MSG_SIZ];
2043 while (*s != NULLCHAR) {
2044 while (*s == '\033') {
2045 while (*s != NULLCHAR && !isalpha(*s)) s++;
2046 if (*s != NULLCHAR) s++;
2048 while (*s != NULLCHAR && *s != '\033') {
2049 if (*s == '(' || *s == '[') {
2060 /* Remove all highlighting escape sequences in s */
2062 StripHighlight (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') {
2080 char engineVariant[MSG_SIZ];
2081 char *variantNames[] = VARIANT_NAMES;
2083 VariantName (VariantClass v)
2085 if(v == VariantUnknown || *engineVariant) return engineVariant;
2086 return variantNames[v];
2090 /* Identify a variant from the strings the chess servers use or the
2091 PGN Variant tag names we use. */
2093 StringToVariant (char *e)
2097 VariantClass v = VariantNormal;
2098 int i, found = FALSE;
2104 /* [HGM] skip over optional board-size prefixes */
2105 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2106 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2107 while( *e++ != '_');
2110 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2114 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2115 if (p = StrCaseStr(e, variantNames[i])) {
2116 if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
2117 v = (VariantClass) i;
2124 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2125 || StrCaseStr(e, "wild/fr")
2126 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2127 v = VariantFischeRandom;
2128 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2129 (i = 1, p = StrCaseStr(e, "w"))) {
2131 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2138 case 0: /* FICS only, actually */
2140 /* Castling legal even if K starts on d-file */
2141 v = VariantWildCastle;
2146 /* Castling illegal even if K & R happen to start in
2147 normal positions. */
2148 v = VariantNoCastle;
2161 /* Castling legal iff K & R start in normal positions */
2167 /* Special wilds for position setup; unclear what to do here */
2168 v = VariantLoadable;
2171 /* Bizarre ICC game */
2172 v = VariantTwoKings;
2175 v = VariantKriegspiel;
2181 v = VariantFischeRandom;
2184 v = VariantCrazyhouse;
2187 v = VariantBughouse;
2193 /* Not quite the same as FICS suicide! */
2194 v = VariantGiveaway;
2200 v = VariantShatranj;
2203 /* Temporary names for future ICC types. The name *will* change in
2204 the next xboard/WinBoard release after ICC defines it. */
2242 v = VariantCapablanca;
2245 v = VariantKnightmate;
2251 v = VariantCylinder;
2257 v = VariantCapaRandom;
2260 v = VariantBerolina;
2272 /* Found "wild" or "w" in the string but no number;
2273 must assume it's normal chess. */
2277 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2278 if( (len >= MSG_SIZ) && appData.debugMode )
2279 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2281 DisplayError(buf, 0);
2287 if (appData.debugMode) {
2288 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2289 e, wnum, VariantName(v));
2294 static int leftover_start = 0, leftover_len = 0;
2295 char star_match[STAR_MATCH_N][MSG_SIZ];
2297 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2298 advance *index beyond it, and set leftover_start to the new value of
2299 *index; else return FALSE. If pattern contains the character '*', it
2300 matches any sequence of characters not containing '\r', '\n', or the
2301 character following the '*' (if any), and the matched sequence(s) are
2302 copied into star_match.
2305 looking_at ( char *buf, int *index, char *pattern)
2307 char *bufp = &buf[*index], *patternp = pattern;
2309 char *matchp = star_match[0];
2312 if (*patternp == NULLCHAR) {
2313 *index = leftover_start = bufp - buf;
2317 if (*bufp == NULLCHAR) return FALSE;
2318 if (*patternp == '*') {
2319 if (*bufp == *(patternp + 1)) {
2321 matchp = star_match[++star_count];
2325 } else if (*bufp == '\n' || *bufp == '\r') {
2327 if (*patternp == NULLCHAR)
2332 *matchp++ = *bufp++;
2336 if (*patternp != *bufp) return FALSE;
2343 SendToPlayer (char *data, int length)
2345 int error, outCount;
2346 outCount = OutputToProcess(NoProc, data, length, &error);
2347 if (outCount < length) {
2348 DisplayFatalError(_("Error writing to display"), error, 1);
2353 PackHolding (char packed[], char *holding)
2363 switch (runlength) {
2374 sprintf(q, "%d", runlength);
2386 /* Telnet protocol requests from the front end */
2388 TelnetRequest (unsigned char ddww, unsigned char option)
2390 unsigned char msg[3];
2391 int outCount, outError;
2393 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2395 if (appData.debugMode) {
2396 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2412 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2421 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2424 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2429 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2431 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2438 if (!appData.icsActive) return;
2439 TelnetRequest(TN_DO, TN_ECHO);
2445 if (!appData.icsActive) return;
2446 TelnetRequest(TN_DONT, TN_ECHO);
2450 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2452 /* put the holdings sent to us by the server on the board holdings area */
2453 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2457 if(gameInfo.holdingsWidth < 2) return;
2458 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2459 return; // prevent overwriting by pre-board holdings
2461 if( (int)lowestPiece >= BlackPawn ) {
2464 holdingsStartRow = BOARD_HEIGHT-1;
2467 holdingsColumn = BOARD_WIDTH-1;
2468 countsColumn = BOARD_WIDTH-2;
2469 holdingsStartRow = 0;
2473 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2474 board[i][holdingsColumn] = EmptySquare;
2475 board[i][countsColumn] = (ChessSquare) 0;
2477 while( (p=*holdings++) != NULLCHAR ) {
2478 piece = CharToPiece( ToUpper(p) );
2479 if(piece == EmptySquare) continue;
2480 /*j = (int) piece - (int) WhitePawn;*/
2481 j = PieceToNumber(piece);
2482 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2483 if(j < 0) continue; /* should not happen */
2484 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2485 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2486 board[holdingsStartRow+j*direction][countsColumn]++;
2492 VariantSwitch (Board board, VariantClass newVariant)
2494 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2495 static Board oldBoard;
2497 startedFromPositionFile = FALSE;
2498 if(gameInfo.variant == newVariant) return;
2500 /* [HGM] This routine is called each time an assignment is made to
2501 * gameInfo.variant during a game, to make sure the board sizes
2502 * are set to match the new variant. If that means adding or deleting
2503 * holdings, we shift the playing board accordingly
2504 * This kludge is needed because in ICS observe mode, we get boards
2505 * of an ongoing game without knowing the variant, and learn about the
2506 * latter only later. This can be because of the move list we requested,
2507 * in which case the game history is refilled from the beginning anyway,
2508 * but also when receiving holdings of a crazyhouse game. In the latter
2509 * case we want to add those holdings to the already received position.
2513 if (appData.debugMode) {
2514 fprintf(debugFP, "Switch board from %s to %s\n",
2515 VariantName(gameInfo.variant), VariantName(newVariant));
2516 setbuf(debugFP, NULL);
2518 shuffleOpenings = 0; /* [HGM] shuffle */
2519 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2523 newWidth = 9; newHeight = 9;
2524 gameInfo.holdingsSize = 7;
2525 case VariantBughouse:
2526 case VariantCrazyhouse:
2527 newHoldingsWidth = 2; break;
2531 newHoldingsWidth = 2;
2532 gameInfo.holdingsSize = 8;
2535 case VariantCapablanca:
2536 case VariantCapaRandom:
2539 newHoldingsWidth = gameInfo.holdingsSize = 0;
2542 if(newWidth != gameInfo.boardWidth ||
2543 newHeight != gameInfo.boardHeight ||
2544 newHoldingsWidth != gameInfo.holdingsWidth ) {
2546 /* shift position to new playing area, if needed */
2547 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2548 for(i=0; i<BOARD_HEIGHT; i++)
2549 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2550 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2552 for(i=0; i<newHeight; i++) {
2553 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2554 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2556 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2557 for(i=0; i<BOARD_HEIGHT; i++)
2558 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2559 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2562 board[HOLDINGS_SET] = 0;
2563 gameInfo.boardWidth = newWidth;
2564 gameInfo.boardHeight = newHeight;
2565 gameInfo.holdingsWidth = newHoldingsWidth;
2566 gameInfo.variant = newVariant;
2567 InitDrawingSizes(-2, 0);
2568 } else gameInfo.variant = newVariant;
2569 CopyBoard(oldBoard, board); // remember correctly formatted board
2570 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2571 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2574 static int loggedOn = FALSE;
2576 /*-- Game start info cache: --*/
2578 char gs_kind[MSG_SIZ];
2579 static char player1Name[128] = "";
2580 static char player2Name[128] = "";
2581 static char cont_seq[] = "\n\\ ";
2582 static int player1Rating = -1;
2583 static int player2Rating = -1;
2584 /*----------------------------*/
2586 ColorClass curColor = ColorNormal;
2587 int suppressKibitz = 0;
2590 Boolean soughtPending = FALSE;
2591 Boolean seekGraphUp;
2592 #define MAX_SEEK_ADS 200
2594 char *seekAdList[MAX_SEEK_ADS];
2595 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2596 float tcList[MAX_SEEK_ADS];
2597 char colorList[MAX_SEEK_ADS];
2598 int nrOfSeekAds = 0;
2599 int minRating = 1010, maxRating = 2800;
2600 int hMargin = 10, vMargin = 20, h, w;
2601 extern int squareSize, lineGap;
2606 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2607 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2608 if(r < minRating+100 && r >=0 ) r = minRating+100;
2609 if(r > maxRating) r = maxRating;
2610 if(tc < 1.f) tc = 1.f;
2611 if(tc > 95.f) tc = 95.f;
2612 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2613 y = ((double)r - minRating)/(maxRating - minRating)
2614 * (h-vMargin-squareSize/8-1) + vMargin;
2615 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2616 if(strstr(seekAdList[i], " u ")) color = 1;
2617 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2618 !strstr(seekAdList[i], "bullet") &&
2619 !strstr(seekAdList[i], "blitz") &&
2620 !strstr(seekAdList[i], "standard") ) color = 2;
2621 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2622 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2626 PlotSingleSeekAd (int i)
2632 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2634 char buf[MSG_SIZ], *ext = "";
2635 VariantClass v = StringToVariant(type);
2636 if(strstr(type, "wild")) {
2637 ext = type + 4; // append wild number
2638 if(v == VariantFischeRandom) type = "chess960"; else
2639 if(v == VariantLoadable) type = "setup"; else
2640 type = VariantName(v);
2642 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2643 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2644 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2645 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2646 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2647 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2648 seekNrList[nrOfSeekAds] = nr;
2649 zList[nrOfSeekAds] = 0;
2650 seekAdList[nrOfSeekAds++] = StrSave(buf);
2651 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2656 EraseSeekDot (int i)
2658 int x = xList[i], y = yList[i], d=squareSize/4, k;
2659 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2660 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2661 // now replot every dot that overlapped
2662 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2663 int xx = xList[k], yy = yList[k];
2664 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2665 DrawSeekDot(xx, yy, colorList[k]);
2670 RemoveSeekAd (int nr)
2673 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2675 if(seekAdList[i]) free(seekAdList[i]);
2676 seekAdList[i] = seekAdList[--nrOfSeekAds];
2677 seekNrList[i] = seekNrList[nrOfSeekAds];
2678 ratingList[i] = ratingList[nrOfSeekAds];
2679 colorList[i] = colorList[nrOfSeekAds];
2680 tcList[i] = tcList[nrOfSeekAds];
2681 xList[i] = xList[nrOfSeekAds];
2682 yList[i] = yList[nrOfSeekAds];
2683 zList[i] = zList[nrOfSeekAds];
2684 seekAdList[nrOfSeekAds] = NULL;
2690 MatchSoughtLine (char *line)
2692 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2693 int nr, base, inc, u=0; char dummy;
2695 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2696 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2698 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2699 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2700 // match: compact and save the line
2701 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2711 if(!seekGraphUp) return FALSE;
2712 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2713 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2715 DrawSeekBackground(0, 0, w, h);
2716 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2717 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2718 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2719 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2721 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2724 snprintf(buf, MSG_SIZ, "%d", i);
2725 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2728 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2729 for(i=1; i<100; i+=(i<10?1:5)) {
2730 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2731 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2732 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2734 snprintf(buf, MSG_SIZ, "%d", i);
2735 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2738 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2743 SeekGraphClick (ClickType click, int x, int y, int moving)
2745 static int lastDown = 0, displayed = 0, lastSecond;
2746 if(y < 0) return FALSE;
2747 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2748 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2749 if(!seekGraphUp) return FALSE;
2750 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2751 DrawPosition(TRUE, NULL);
2754 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2755 if(click == Release || moving) return FALSE;
2757 soughtPending = TRUE;
2758 SendToICS(ics_prefix);
2759 SendToICS("sought\n"); // should this be "sought all"?
2760 } else { // issue challenge based on clicked ad
2761 int dist = 10000; int i, closest = 0, second = 0;
2762 for(i=0; i<nrOfSeekAds; i++) {
2763 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2764 if(d < dist) { dist = d; closest = i; }
2765 second += (d - zList[i] < 120); // count in-range ads
2766 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2770 second = (second > 1);
2771 if(displayed != closest || second != lastSecond) {
2772 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2773 lastSecond = second; displayed = closest;
2775 if(click == Press) {
2776 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2779 } // on press 'hit', only show info
2780 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2781 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2782 SendToICS(ics_prefix);
2784 return TRUE; // let incoming board of started game pop down the graph
2785 } else if(click == Release) { // release 'miss' is ignored
2786 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2787 if(moving == 2) { // right up-click
2788 nrOfSeekAds = 0; // refresh graph
2789 soughtPending = TRUE;
2790 SendToICS(ics_prefix);
2791 SendToICS("sought\n"); // should this be "sought all"?
2794 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2795 // press miss or release hit 'pop down' seek graph
2796 seekGraphUp = FALSE;
2797 DrawPosition(TRUE, NULL);
2803 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2805 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2806 #define STARTED_NONE 0
2807 #define STARTED_MOVES 1
2808 #define STARTED_BOARD 2
2809 #define STARTED_OBSERVE 3
2810 #define STARTED_HOLDINGS 4
2811 #define STARTED_CHATTER 5
2812 #define STARTED_COMMENT 6
2813 #define STARTED_MOVES_NOHIDE 7
2815 static int started = STARTED_NONE;
2816 static char parse[20000];
2817 static int parse_pos = 0;
2818 static char buf[BUF_SIZE + 1];
2819 static int firstTime = TRUE, intfSet = FALSE;
2820 static ColorClass prevColor = ColorNormal;
2821 static int savingComment = FALSE;
2822 static int cmatch = 0; // continuation sequence match
2829 int backup; /* [DM] For zippy color lines */
2831 char talker[MSG_SIZ]; // [HGM] chat
2834 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2836 if (appData.debugMode) {
2838 fprintf(debugFP, "<ICS: ");
2839 show_bytes(debugFP, data, count);
2840 fprintf(debugFP, "\n");
2844 if (appData.debugMode) { int f = forwardMostMove;
2845 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2846 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2847 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2850 /* If last read ended with a partial line that we couldn't parse,
2851 prepend it to the new read and try again. */
2852 if (leftover_len > 0) {
2853 for (i=0; i<leftover_len; i++)
2854 buf[i] = buf[leftover_start + i];
2857 /* copy new characters into the buffer */
2858 bp = buf + leftover_len;
2859 buf_len=leftover_len;
2860 for (i=0; i<count; i++)
2863 if (data[i] == '\r')
2866 // join lines split by ICS?
2867 if (!appData.noJoin)
2870 Joining just consists of finding matches against the
2871 continuation sequence, and discarding that sequence
2872 if found instead of copying it. So, until a match
2873 fails, there's nothing to do since it might be the
2874 complete sequence, and thus, something we don't want
2877 if (data[i] == cont_seq[cmatch])
2880 if (cmatch == strlen(cont_seq))
2882 cmatch = 0; // complete match. just reset the counter
2885 it's possible for the ICS to not include the space
2886 at the end of the last word, making our [correct]
2887 join operation fuse two separate words. the server
2888 does this when the space occurs at the width setting.
2890 if (!buf_len || buf[buf_len-1] != ' ')
2901 match failed, so we have to copy what matched before
2902 falling through and copying this character. In reality,
2903 this will only ever be just the newline character, but
2904 it doesn't hurt to be precise.
2906 strncpy(bp, cont_seq, cmatch);
2918 buf[buf_len] = NULLCHAR;
2919 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2924 while (i < buf_len) {
2925 /* Deal with part of the TELNET option negotiation
2926 protocol. We refuse to do anything beyond the
2927 defaults, except that we allow the WILL ECHO option,
2928 which ICS uses to turn off password echoing when we are
2929 directly connected to it. We reject this option
2930 if localLineEditing mode is on (always on in xboard)
2931 and we are talking to port 23, which might be a real
2932 telnet server that will try to keep WILL ECHO on permanently.
2934 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2935 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2936 unsigned char option;
2938 switch ((unsigned char) buf[++i]) {
2940 if (appData.debugMode)
2941 fprintf(debugFP, "\n<WILL ");
2942 switch (option = (unsigned char) buf[++i]) {
2944 if (appData.debugMode)
2945 fprintf(debugFP, "ECHO ");
2946 /* Reply only if this is a change, according
2947 to the protocol rules. */
2948 if (remoteEchoOption) break;
2949 if (appData.localLineEditing &&
2950 atoi(appData.icsPort) == TN_PORT) {
2951 TelnetRequest(TN_DONT, TN_ECHO);
2954 TelnetRequest(TN_DO, TN_ECHO);
2955 remoteEchoOption = TRUE;
2959 if (appData.debugMode)
2960 fprintf(debugFP, "%d ", option);
2961 /* Whatever this is, we don't want it. */
2962 TelnetRequest(TN_DONT, option);
2967 if (appData.debugMode)
2968 fprintf(debugFP, "\n<WONT ");
2969 switch (option = (unsigned char) buf[++i]) {
2971 if (appData.debugMode)
2972 fprintf(debugFP, "ECHO ");
2973 /* Reply only if this is a change, according
2974 to the protocol rules. */
2975 if (!remoteEchoOption) break;
2977 TelnetRequest(TN_DONT, TN_ECHO);
2978 remoteEchoOption = FALSE;
2981 if (appData.debugMode)
2982 fprintf(debugFP, "%d ", (unsigned char) option);
2983 /* Whatever this is, it must already be turned
2984 off, because we never agree to turn on
2985 anything non-default, so according to the
2986 protocol rules, we don't reply. */
2991 if (appData.debugMode)
2992 fprintf(debugFP, "\n<DO ");
2993 switch (option = (unsigned char) buf[++i]) {
2995 /* Whatever this is, we refuse to do it. */
2996 if (appData.debugMode)
2997 fprintf(debugFP, "%d ", option);
2998 TelnetRequest(TN_WONT, option);
3003 if (appData.debugMode)
3004 fprintf(debugFP, "\n<DONT ");
3005 switch (option = (unsigned char) buf[++i]) {
3007 if (appData.debugMode)
3008 fprintf(debugFP, "%d ", option);
3009 /* Whatever this is, we are already not doing
3010 it, because we never agree to do anything
3011 non-default, so according to the protocol
3012 rules, we don't reply. */
3017 if (appData.debugMode)
3018 fprintf(debugFP, "\n<IAC ");
3019 /* Doubled IAC; pass it through */
3023 if (appData.debugMode)
3024 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3025 /* Drop all other telnet commands on the floor */
3028 if (oldi > next_out)
3029 SendToPlayer(&buf[next_out], oldi - next_out);
3035 /* OK, this at least will *usually* work */
3036 if (!loggedOn && looking_at(buf, &i, "ics%")) {
3040 if (loggedOn && !intfSet) {
3041 if (ics_type == ICS_ICC) {
3042 snprintf(str, MSG_SIZ,
3043 "/set-quietly interface %s\n/set-quietly style 12\n",
3045 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3046 strcat(str, "/set-2 51 1\n/set seek 1\n");
3047 } else if (ics_type == ICS_CHESSNET) {
3048 snprintf(str, MSG_SIZ, "/style 12\n");
3050 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3051 strcat(str, programVersion);
3052 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3053 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3054 strcat(str, "$iset seekremove 1\n$set seek 1\n");
3056 strcat(str, "$iset nohighlight 1\n");
3058 strcat(str, "$iset lock 1\n$style 12\n");
3061 NotifyFrontendLogin();
3065 if (started == STARTED_COMMENT) {
3066 /* Accumulate characters in comment */
3067 parse[parse_pos++] = buf[i];
3068 if (buf[i] == '\n') {
3069 parse[parse_pos] = NULLCHAR;
3070 if(chattingPartner>=0) {
3072 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3073 OutputChatMessage(chattingPartner, mess);
3074 chattingPartner = -1;
3075 next_out = i+1; // [HGM] suppress printing in ICS window
3077 if(!suppressKibitz) // [HGM] kibitz
3078 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3079 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3080 int nrDigit = 0, nrAlph = 0, j;
3081 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3082 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3083 parse[parse_pos] = NULLCHAR;
3084 // try to be smart: if it does not look like search info, it should go to
3085 // ICS interaction window after all, not to engine-output window.
3086 for(j=0; j<parse_pos; j++) { // count letters and digits
3087 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3088 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3089 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3091 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3092 int depth=0; float score;
3093 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3094 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3095 pvInfoList[forwardMostMove-1].depth = depth;
3096 pvInfoList[forwardMostMove-1].score = 100*score;
3098 OutputKibitz(suppressKibitz, parse);
3101 if(gameMode == IcsObserving) // restore original ICS messages
3102 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3103 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3105 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3106 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3107 SendToPlayer(tmp, strlen(tmp));
3109 next_out = i+1; // [HGM] suppress printing in ICS window
3111 started = STARTED_NONE;
3113 /* Don't match patterns against characters in comment */
3118 if (started == STARTED_CHATTER) {
3119 if (buf[i] != '\n') {
3120 /* Don't match patterns against characters in chatter */
3124 started = STARTED_NONE;
3125 if(suppressKibitz) next_out = i+1;
3128 /* Kludge to deal with rcmd protocol */
3129 if (firstTime && looking_at(buf, &i, "\001*")) {
3130 DisplayFatalError(&buf[1], 0, 1);
3136 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3139 if (appData.debugMode)
3140 fprintf(debugFP, "ics_type %d\n", ics_type);
3143 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3144 ics_type = ICS_FICS;
3146 if (appData.debugMode)
3147 fprintf(debugFP, "ics_type %d\n", ics_type);
3150 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3151 ics_type = ICS_CHESSNET;
3153 if (appData.debugMode)
3154 fprintf(debugFP, "ics_type %d\n", ics_type);
3159 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3160 looking_at(buf, &i, "Logging you in as \"*\"") ||
3161 looking_at(buf, &i, "will be \"*\""))) {
3162 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3166 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3168 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3169 DisplayIcsInteractionTitle(buf);
3170 have_set_title = TRUE;
3173 /* skip finger notes */
3174 if (started == STARTED_NONE &&
3175 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3176 (buf[i] == '1' && buf[i+1] == '0')) &&
3177 buf[i+2] == ':' && buf[i+3] == ' ') {
3178 started = STARTED_CHATTER;
3184 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3185 if(appData.seekGraph) {
3186 if(soughtPending && MatchSoughtLine(buf+i)) {
3187 i = strstr(buf+i, "rated") - buf;
3188 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3189 next_out = leftover_start = i;
3190 started = STARTED_CHATTER;
3191 suppressKibitz = TRUE;
3194 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3195 && looking_at(buf, &i, "* ads displayed")) {
3196 soughtPending = FALSE;
3201 if(appData.autoRefresh) {
3202 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3203 int s = (ics_type == ICS_ICC); // ICC format differs
3205 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3206 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3207 looking_at(buf, &i, "*% "); // eat prompt
3208 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3209 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3210 next_out = i; // suppress
3213 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3214 char *p = star_match[0];
3216 if(seekGraphUp) RemoveSeekAd(atoi(p));
3217 while(*p && *p++ != ' '); // next
3219 looking_at(buf, &i, "*% "); // eat prompt
3220 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3227 /* skip formula vars */
3228 if (started == STARTED_NONE &&
3229 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3230 started = STARTED_CHATTER;
3235 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3236 if (appData.autoKibitz && started == STARTED_NONE &&
3237 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3238 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3239 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3240 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3241 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3242 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3243 suppressKibitz = TRUE;
3244 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3246 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3247 && (gameMode == IcsPlayingWhite)) ||
3248 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3249 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3250 started = STARTED_CHATTER; // own kibitz we simply discard
3252 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3253 parse_pos = 0; parse[0] = NULLCHAR;
3254 savingComment = TRUE;
3255 suppressKibitz = gameMode != IcsObserving ? 2 :
3256 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3260 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3261 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3262 && atoi(star_match[0])) {
3263 // suppress the acknowledgements of our own autoKibitz
3265 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3266 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3267 SendToPlayer(star_match[0], strlen(star_match[0]));
3268 if(looking_at(buf, &i, "*% ")) // eat prompt
3269 suppressKibitz = FALSE;
3273 } // [HGM] kibitz: end of patch
3275 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3277 // [HGM] chat: intercept tells by users for which we have an open chat window
3279 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3280 looking_at(buf, &i, "* whispers:") ||
3281 looking_at(buf, &i, "* kibitzes:") ||
3282 looking_at(buf, &i, "* shouts:") ||
3283 looking_at(buf, &i, "* c-shouts:") ||
3284 looking_at(buf, &i, "--> * ") ||
3285 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3286 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3287 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3288 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3290 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3291 chattingPartner = -1;
3293 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3294 for(p=0; p<MAX_CHAT; p++) {
3295 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3296 talker[0] = '['; strcat(talker, "] ");
3297 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3298 chattingPartner = p; break;
3301 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3302 for(p=0; p<MAX_CHAT; p++) {
3303 if(!strcmp("kibitzes", chatPartner[p])) {
3304 talker[0] = '['; strcat(talker, "] ");
3305 chattingPartner = p; break;
3308 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3309 for(p=0; p<MAX_CHAT; p++) {
3310 if(!strcmp("whispers", chatPartner[p])) {
3311 talker[0] = '['; strcat(talker, "] ");
3312 chattingPartner = p; break;
3315 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3316 if(buf[i-8] == '-' && buf[i-3] == 't')
3317 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3318 if(!strcmp("c-shouts", chatPartner[p])) {
3319 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3320 chattingPartner = p; break;
3323 if(chattingPartner < 0)
3324 for(p=0; p<MAX_CHAT; p++) {
3325 if(!strcmp("shouts", chatPartner[p])) {
3326 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3327 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3328 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3329 chattingPartner = p; break;
3333 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3334 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3335 talker[0] = 0; Colorize(ColorTell, FALSE);
3336 chattingPartner = p; break;
3338 if(chattingPartner<0) i = oldi; else {
3339 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3340 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3341 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3342 started = STARTED_COMMENT;
3343 parse_pos = 0; parse[0] = NULLCHAR;
3344 savingComment = 3 + chattingPartner; // counts as TRUE
3345 suppressKibitz = TRUE;
3348 } // [HGM] chat: end of patch
3351 if (appData.zippyTalk || appData.zippyPlay) {
3352 /* [DM] Backup address for color zippy lines */
3354 if (loggedOn == TRUE)
3355 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3356 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3358 } // [DM] 'else { ' deleted
3360 /* Regular tells and says */
3361 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3362 looking_at(buf, &i, "* (your partner) tells you: ") ||
3363 looking_at(buf, &i, "* says: ") ||
3364 /* Don't color "message" or "messages" output */
3365 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3366 looking_at(buf, &i, "*. * at *:*: ") ||
3367 looking_at(buf, &i, "--* (*:*): ") ||
3368 /* Message notifications (same color as tells) */
3369 looking_at(buf, &i, "* has left a message ") ||
3370 looking_at(buf, &i, "* just sent you a message:\n") ||
3371 /* Whispers and kibitzes */
3372 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3373 looking_at(buf, &i, "* kibitzes: ") ||
3375 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3377 if (tkind == 1 && strchr(star_match[0], ':')) {
3378 /* Avoid "tells you:" spoofs in channels */
3381 if (star_match[0][0] == NULLCHAR ||
3382 strchr(star_match[0], ' ') ||
3383 (tkind == 3 && strchr(star_match[1], ' '))) {
3384 /* Reject bogus matches */
3387 if (appData.colorize) {
3388 if (oldi > next_out) {
3389 SendToPlayer(&buf[next_out], oldi - next_out);
3394 Colorize(ColorTell, FALSE);
3395 curColor = ColorTell;
3398 Colorize(ColorKibitz, FALSE);
3399 curColor = ColorKibitz;
3402 p = strrchr(star_match[1], '(');
3409 Colorize(ColorChannel1, FALSE);
3410 curColor = ColorChannel1;
3412 Colorize(ColorChannel, FALSE);
3413 curColor = ColorChannel;
3417 curColor = ColorNormal;
3421 if (started == STARTED_NONE && appData.autoComment &&
3422 (gameMode == IcsObserving ||
3423 gameMode == IcsPlayingWhite ||
3424 gameMode == IcsPlayingBlack)) {
3425 parse_pos = i - oldi;
3426 memcpy(parse, &buf[oldi], parse_pos);
3427 parse[parse_pos] = NULLCHAR;
3428 started = STARTED_COMMENT;
3429 savingComment = TRUE;
3431 started = STARTED_CHATTER;
3432 savingComment = FALSE;
3439 if (looking_at(buf, &i, "* s-shouts: ") ||
3440 looking_at(buf, &i, "* c-shouts: ")) {
3441 if (appData.colorize) {
3442 if (oldi > next_out) {
3443 SendToPlayer(&buf[next_out], oldi - next_out);
3446 Colorize(ColorSShout, FALSE);
3447 curColor = ColorSShout;
3450 started = STARTED_CHATTER;
3454 if (looking_at(buf, &i, "--->")) {
3459 if (looking_at(buf, &i, "* shouts: ") ||
3460 looking_at(buf, &i, "--> ")) {
3461 if (appData.colorize) {
3462 if (oldi > next_out) {
3463 SendToPlayer(&buf[next_out], oldi - next_out);
3466 Colorize(ColorShout, FALSE);
3467 curColor = ColorShout;
3470 started = STARTED_CHATTER;
3474 if (looking_at( buf, &i, "Challenge:")) {
3475 if (appData.colorize) {
3476 if (oldi > next_out) {
3477 SendToPlayer(&buf[next_out], oldi - next_out);
3480 Colorize(ColorChallenge, FALSE);
3481 curColor = ColorChallenge;
3487 if (looking_at(buf, &i, "* offers you") ||
3488 looking_at(buf, &i, "* offers to be") ||
3489 looking_at(buf, &i, "* would like to") ||
3490 looking_at(buf, &i, "* requests to") ||
3491 looking_at(buf, &i, "Your opponent offers") ||
3492 looking_at(buf, &i, "Your opponent requests")) {
3494 if (appData.colorize) {
3495 if (oldi > next_out) {
3496 SendToPlayer(&buf[next_out], oldi - next_out);
3499 Colorize(ColorRequest, FALSE);
3500 curColor = ColorRequest;
3505 if (looking_at(buf, &i, "* (*) seeking")) {
3506 if (appData.colorize) {
3507 if (oldi > next_out) {
3508 SendToPlayer(&buf[next_out], oldi - next_out);
3511 Colorize(ColorSeek, FALSE);
3512 curColor = ColorSeek;
3517 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3519 if (looking_at(buf, &i, "\\ ")) {
3520 if (prevColor != ColorNormal) {
3521 if (oldi > next_out) {
3522 SendToPlayer(&buf[next_out], oldi - next_out);
3525 Colorize(prevColor, TRUE);
3526 curColor = prevColor;
3528 if (savingComment) {
3529 parse_pos = i - oldi;
3530 memcpy(parse, &buf[oldi], parse_pos);
3531 parse[parse_pos] = NULLCHAR;
3532 started = STARTED_COMMENT;
3533 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3534 chattingPartner = savingComment - 3; // kludge to remember the box
3536 started = STARTED_CHATTER;
3541 if (looking_at(buf, &i, "Black Strength :") ||
3542 looking_at(buf, &i, "<<< style 10 board >>>") ||
3543 looking_at(buf, &i, "<10>") ||
3544 looking_at(buf, &i, "#@#")) {
3545 /* Wrong board style */
3547 SendToICS(ics_prefix);
3548 SendToICS("set style 12\n");
3549 SendToICS(ics_prefix);
3550 SendToICS("refresh\n");
3554 if (looking_at(buf, &i, "login:")) {
3555 if (!have_sent_ICS_logon) {
3557 have_sent_ICS_logon = 1;
3558 else // no init script was found
3559 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3560 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3561 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3566 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3567 (looking_at(buf, &i, "\n<12> ") ||
3568 looking_at(buf, &i, "<12> "))) {
3570 if (oldi > next_out) {
3571 SendToPlayer(&buf[next_out], oldi - next_out);
3574 started = STARTED_BOARD;
3579 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3580 looking_at(buf, &i, "<b1> ")) {
3581 if (oldi > next_out) {
3582 SendToPlayer(&buf[next_out], oldi - next_out);
3585 started = STARTED_HOLDINGS;
3590 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3592 /* Header for a move list -- first line */
3594 switch (ics_getting_history) {
3598 case BeginningOfGame:
3599 /* User typed "moves" or "oldmoves" while we
3600 were idle. Pretend we asked for these
3601 moves and soak them up so user can step
3602 through them and/or save them.
3605 gameMode = IcsObserving;
3608 ics_getting_history = H_GOT_UNREQ_HEADER;
3610 case EditGame: /*?*/
3611 case EditPosition: /*?*/
3612 /* Should above feature work in these modes too? */
3613 /* For now it doesn't */
3614 ics_getting_history = H_GOT_UNWANTED_HEADER;
3617 ics_getting_history = H_GOT_UNWANTED_HEADER;
3622 /* Is this the right one? */
3623 if (gameInfo.white && gameInfo.black &&
3624 strcmp(gameInfo.white, star_match[0]) == 0 &&
3625 strcmp(gameInfo.black, star_match[2]) == 0) {
3627 ics_getting_history = H_GOT_REQ_HEADER;
3630 case H_GOT_REQ_HEADER:
3631 case H_GOT_UNREQ_HEADER:
3632 case H_GOT_UNWANTED_HEADER:
3633 case H_GETTING_MOVES:
3634 /* Should not happen */
3635 DisplayError(_("Error gathering move list: two headers"), 0);
3636 ics_getting_history = H_FALSE;
3640 /* Save player ratings into gameInfo if needed */
3641 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3642 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3643 (gameInfo.whiteRating == -1 ||
3644 gameInfo.blackRating == -1)) {
3646 gameInfo.whiteRating = string_to_rating(star_match[1]);
3647 gameInfo.blackRating = string_to_rating(star_match[3]);
3648 if (appData.debugMode)
3649 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3650 gameInfo.whiteRating, gameInfo.blackRating);
3655 if (looking_at(buf, &i,
3656 "* * match, initial time: * minute*, increment: * second")) {
3657 /* Header for a move list -- second line */
3658 /* Initial board will follow if this is a wild game */
3659 if (gameInfo.event != NULL) free(gameInfo.event);
3660 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3661 gameInfo.event = StrSave(str);
3662 /* [HGM] we switched variant. Translate boards if needed. */
3663 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3667 if (looking_at(buf, &i, "Move ")) {
3668 /* Beginning of a move list */
3669 switch (ics_getting_history) {
3671 /* Normally should not happen */
3672 /* Maybe user hit reset while we were parsing */
3675 /* Happens if we are ignoring a move list that is not
3676 * the one we just requested. Common if the user
3677 * tries to observe two games without turning off
3680 case H_GETTING_MOVES:
3681 /* Should not happen */
3682 DisplayError(_("Error gathering move list: nested"), 0);
3683 ics_getting_history = H_FALSE;
3685 case H_GOT_REQ_HEADER:
3686 ics_getting_history = H_GETTING_MOVES;
3687 started = STARTED_MOVES;
3689 if (oldi > next_out) {
3690 SendToPlayer(&buf[next_out], oldi - next_out);
3693 case H_GOT_UNREQ_HEADER:
3694 ics_getting_history = H_GETTING_MOVES;
3695 started = STARTED_MOVES_NOHIDE;
3698 case H_GOT_UNWANTED_HEADER:
3699 ics_getting_history = H_FALSE;
3705 if (looking_at(buf, &i, "% ") ||
3706 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3707 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3708 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3709 soughtPending = FALSE;
3713 if(suppressKibitz) next_out = i;
3714 savingComment = FALSE;
3718 case STARTED_MOVES_NOHIDE:
3719 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3720 parse[parse_pos + i - oldi] = NULLCHAR;
3721 ParseGameHistory(parse);
3723 if (appData.zippyPlay && first.initDone) {
3724 FeedMovesToProgram(&first, forwardMostMove);
3725 if (gameMode == IcsPlayingWhite) {
3726 if (WhiteOnMove(forwardMostMove)) {
3727 if (first.sendTime) {
3728 if (first.useColors) {
3729 SendToProgram("black\n", &first);
3731 SendTimeRemaining(&first, TRUE);
3733 if (first.useColors) {
3734 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3736 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3737 first.maybeThinking = TRUE;
3739 if (first.usePlayother) {
3740 if (first.sendTime) {
3741 SendTimeRemaining(&first, TRUE);
3743 SendToProgram("playother\n", &first);
3749 } else if (gameMode == IcsPlayingBlack) {
3750 if (!WhiteOnMove(forwardMostMove)) {
3751 if (first.sendTime) {
3752 if (first.useColors) {
3753 SendToProgram("white\n", &first);
3755 SendTimeRemaining(&first, FALSE);
3757 if (first.useColors) {
3758 SendToProgram("black\n", &first);
3760 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3761 first.maybeThinking = TRUE;
3763 if (first.usePlayother) {
3764 if (first.sendTime) {
3765 SendTimeRemaining(&first, FALSE);
3767 SendToProgram("playother\n", &first);
3776 if (gameMode == IcsObserving && ics_gamenum == -1) {
3777 /* Moves came from oldmoves or moves command
3778 while we weren't doing anything else.
3780 currentMove = forwardMostMove;
3781 ClearHighlights();/*!!could figure this out*/
3782 flipView = appData.flipView;
3783 DrawPosition(TRUE, boards[currentMove]);
3784 DisplayBothClocks();
3785 snprintf(str, MSG_SIZ, "%s %s %s",
3786 gameInfo.white, _("vs."), gameInfo.black);
3790 /* Moves were history of an active game */
3791 if (gameInfo.resultDetails != NULL) {
3792 free(gameInfo.resultDetails);
3793 gameInfo.resultDetails = NULL;
3796 HistorySet(parseList, backwardMostMove,
3797 forwardMostMove, currentMove-1);
3798 DisplayMove(currentMove - 1);
3799 if (started == STARTED_MOVES) next_out = i;
3800 started = STARTED_NONE;
3801 ics_getting_history = H_FALSE;
3804 case STARTED_OBSERVE:
3805 started = STARTED_NONE;
3806 SendToICS(ics_prefix);
3807 SendToICS("refresh\n");
3813 if(bookHit) { // [HGM] book: simulate book reply
3814 static char bookMove[MSG_SIZ]; // a bit generous?
3816 programStats.nodes = programStats.depth = programStats.time =
3817 programStats.score = programStats.got_only_move = 0;
3818 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3820 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3821 strcat(bookMove, bookHit);
3822 HandleMachineMove(bookMove, &first);
3827 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3828 started == STARTED_HOLDINGS ||
3829 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3830 /* Accumulate characters in move list or board */
3831 parse[parse_pos++] = buf[i];
3834 /* Start of game messages. Mostly we detect start of game
3835 when the first board image arrives. On some versions
3836 of the ICS, though, we need to do a "refresh" after starting
3837 to observe in order to get the current board right away. */
3838 if (looking_at(buf, &i, "Adding game * to observation list")) {
3839 started = STARTED_OBSERVE;
3843 /* Handle auto-observe */
3844 if (appData.autoObserve &&
3845 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3846 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3848 /* Choose the player that was highlighted, if any. */
3849 if (star_match[0][0] == '\033' ||
3850 star_match[1][0] != '\033') {
3851 player = star_match[0];
3853 player = star_match[2];
3855 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3856 ics_prefix, StripHighlightAndTitle(player));
3859 /* Save ratings from notify string */
3860 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3861 player1Rating = string_to_rating(star_match[1]);
3862 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3863 player2Rating = string_to_rating(star_match[3]);
3865 if (appData.debugMode)
3867 "Ratings from 'Game notification:' %s %d, %s %d\n",
3868 player1Name, player1Rating,
3869 player2Name, player2Rating);
3874 /* Deal with automatic examine mode after a game,
3875 and with IcsObserving -> IcsExamining transition */
3876 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3877 looking_at(buf, &i, "has made you an examiner of game *")) {
3879 int gamenum = atoi(star_match[0]);
3880 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3881 gamenum == ics_gamenum) {
3882 /* We were already playing or observing this game;
3883 no need to refetch history */
3884 gameMode = IcsExamining;
3886 pauseExamForwardMostMove = forwardMostMove;
3887 } else if (currentMove < forwardMostMove) {
3888 ForwardInner(forwardMostMove);
3891 /* I don't think this case really can happen */
3892 SendToICS(ics_prefix);
3893 SendToICS("refresh\n");
3898 /* Error messages */
3899 // if (ics_user_moved) {
3900 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3901 if (looking_at(buf, &i, "Illegal move") ||
3902 looking_at(buf, &i, "Not a legal move") ||
3903 looking_at(buf, &i, "Your king is in check") ||
3904 looking_at(buf, &i, "It isn't your turn") ||
3905 looking_at(buf, &i, "It is not your move")) {
3907 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3908 currentMove = forwardMostMove-1;
3909 DisplayMove(currentMove - 1); /* before DMError */
3910 DrawPosition(FALSE, boards[currentMove]);
3911 SwitchClocks(forwardMostMove-1); // [HGM] race
3912 DisplayBothClocks();
3914 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3920 if (looking_at(buf, &i, "still have time") ||
3921 looking_at(buf, &i, "not out of time") ||
3922 looking_at(buf, &i, "either player is out of time") ||
3923 looking_at(buf, &i, "has timeseal; checking")) {
3924 /* We must have called his flag a little too soon */
3925 whiteFlag = blackFlag = FALSE;
3929 if (looking_at(buf, &i, "added * seconds to") ||
3930 looking_at(buf, &i, "seconds were added to")) {
3931 /* Update the clocks */
3932 SendToICS(ics_prefix);
3933 SendToICS("refresh\n");
3937 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3938 ics_clock_paused = TRUE;
3943 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3944 ics_clock_paused = FALSE;
3949 /* Grab player ratings from the Creating: message.
3950 Note we have to check for the special case when
3951 the ICS inserts things like [white] or [black]. */
3952 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3953 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3955 0 player 1 name (not necessarily white)
3957 2 empty, white, or black (IGNORED)
3958 3 player 2 name (not necessarily black)
3961 The names/ratings are sorted out when the game
3962 actually starts (below).
3964 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3965 player1Rating = string_to_rating(star_match[1]);
3966 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3967 player2Rating = string_to_rating(star_match[4]);
3969 if (appData.debugMode)
3971 "Ratings from 'Creating:' %s %d, %s %d\n",
3972 player1Name, player1Rating,
3973 player2Name, player2Rating);
3978 /* Improved generic start/end-of-game messages */
3979 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3980 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3981 /* If tkind == 0: */
3982 /* star_match[0] is the game number */
3983 /* [1] is the white player's name */
3984 /* [2] is the black player's name */
3985 /* For end-of-game: */
3986 /* [3] is the reason for the game end */
3987 /* [4] is a PGN end game-token, preceded by " " */
3988 /* For start-of-game: */
3989 /* [3] begins with "Creating" or "Continuing" */
3990 /* [4] is " *" or empty (don't care). */
3991 int gamenum = atoi(star_match[0]);
3992 char *whitename, *blackname, *why, *endtoken;
3993 ChessMove endtype = EndOfFile;
3996 whitename = star_match[1];
3997 blackname = star_match[2];
3998 why = star_match[3];
3999 endtoken = star_match[4];
4001 whitename = star_match[1];
4002 blackname = star_match[3];
4003 why = star_match[5];
4004 endtoken = star_match[6];
4007 /* Game start messages */
4008 if (strncmp(why, "Creating ", 9) == 0 ||
4009 strncmp(why, "Continuing ", 11) == 0) {
4010 gs_gamenum = gamenum;
4011 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4012 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4013 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4015 if (appData.zippyPlay) {
4016 ZippyGameStart(whitename, blackname);
4019 partnerBoardValid = FALSE; // [HGM] bughouse
4023 /* Game end messages */
4024 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4025 ics_gamenum != gamenum) {
4028 while (endtoken[0] == ' ') endtoken++;
4029 switch (endtoken[0]) {
4032 endtype = GameUnfinished;
4035 endtype = BlackWins;
4038 if (endtoken[1] == '/')
4039 endtype = GameIsDrawn;
4041 endtype = WhiteWins;
4044 GameEnds(endtype, why, GE_ICS);
4046 if (appData.zippyPlay && first.initDone) {
4047 ZippyGameEnd(endtype, why);
4048 if (first.pr == NoProc) {
4049 /* Start the next process early so that we'll
4050 be ready for the next challenge */
4051 StartChessProgram(&first);
4053 /* Send "new" early, in case this command takes
4054 a long time to finish, so that we'll be ready
4055 for the next challenge. */
4056 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4060 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4064 if (looking_at(buf, &i, "Removing game * from observation") ||
4065 looking_at(buf, &i, "no longer observing game *") ||
4066 looking_at(buf, &i, "Game * (*) has no examiners")) {
4067 if (gameMode == IcsObserving &&
4068 atoi(star_match[0]) == ics_gamenum)
4070 /* icsEngineAnalyze */
4071 if (appData.icsEngineAnalyze) {
4078 ics_user_moved = FALSE;
4083 if (looking_at(buf, &i, "no longer examining game *")) {
4084 if (gameMode == IcsExamining &&
4085 atoi(star_match[0]) == ics_gamenum)
4089 ics_user_moved = FALSE;
4094 /* Advance leftover_start past any newlines we find,
4095 so only partial lines can get reparsed */
4096 if (looking_at(buf, &i, "\n")) {
4097 prevColor = curColor;
4098 if (curColor != ColorNormal) {
4099 if (oldi > next_out) {
4100 SendToPlayer(&buf[next_out], oldi - next_out);
4103 Colorize(ColorNormal, FALSE);
4104 curColor = ColorNormal;
4106 if (started == STARTED_BOARD) {
4107 started = STARTED_NONE;
4108 parse[parse_pos] = NULLCHAR;
4109 ParseBoard12(parse);
4112 /* Send premove here */
4113 if (appData.premove) {
4115 if (currentMove == 0 &&
4116 gameMode == IcsPlayingWhite &&
4117 appData.premoveWhite) {
4118 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4119 if (appData.debugMode)
4120 fprintf(debugFP, "Sending premove:\n");
4122 } else if (currentMove == 1 &&
4123 gameMode == IcsPlayingBlack &&
4124 appData.premoveBlack) {
4125 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4126 if (appData.debugMode)
4127 fprintf(debugFP, "Sending premove:\n");
4129 } else if (gotPremove) {
4131 ClearPremoveHighlights();
4132 if (appData.debugMode)
4133 fprintf(debugFP, "Sending premove:\n");
4134 UserMoveEvent(premoveFromX, premoveFromY,
4135 premoveToX, premoveToY,
4140 /* Usually suppress following prompt */
4141 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4142 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4143 if (looking_at(buf, &i, "*% ")) {
4144 savingComment = FALSE;
4149 } else if (started == STARTED_HOLDINGS) {
4151 char new_piece[MSG_SIZ];
4152 started = STARTED_NONE;
4153 parse[parse_pos] = NULLCHAR;
4154 if (appData.debugMode)
4155 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4156 parse, currentMove);
4157 if (sscanf(parse, " game %d", &gamenum) == 1) {
4158 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4159 if (gameInfo.variant == VariantNormal) {
4160 /* [HGM] We seem to switch variant during a game!
4161 * Presumably no holdings were displayed, so we have
4162 * to move the position two files to the right to
4163 * create room for them!
4165 VariantClass newVariant;
4166 switch(gameInfo.boardWidth) { // base guess on board width
4167 case 9: newVariant = VariantShogi; break;
4168 case 10: newVariant = VariantGreat; break;
4169 default: newVariant = VariantCrazyhouse; break;
4171 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4172 /* Get a move list just to see the header, which
4173 will tell us whether this is really bug or zh */
4174 if (ics_getting_history == H_FALSE) {
4175 ics_getting_history = H_REQUESTED;
4176 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4180 new_piece[0] = NULLCHAR;
4181 sscanf(parse, "game %d white [%s black [%s <- %s",
4182 &gamenum, white_holding, black_holding,
4184 white_holding[strlen(white_holding)-1] = NULLCHAR;
4185 black_holding[strlen(black_holding)-1] = NULLCHAR;
4186 /* [HGM] copy holdings to board holdings area */
4187 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4188 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4189 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4191 if (appData.zippyPlay && first.initDone) {
4192 ZippyHoldings(white_holding, black_holding,
4196 if (tinyLayout || smallLayout) {
4197 char wh[16], bh[16];
4198 PackHolding(wh, white_holding);
4199 PackHolding(bh, black_holding);
4200 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4201 gameInfo.white, gameInfo.black);
4203 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4204 gameInfo.white, white_holding, _("vs."),
4205 gameInfo.black, black_holding);
4207 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4208 DrawPosition(FALSE, boards[currentMove]);
4210 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4211 sscanf(parse, "game %d white [%s black [%s <- %s",
4212 &gamenum, white_holding, black_holding,
4214 white_holding[strlen(white_holding)-1] = NULLCHAR;
4215 black_holding[strlen(black_holding)-1] = NULLCHAR;
4216 /* [HGM] copy holdings to partner-board holdings area */
4217 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4218 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4219 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4220 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4221 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4224 /* Suppress following prompt */
4225 if (looking_at(buf, &i, "*% ")) {
4226 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4227 savingComment = FALSE;
4235 i++; /* skip unparsed character and loop back */
4238 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4239 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4240 // SendToPlayer(&buf[next_out], i - next_out);
4241 started != STARTED_HOLDINGS && leftover_start > next_out) {
4242 SendToPlayer(&buf[next_out], leftover_start - next_out);
4246 leftover_len = buf_len - leftover_start;
4247 /* if buffer ends with something we couldn't parse,
4248 reparse it after appending the next read */
4250 } else if (count == 0) {
4251 RemoveInputSource(isr);
4252 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4254 DisplayFatalError(_("Error reading from ICS"), error, 1);
4259 /* Board style 12 looks like this:
4261 <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
4263 * The "<12> " is stripped before it gets to this routine. The two
4264 * trailing 0's (flip state and clock ticking) are later addition, and
4265 * some chess servers may not have them, or may have only the first.
4266 * Additional trailing fields may be added in the future.
4269 #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"
4271 #define RELATION_OBSERVING_PLAYED 0
4272 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4273 #define RELATION_PLAYING_MYMOVE 1
4274 #define RELATION_PLAYING_NOTMYMOVE -1
4275 #define RELATION_EXAMINING 2
4276 #define RELATION_ISOLATED_BOARD -3
4277 #define RELATION_STARTING_POSITION -4 /* FICS only */
4280 ParseBoard12 (char *string)
4284 char *bookHit = NULL; // [HGM] book
4286 GameMode newGameMode;
4287 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4288 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4289 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4290 char to_play, board_chars[200];
4291 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4292 char black[32], white[32];
4294 int prevMove = currentMove;
4297 int fromX, fromY, toX, toY;
4299 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4300 Boolean weird = FALSE, reqFlag = FALSE;
4302 fromX = fromY = toX = toY = -1;
4306 if (appData.debugMode)
4307 fprintf(debugFP, "Parsing board: %s\n", string);
4309 move_str[0] = NULLCHAR;
4310 elapsed_time[0] = NULLCHAR;
4311 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4313 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4314 if(string[i] == ' ') { ranks++; files = 0; }
4316 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4319 for(j = 0; j <i; j++) board_chars[j] = string[j];
4320 board_chars[i] = '\0';
4323 n = sscanf(string, PATTERN, &to_play, &double_push,
4324 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4325 &gamenum, white, black, &relation, &basetime, &increment,
4326 &white_stren, &black_stren, &white_time, &black_time,
4327 &moveNum, str, elapsed_time, move_str, &ics_flip,
4331 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4332 DisplayError(str, 0);
4336 /* Convert the move number to internal form */
4337 moveNum = (moveNum - 1) * 2;
4338 if (to_play == 'B') moveNum++;
4339 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4340 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4346 case RELATION_OBSERVING_PLAYED:
4347 case RELATION_OBSERVING_STATIC:
4348 if (gamenum == -1) {
4349 /* Old ICC buglet */
4350 relation = RELATION_OBSERVING_STATIC;
4352 newGameMode = IcsObserving;
4354 case RELATION_PLAYING_MYMOVE:
4355 case RELATION_PLAYING_NOTMYMOVE:
4357 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4358 IcsPlayingWhite : IcsPlayingBlack;
4359 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4361 case RELATION_EXAMINING:
4362 newGameMode = IcsExamining;
4364 case RELATION_ISOLATED_BOARD:
4366 /* Just display this board. If user was doing something else,
4367 we will forget about it until the next board comes. */
4368 newGameMode = IcsIdle;
4370 case RELATION_STARTING_POSITION:
4371 newGameMode = gameMode;
4375 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4376 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4377 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4378 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4379 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4380 static int lastBgGame = -1;
4382 for (k = 0; k < ranks; k++) {
4383 for (j = 0; j < files; j++)
4384 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4385 if(gameInfo.holdingsWidth > 1) {
4386 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4387 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4390 CopyBoard(partnerBoard, board);
4391 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4392 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4393 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4394 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4395 if(toSqr = strchr(str, '-')) {
4396 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4397 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4398 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4399 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4400 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4401 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4403 DisplayWhiteClock(white_time*fac, to_play == 'W');
4404 DisplayBlackClock(black_time*fac, to_play != 'W');
4405 activePartner = to_play;
4406 if(gamenum != lastBgGame) {
4408 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4411 lastBgGame = gamenum;
4412 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4413 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4414 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4415 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4416 if(!twoBoards) DisplayMessage(partnerStatus, "");
4417 partnerBoardValid = TRUE;
4421 if(appData.dualBoard && appData.bgObserve) {
4422 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4423 SendToICS(ics_prefix), SendToICS("pobserve\n");
4424 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4426 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4431 /* Modify behavior for initial board display on move listing
4434 switch (ics_getting_history) {
4438 case H_GOT_REQ_HEADER:
4439 case H_GOT_UNREQ_HEADER:
4440 /* This is the initial position of the current game */
4441 gamenum = ics_gamenum;
4442 moveNum = 0; /* old ICS bug workaround */
4443 if (to_play == 'B') {
4444 startedFromSetupPosition = TRUE;
4445 blackPlaysFirst = TRUE;
4447 if (forwardMostMove == 0) forwardMostMove = 1;
4448 if (backwardMostMove == 0) backwardMostMove = 1;
4449 if (currentMove == 0) currentMove = 1;
4451 newGameMode = gameMode;
4452 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4454 case H_GOT_UNWANTED_HEADER:
4455 /* This is an initial board that we don't want */
4457 case H_GETTING_MOVES:
4458 /* Should not happen */
4459 DisplayError(_("Error gathering move list: extra board"), 0);
4460 ics_getting_history = H_FALSE;
4464 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4465 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4466 weird && (int)gameInfo.variant < (int)VariantShogi) {
4467 /* [HGM] We seem to have switched variant unexpectedly
4468 * Try to guess new variant from board size
4470 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4471 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4472 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4473 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4474 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4475 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4476 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4477 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4478 /* Get a move list just to see the header, which
4479 will tell us whether this is really bug or zh */
4480 if (ics_getting_history == H_FALSE) {
4481 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4482 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4487 /* Take action if this is the first board of a new game, or of a
4488 different game than is currently being displayed. */
4489 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4490 relation == RELATION_ISOLATED_BOARD) {
4492 /* Forget the old game and get the history (if any) of the new one */
4493 if (gameMode != BeginningOfGame) {
4497 if (appData.autoRaiseBoard) BoardToTop();
4499 if (gamenum == -1) {
4500 newGameMode = IcsIdle;
4501 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4502 appData.getMoveList && !reqFlag) {
4503 /* Need to get game history */
4504 ics_getting_history = H_REQUESTED;
4505 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4509 /* Initially flip the board to have black on the bottom if playing
4510 black or if the ICS flip flag is set, but let the user change
4511 it with the Flip View button. */
4512 flipView = appData.autoFlipView ?
4513 (newGameMode == IcsPlayingBlack) || ics_flip :
4516 /* Done with values from previous mode; copy in new ones */
4517 gameMode = newGameMode;
4519 ics_gamenum = gamenum;
4520 if (gamenum == gs_gamenum) {
4521 int klen = strlen(gs_kind);
4522 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4523 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4524 gameInfo.event = StrSave(str);
4526 gameInfo.event = StrSave("ICS game");
4528 gameInfo.site = StrSave(appData.icsHost);
4529 gameInfo.date = PGNDate();
4530 gameInfo.round = StrSave("-");
4531 gameInfo.white = StrSave(white);
4532 gameInfo.black = StrSave(black);
4533 timeControl = basetime * 60 * 1000;
4535 timeIncrement = increment * 1000;
4536 movesPerSession = 0;
4537 gameInfo.timeControl = TimeControlTagValue();
4538 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4539 if (appData.debugMode) {
4540 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4541 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4542 setbuf(debugFP, NULL);
4545 gameInfo.outOfBook = NULL;
4547 /* Do we have the ratings? */
4548 if (strcmp(player1Name, white) == 0 &&
4549 strcmp(player2Name, black) == 0) {
4550 if (appData.debugMode)
4551 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4552 player1Rating, player2Rating);
4553 gameInfo.whiteRating = player1Rating;
4554 gameInfo.blackRating = player2Rating;
4555 } else if (strcmp(player2Name, white) == 0 &&
4556 strcmp(player1Name, black) == 0) {
4557 if (appData.debugMode)
4558 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4559 player2Rating, player1Rating);
4560 gameInfo.whiteRating = player2Rating;
4561 gameInfo.blackRating = player1Rating;
4563 player1Name[0] = player2Name[0] = NULLCHAR;
4565 /* Silence shouts if requested */
4566 if (appData.quietPlay &&
4567 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4568 SendToICS(ics_prefix);
4569 SendToICS("set shout 0\n");
4573 /* Deal with midgame name changes */
4575 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4576 if (gameInfo.white) free(gameInfo.white);
4577 gameInfo.white = StrSave(white);
4579 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4580 if (gameInfo.black) free(gameInfo.black);
4581 gameInfo.black = StrSave(black);
4585 /* Throw away game result if anything actually changes in examine mode */
4586 if (gameMode == IcsExamining && !newGame) {
4587 gameInfo.result = GameUnfinished;
4588 if (gameInfo.resultDetails != NULL) {
4589 free(gameInfo.resultDetails);
4590 gameInfo.resultDetails = NULL;
4594 /* In pausing && IcsExamining mode, we ignore boards coming
4595 in if they are in a different variation than we are. */
4596 if (pauseExamInvalid) return;
4597 if (pausing && gameMode == IcsExamining) {
4598 if (moveNum <= pauseExamForwardMostMove) {
4599 pauseExamInvalid = TRUE;
4600 forwardMostMove = pauseExamForwardMostMove;
4605 if (appData.debugMode) {
4606 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4608 /* Parse the board */
4609 for (k = 0; k < ranks; k++) {
4610 for (j = 0; j < files; j++)
4611 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4612 if(gameInfo.holdingsWidth > 1) {
4613 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4614 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4617 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4618 board[5][BOARD_RGHT+1] = WhiteAngel;
4619 board[6][BOARD_RGHT+1] = WhiteMarshall;
4620 board[1][0] = BlackMarshall;
4621 board[2][0] = BlackAngel;
4622 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4624 CopyBoard(boards[moveNum], board);
4625 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4627 startedFromSetupPosition =
4628 !CompareBoards(board, initialPosition);
4629 if(startedFromSetupPosition)
4630 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4633 /* [HGM] Set castling rights. Take the outermost Rooks,
4634 to make it also work for FRC opening positions. Note that board12
4635 is really defective for later FRC positions, as it has no way to
4636 indicate which Rook can castle if they are on the same side of King.
4637 For the initial position we grant rights to the outermost Rooks,
4638 and remember thos rights, and we then copy them on positions
4639 later in an FRC game. This means WB might not recognize castlings with
4640 Rooks that have moved back to their original position as illegal,
4641 but in ICS mode that is not its job anyway.
4643 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4644 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4646 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4647 if(board[0][i] == WhiteRook) j = i;
4648 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4649 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4650 if(board[0][i] == WhiteRook) j = i;
4651 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4652 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4653 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4654 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4655 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4656 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4657 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4659 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4660 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4661 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4662 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4663 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4664 if(board[BOARD_HEIGHT-1][k] == bKing)
4665 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4666 if(gameInfo.variant == VariantTwoKings) {
4667 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4668 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4669 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4672 r = boards[moveNum][CASTLING][0] = initialRights[0];
4673 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4674 r = boards[moveNum][CASTLING][1] = initialRights[1];
4675 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4676 r = boards[moveNum][CASTLING][3] = initialRights[3];
4677 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4678 r = boards[moveNum][CASTLING][4] = initialRights[4];
4679 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4680 /* wildcastle kludge: always assume King has rights */
4681 r = boards[moveNum][CASTLING][2] = initialRights[2];
4682 r = boards[moveNum][CASTLING][5] = initialRights[5];
4684 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4685 boards[moveNum][EP_STATUS] = EP_NONE;
4686 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4687 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4688 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4691 if (ics_getting_history == H_GOT_REQ_HEADER ||
4692 ics_getting_history == H_GOT_UNREQ_HEADER) {
4693 /* This was an initial position from a move list, not
4694 the current position */
4698 /* Update currentMove and known move number limits */
4699 newMove = newGame || moveNum > forwardMostMove;
4702 forwardMostMove = backwardMostMove = currentMove = moveNum;
4703 if (gameMode == IcsExamining && moveNum == 0) {
4704 /* Workaround for ICS limitation: we are not told the wild
4705 type when starting to examine a game. But if we ask for
4706 the move list, the move list header will tell us */
4707 ics_getting_history = H_REQUESTED;
4708 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4711 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4712 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4714 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4715 /* [HGM] applied this also to an engine that is silently watching */
4716 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4717 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4718 gameInfo.variant == currentlyInitializedVariant) {
4719 takeback = forwardMostMove - moveNum;
4720 for (i = 0; i < takeback; i++) {
4721 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4722 SendToProgram("undo\n", &first);
4727 forwardMostMove = moveNum;
4728 if (!pausing || currentMove > forwardMostMove)
4729 currentMove = forwardMostMove;
4731 /* New part of history that is not contiguous with old part */
4732 if (pausing && gameMode == IcsExamining) {
4733 pauseExamInvalid = TRUE;
4734 forwardMostMove = pauseExamForwardMostMove;
4737 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4739 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4740 // [HGM] when we will receive the move list we now request, it will be
4741 // fed to the engine from the first move on. So if the engine is not
4742 // in the initial position now, bring it there.
4743 InitChessProgram(&first, 0);
4746 ics_getting_history = H_REQUESTED;
4747 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4750 forwardMostMove = backwardMostMove = currentMove = moveNum;
4753 /* Update the clocks */
4754 if (strchr(elapsed_time, '.')) {
4756 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4757 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4759 /* Time is in seconds */
4760 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4761 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4766 if (appData.zippyPlay && newGame &&
4767 gameMode != IcsObserving && gameMode != IcsIdle &&
4768 gameMode != IcsExamining)
4769 ZippyFirstBoard(moveNum, basetime, increment);
4772 /* Put the move on the move list, first converting
4773 to canonical algebraic form. */
4775 if (appData.debugMode) {
4776 int f = forwardMostMove;
4777 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4778 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4779 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4780 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4781 fprintf(debugFP, "moveNum = %d\n", moveNum);
4782 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4783 setbuf(debugFP, NULL);
4785 if (moveNum <= backwardMostMove) {
4786 /* We don't know what the board looked like before
4788 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4789 strcat(parseList[moveNum - 1], " ");
4790 strcat(parseList[moveNum - 1], elapsed_time);
4791 moveList[moveNum - 1][0] = NULLCHAR;
4792 } else if (strcmp(move_str, "none") == 0) {
4793 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4794 /* Again, we don't know what the board looked like;
4795 this is really the start of the game. */
4796 parseList[moveNum - 1][0] = NULLCHAR;
4797 moveList[moveNum - 1][0] = NULLCHAR;
4798 backwardMostMove = moveNum;
4799 startedFromSetupPosition = TRUE;
4800 fromX = fromY = toX = toY = -1;
4802 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4803 // So we parse the long-algebraic move string in stead of the SAN move
4804 int valid; char buf[MSG_SIZ], *prom;
4806 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4807 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4808 // str looks something like "Q/a1-a2"; kill the slash
4810 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4811 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4812 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4813 strcat(buf, prom); // long move lacks promo specification!
4814 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4815 if(appData.debugMode)
4816 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4817 safeStrCpy(move_str, buf, MSG_SIZ);
4819 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4820 &fromX, &fromY, &toX, &toY, &promoChar)
4821 || ParseOneMove(buf, moveNum - 1, &moveType,
4822 &fromX, &fromY, &toX, &toY, &promoChar);
4823 // end of long SAN patch
4825 (void) CoordsToAlgebraic(boards[moveNum - 1],
4826 PosFlags(moveNum - 1),
4827 fromY, fromX, toY, toX, promoChar,
4828 parseList[moveNum-1]);
4829 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4835 if(!IS_SHOGI(gameInfo.variant))
4836 strcat(parseList[moveNum - 1], "+");
4839 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4840 strcat(parseList[moveNum - 1], "#");
4843 strcat(parseList[moveNum - 1], " ");
4844 strcat(parseList[moveNum - 1], elapsed_time);
4845 /* currentMoveString is set as a side-effect of ParseOneMove */
4846 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4847 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4848 strcat(moveList[moveNum - 1], "\n");
4850 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4851 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4852 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4853 ChessSquare old, new = boards[moveNum][k][j];
4854 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4855 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4856 if(old == new) continue;
4857 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4858 else if(new == WhiteWazir || new == BlackWazir) {
4859 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4860 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4861 else boards[moveNum][k][j] = old; // preserve type of Gold
4862 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4863 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4866 /* Move from ICS was illegal!? Punt. */
4867 if (appData.debugMode) {
4868 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4869 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4871 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4872 strcat(parseList[moveNum - 1], " ");
4873 strcat(parseList[moveNum - 1], elapsed_time);
4874 moveList[moveNum - 1][0] = NULLCHAR;
4875 fromX = fromY = toX = toY = -1;
4878 if (appData.debugMode) {
4879 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4880 setbuf(debugFP, NULL);
4884 /* Send move to chess program (BEFORE animating it). */
4885 if (appData.zippyPlay && !newGame && newMove &&
4886 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4888 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4889 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4890 if (moveList[moveNum - 1][0] == NULLCHAR) {
4891 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4893 DisplayError(str, 0);
4895 if (first.sendTime) {
4896 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4898 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4899 if (firstMove && !bookHit) {
4901 if (first.useColors) {
4902 SendToProgram(gameMode == IcsPlayingWhite ?
4904 "black\ngo\n", &first);
4906 SendToProgram("go\n", &first);
4908 first.maybeThinking = TRUE;
4911 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4912 if (moveList[moveNum - 1][0] == NULLCHAR) {
4913 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4914 DisplayError(str, 0);
4916 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4917 SendMoveToProgram(moveNum - 1, &first);
4924 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4925 /* If move comes from a remote source, animate it. If it
4926 isn't remote, it will have already been animated. */
4927 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4928 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4930 if (!pausing && appData.highlightLastMove) {
4931 SetHighlights(fromX, fromY, toX, toY);
4935 /* Start the clocks */
4936 whiteFlag = blackFlag = FALSE;
4937 appData.clockMode = !(basetime == 0 && increment == 0);
4939 ics_clock_paused = TRUE;
4941 } else if (ticking == 1) {
4942 ics_clock_paused = FALSE;
4944 if (gameMode == IcsIdle ||
4945 relation == RELATION_OBSERVING_STATIC ||
4946 relation == RELATION_EXAMINING ||
4948 DisplayBothClocks();
4952 /* Display opponents and material strengths */
4953 if (gameInfo.variant != VariantBughouse &&
4954 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4955 if (tinyLayout || smallLayout) {
4956 if(gameInfo.variant == VariantNormal)
4957 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4958 gameInfo.white, white_stren, gameInfo.black, black_stren,
4959 basetime, increment);
4961 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4962 gameInfo.white, white_stren, gameInfo.black, black_stren,
4963 basetime, increment, (int) gameInfo.variant);
4965 if(gameInfo.variant == VariantNormal)
4966 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4967 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4968 basetime, increment);
4970 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4971 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4972 basetime, increment, VariantName(gameInfo.variant));
4975 if (appData.debugMode) {
4976 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4981 /* Display the board */
4982 if (!pausing && !appData.noGUI) {
4984 if (appData.premove)
4986 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4987 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4988 ClearPremoveHighlights();
4990 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4991 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4992 DrawPosition(j, boards[currentMove]);
4994 DisplayMove(moveNum - 1);
4995 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4996 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4997 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4998 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5002 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5004 if(bookHit) { // [HGM] book: simulate book reply
5005 static char bookMove[MSG_SIZ]; // a bit generous?
5007 programStats.nodes = programStats.depth = programStats.time =
5008 programStats.score = programStats.got_only_move = 0;
5009 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5011 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5012 strcat(bookMove, bookHit);
5013 HandleMachineMove(bookMove, &first);
5022 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5023 ics_getting_history = H_REQUESTED;
5024 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5030 SendToBoth (char *msg)
5031 { // to make it easy to keep two engines in step in dual analysis
5032 SendToProgram(msg, &first);
5033 if(second.analyzing) SendToProgram(msg, &second);
5037 AnalysisPeriodicEvent (int force)
5039 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5040 && !force) || !appData.periodicUpdates)
5043 /* Send . command to Crafty to collect stats */
5046 /* Don't send another until we get a response (this makes
5047 us stop sending to old Crafty's which don't understand
5048 the "." command (sending illegal cmds resets node count & time,
5049 which looks bad)) */
5050 programStats.ok_to_send = 0;
5054 ics_update_width (int new_width)
5056 ics_printf("set width %d\n", new_width);
5060 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5064 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5065 if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5066 sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5067 SendToProgram(buf, cps);
5070 // null move in variant where engine does not understand it (for analysis purposes)
5071 SendBoard(cps, moveNum + 1); // send position after move in stead.
5074 if (cps->useUsermove) {
5075 SendToProgram("usermove ", cps);
5079 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5080 int len = space - parseList[moveNum];
5081 memcpy(buf, parseList[moveNum], len);
5083 buf[len] = NULLCHAR;
5085 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5087 SendToProgram(buf, cps);
5089 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5090 AlphaRank(moveList[moveNum], 4);
5091 SendToProgram(moveList[moveNum], cps);
5092 AlphaRank(moveList[moveNum], 4); // and back
5094 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5095 * the engine. It would be nice to have a better way to identify castle
5097 if(appData.fischerCastling && cps->useOOCastle) {
5098 int fromX = moveList[moveNum][0] - AAA;
5099 int fromY = moveList[moveNum][1] - ONE;
5100 int toX = moveList[moveNum][2] - AAA;
5101 int toY = moveList[moveNum][3] - ONE;
5102 if((boards[moveNum][fromY][fromX] == WhiteKing
5103 && boards[moveNum][toY][toX] == WhiteRook)
5104 || (boards[moveNum][fromY][fromX] == BlackKing
5105 && boards[moveNum][toY][toX] == BlackRook)) {
5106 if(toX > fromX) SendToProgram("O-O\n", cps);
5107 else SendToProgram("O-O-O\n", cps);
5109 else SendToProgram(moveList[moveNum], cps);
5111 if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5112 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5113 moveList[moveNum][5], moveList[moveNum][6] - '0',
5114 moveList[moveNum][5], moveList[moveNum][6] - '0',
5115 moveList[moveNum][2], moveList[moveNum][3] - '0');
5116 SendToProgram(buf, cps);
5118 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5119 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5120 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5121 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5122 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5124 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5125 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5126 SendToProgram(buf, cps);
5128 else SendToProgram(moveList[moveNum], cps);
5129 /* End of additions by Tord */
5132 /* [HGM] setting up the opening has brought engine in force mode! */
5133 /* Send 'go' if we are in a mode where machine should play. */
5134 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5135 (gameMode == TwoMachinesPlay ||
5137 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5139 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5140 SendToProgram("go\n", cps);
5141 if (appData.debugMode) {
5142 fprintf(debugFP, "(extra)\n");
5145 setboardSpoiledMachineBlack = 0;
5149 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5151 char user_move[MSG_SIZ];
5154 if(gameInfo.variant == VariantSChess && promoChar) {
5155 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5156 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5157 } else suffix[0] = NULLCHAR;
5161 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5162 (int)moveType, fromX, fromY, toX, toY);
5163 DisplayError(user_move + strlen("say "), 0);
5165 case WhiteKingSideCastle:
5166 case BlackKingSideCastle:
5167 case WhiteQueenSideCastleWild:
5168 case BlackQueenSideCastleWild:
5170 case WhiteHSideCastleFR:
5171 case BlackHSideCastleFR:
5173 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5175 case WhiteQueenSideCastle:
5176 case BlackQueenSideCastle:
5177 case WhiteKingSideCastleWild:
5178 case BlackKingSideCastleWild:
5180 case WhiteASideCastleFR:
5181 case BlackASideCastleFR:
5183 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5185 case WhiteNonPromotion:
5186 case BlackNonPromotion:
5187 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5189 case WhitePromotion:
5190 case BlackPromotion:
5191 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5192 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5193 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5194 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5195 PieceToChar(WhiteFerz));
5196 else if(gameInfo.variant == VariantGreat)
5197 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5198 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5199 PieceToChar(WhiteMan));
5201 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5202 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5208 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5209 ToUpper(PieceToChar((ChessSquare) fromX)),
5210 AAA + toX, ONE + toY);
5212 case IllegalMove: /* could be a variant we don't quite understand */
5213 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5215 case WhiteCapturesEnPassant:
5216 case BlackCapturesEnPassant:
5217 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5218 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5221 SendToICS(user_move);
5222 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5223 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5228 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5229 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5230 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5231 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5232 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5235 if(gameMode != IcsExamining) { // is this ever not the case?
5236 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5238 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5239 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5240 } else { // on FICS we must first go to general examine mode
5241 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5243 if(gameInfo.variant != VariantNormal) {
5244 // try figure out wild number, as xboard names are not always valid on ICS
5245 for(i=1; i<=36; i++) {
5246 snprintf(buf, MSG_SIZ, "wild/%d", i);
5247 if(StringToVariant(buf) == gameInfo.variant) break;
5249 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5250 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5251 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5252 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5253 SendToICS(ics_prefix);
5255 if(startedFromSetupPosition || backwardMostMove != 0) {
5256 fen = PositionToFEN(backwardMostMove, NULL, 1);
5257 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5258 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5260 } else { // FICS: everything has to set by separate bsetup commands
5261 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5262 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5264 if(!WhiteOnMove(backwardMostMove)) {
5265 SendToICS("bsetup tomove black\n");
5267 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5268 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5270 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5271 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5273 i = boards[backwardMostMove][EP_STATUS];
5274 if(i >= 0) { // set e.p.
5275 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5281 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5282 SendToICS("bsetup done\n"); // switch to normal examining.
5284 for(i = backwardMostMove; i<last; i++) {
5286 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5287 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5288 int len = strlen(moveList[i]);
5289 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5290 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5294 SendToICS(ics_prefix);
5295 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5298 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5301 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5303 if (rf == DROP_RANK) {
5304 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5305 sprintf(move, "%c@%c%c\n",
5306 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5308 if (promoChar == 'x' || promoChar == NULLCHAR) {
5309 sprintf(move, "%c%c%c%c\n",
5310 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5311 if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5313 sprintf(move, "%c%c%c%c%c\n",
5314 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5320 ProcessICSInitScript (FILE *f)
5324 while (fgets(buf, MSG_SIZ, f)) {
5325 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5332 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5334 static ClickType lastClickType;
5337 Partner (ChessSquare *p)
5338 { // change piece into promotion partner if one shogi-promotes to the other
5339 int stride = gameInfo.variant == VariantChu ? 22 : 11;
5340 ChessSquare partner;
5341 partner = (*p/stride & 1 ? *p - stride : *p + stride);
5342 if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5350 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5351 static int toggleFlag;
5352 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5353 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5354 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5355 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5356 if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5357 if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5359 if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5360 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5361 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5362 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5363 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5364 if(!step) step = -1;
5365 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5366 !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5367 appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5368 (promoSweep == WhiteLion || promoSweep == BlackLion)));
5370 int victim = boards[currentMove][toY][toX];
5371 boards[currentMove][toY][toX] = promoSweep;
5372 DrawPosition(FALSE, boards[currentMove]);
5373 boards[currentMove][toY][toX] = victim;
5375 ChangeDragPiece(promoSweep);
5379 PromoScroll (int x, int y)
5383 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5384 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5385 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5386 if(!step) return FALSE;
5387 lastX = x; lastY = y;
5388 if((promoSweep < BlackPawn) == flipView) step = -step;
5389 if(step > 0) selectFlag = 1;
5390 if(!selectFlag) Sweep(step);
5395 NextPiece (int step)
5397 ChessSquare piece = boards[currentMove][toY][toX];
5400 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5401 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5402 if(!step) step = -1;
5403 } while(PieceToChar(pieceSweep) == '.');
5404 boards[currentMove][toY][toX] = pieceSweep;
5405 DrawPosition(FALSE, boards[currentMove]);
5406 boards[currentMove][toY][toX] = piece;
5408 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5410 AlphaRank (char *move, int n)
5412 // char *p = move, c; int x, y;
5414 if (appData.debugMode) {
5415 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5419 move[2]>='0' && move[2]<='9' &&
5420 move[3]>='a' && move[3]<='x' ) {
5422 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5423 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5425 if(move[0]>='0' && move[0]<='9' &&
5426 move[1]>='a' && move[1]<='x' &&
5427 move[2]>='0' && move[2]<='9' &&
5428 move[3]>='a' && move[3]<='x' ) {
5429 /* input move, Shogi -> normal */
5430 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5431 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5432 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5433 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5436 move[3]>='0' && move[3]<='9' &&
5437 move[2]>='a' && move[2]<='x' ) {
5439 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5440 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5443 move[0]>='a' && move[0]<='x' &&
5444 move[3]>='0' && move[3]<='9' &&
5445 move[2]>='a' && move[2]<='x' ) {
5446 /* output move, normal -> Shogi */
5447 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5448 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5449 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5450 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5451 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5453 if (appData.debugMode) {
5454 fprintf(debugFP, " out = '%s'\n", move);
5458 char yy_textstr[8000];
5460 /* Parser for moves from gnuchess, ICS, or user typein box */
5462 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5464 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5466 switch (*moveType) {
5467 case WhitePromotion:
5468 case BlackPromotion:
5469 case WhiteNonPromotion:
5470 case BlackNonPromotion:
5473 case WhiteCapturesEnPassant:
5474 case BlackCapturesEnPassant:
5475 case WhiteKingSideCastle:
5476 case WhiteQueenSideCastle:
5477 case BlackKingSideCastle:
5478 case BlackQueenSideCastle:
5479 case WhiteKingSideCastleWild:
5480 case WhiteQueenSideCastleWild:
5481 case BlackKingSideCastleWild:
5482 case BlackQueenSideCastleWild:
5483 /* Code added by Tord: */
5484 case WhiteHSideCastleFR:
5485 case WhiteASideCastleFR:
5486 case BlackHSideCastleFR:
5487 case BlackASideCastleFR:
5488 /* End of code added by Tord */
5489 case IllegalMove: /* bug or odd chess variant */
5490 *fromX = currentMoveString[0] - AAA;
5491 *fromY = currentMoveString[1] - ONE;
5492 *toX = currentMoveString[2] - AAA;
5493 *toY = currentMoveString[3] - ONE;
5494 *promoChar = currentMoveString[4];
5495 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5496 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5497 if (appData.debugMode) {
5498 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5500 *fromX = *fromY = *toX = *toY = 0;
5503 if (appData.testLegality) {
5504 return (*moveType != IllegalMove);
5506 return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5507 // [HGM] lion: if this is a double move we are less critical
5508 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5513 *fromX = *moveType == WhiteDrop ?
5514 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5515 (int) CharToPiece(ToLower(currentMoveString[0]));
5517 *toX = currentMoveString[2] - AAA;
5518 *toY = currentMoveString[3] - ONE;
5519 *promoChar = NULLCHAR;
5523 case ImpossibleMove:
5533 if (appData.debugMode) {
5534 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5537 *fromX = *fromY = *toX = *toY = 0;
5538 *promoChar = NULLCHAR;
5543 Boolean pushed = FALSE;
5544 char *lastParseAttempt;
5547 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5548 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5549 int fromX, fromY, toX, toY; char promoChar;
5554 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5555 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5556 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5559 endPV = forwardMostMove;
5561 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5562 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5563 lastParseAttempt = pv;
5564 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5565 if(!valid && nr == 0 &&
5566 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5567 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5568 // Hande case where played move is different from leading PV move
5569 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5570 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5571 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5572 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5573 endPV += 2; // if position different, keep this
5574 moveList[endPV-1][0] = fromX + AAA;
5575 moveList[endPV-1][1] = fromY + ONE;
5576 moveList[endPV-1][2] = toX + AAA;
5577 moveList[endPV-1][3] = toY + ONE;
5578 parseList[endPV-1][0] = NULLCHAR;
5579 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5582 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5583 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5584 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5585 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5586 valid++; // allow comments in PV
5590 if(endPV+1 > framePtr) break; // no space, truncate
5593 CopyBoard(boards[endPV], boards[endPV-1]);
5594 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5595 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5596 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5597 CoordsToAlgebraic(boards[endPV - 1],
5598 PosFlags(endPV - 1),
5599 fromY, fromX, toY, toX, promoChar,
5600 parseList[endPV - 1]);
5602 if(atEnd == 2) return; // used hidden, for PV conversion
5603 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5604 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5605 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5606 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5607 DrawPosition(TRUE, boards[currentMove]);
5611 MultiPV (ChessProgramState *cps)
5612 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5614 for(i=0; i<cps->nrOptions; i++)
5615 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5620 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5623 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5625 int startPV, multi, lineStart, origIndex = index;
5626 char *p, buf2[MSG_SIZ];
5627 ChessProgramState *cps = (pane ? &second : &first);
5629 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5630 lastX = x; lastY = y;
5631 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5632 lineStart = startPV = index;
5633 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5634 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5636 do{ while(buf[index] && buf[index] != '\n') index++;
5637 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5639 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5640 int n = cps->option[multi].value;
5641 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5642 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5643 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5644 cps->option[multi].value = n;
5647 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5648 ExcludeClick(origIndex - lineStart);
5650 } else if(!strncmp(buf+lineStart, "dep\t", 4)) { // column headers clicked
5651 Collapse(origIndex - lineStart);
5654 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5655 *start = startPV; *end = index-1;
5656 extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5663 static char buf[10*MSG_SIZ];
5664 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5666 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5667 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5668 for(i = forwardMostMove; i<endPV; i++){
5669 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5670 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5673 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5674 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5675 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5681 LoadPV (int x, int y)
5682 { // called on right mouse click to load PV
5683 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5684 lastX = x; lastY = y;
5685 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5693 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5694 if(endPV < 0) return;
5695 if(appData.autoCopyPV) CopyFENToClipboard();
5697 if(extendGame && currentMove > forwardMostMove) {
5698 Boolean saveAnimate = appData.animate;
5700 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5701 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5702 } else storedGames--; // abandon shelved tail of original game
5705 forwardMostMove = currentMove;
5706 currentMove = oldFMM;
5707 appData.animate = FALSE;
5708 ToNrEvent(forwardMostMove);
5709 appData.animate = saveAnimate;
5711 currentMove = forwardMostMove;
5712 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5713 ClearPremoveHighlights();
5714 DrawPosition(TRUE, boards[currentMove]);
5718 MovePV (int x, int y, int h)
5719 { // step through PV based on mouse coordinates (called on mouse move)
5720 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5722 // we must somehow check if right button is still down (might be released off board!)
5723 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5724 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5725 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5727 lastX = x; lastY = y;
5729 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5730 if(endPV < 0) return;
5731 if(y < margin) step = 1; else
5732 if(y > h - margin) step = -1;
5733 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5734 currentMove += step;
5735 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5736 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5737 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5738 DrawPosition(FALSE, boards[currentMove]);
5742 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5743 // All positions will have equal probability, but the current method will not provide a unique
5744 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5750 int piecesLeft[(int)BlackPawn];
5751 int seed, nrOfShuffles;
5754 GetPositionNumber ()
5755 { // sets global variable seed
5758 seed = appData.defaultFrcPosition;
5759 if(seed < 0) { // randomize based on time for negative FRC position numbers
5760 for(i=0; i<50; i++) seed += random();
5761 seed = random() ^ random() >> 8 ^ random() << 8;
5762 if(seed<0) seed = -seed;
5767 put (Board board, int pieceType, int rank, int n, int shade)
5768 // put the piece on the (n-1)-th empty squares of the given shade
5772 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5773 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5774 board[rank][i] = (ChessSquare) pieceType;
5775 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5777 piecesLeft[pieceType]--;
5786 AddOnePiece (Board board, int pieceType, int rank, int shade)
5787 // calculate where the next piece goes, (any empty square), and put it there
5791 i = seed % squaresLeft[shade];
5792 nrOfShuffles *= squaresLeft[shade];
5793 seed /= squaresLeft[shade];
5794 put(board, pieceType, rank, i, shade);
5798 AddTwoPieces (Board board, int pieceType, int rank)
5799 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5801 int i, n=squaresLeft[ANY], j=n-1, k;
5803 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5804 i = seed % k; // pick one
5807 while(i >= j) i -= j--;
5808 j = n - 1 - j; i += j;
5809 put(board, pieceType, rank, j, ANY);
5810 put(board, pieceType, rank, i, ANY);
5814 SetUpShuffle (Board board, int number)
5818 GetPositionNumber(); nrOfShuffles = 1;
5820 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5821 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5822 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5824 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5826 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5827 p = (int) board[0][i];
5828 if(p < (int) BlackPawn) piecesLeft[p] ++;
5829 board[0][i] = EmptySquare;
5832 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5833 // shuffles restricted to allow normal castling put KRR first
5834 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5835 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5836 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5837 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5838 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5839 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5840 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5841 put(board, WhiteRook, 0, 0, ANY);
5842 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5845 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5846 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5847 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5848 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5849 while(piecesLeft[p] >= 2) {
5850 AddOnePiece(board, p, 0, LITE);
5851 AddOnePiece(board, p, 0, DARK);
5853 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5856 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5857 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5858 // but we leave King and Rooks for last, to possibly obey FRC restriction
5859 if(p == (int)WhiteRook) continue;
5860 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5861 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5864 // now everything is placed, except perhaps King (Unicorn) and Rooks
5866 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5867 // Last King gets castling rights
5868 while(piecesLeft[(int)WhiteUnicorn]) {
5869 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5870 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5873 while(piecesLeft[(int)WhiteKing]) {
5874 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5875 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5880 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5881 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5884 // Only Rooks can be left; simply place them all
5885 while(piecesLeft[(int)WhiteRook]) {
5886 i = put(board, WhiteRook, 0, 0, ANY);
5887 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5890 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5892 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5895 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5896 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5899 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5903 SetCharTable (char *table, const char * map)
5904 /* [HGM] moved here from winboard.c because of its general usefulness */
5905 /* Basically a safe strcpy that uses the last character as King */
5907 int result = FALSE; int NrPieces;
5909 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5910 && NrPieces >= 12 && !(NrPieces&1)) {
5911 int i; /* [HGM] Accept even length from 12 to 34 */
5913 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5914 for( i=0; i<NrPieces/2-1; i++ ) {
5916 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5918 table[(int) WhiteKing] = map[NrPieces/2-1];
5919 table[(int) BlackKing] = map[NrPieces-1];
5928 Prelude (Board board)
5929 { // [HGM] superchess: random selection of exo-pieces
5930 int i, j, k; ChessSquare p;
5931 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5933 GetPositionNumber(); // use FRC position number
5935 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5936 SetCharTable(pieceToChar, appData.pieceToCharTable);
5937 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5938 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5941 j = seed%4; seed /= 4;
5942 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5943 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5944 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5945 j = seed%3 + (seed%3 >= j); seed /= 3;
5946 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5947 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5948 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5949 j = seed%3; seed /= 3;
5950 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5951 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5952 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5953 j = seed%2 + (seed%2 >= j); seed /= 2;
5954 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5955 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5956 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5957 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5958 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5959 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5960 put(board, exoPieces[0], 0, 0, ANY);
5961 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5965 InitPosition (int redraw)
5967 ChessSquare (* pieces)[BOARD_FILES];
5968 int i, j, pawnRow=1, pieceRows=1, overrule,
5969 oldx = gameInfo.boardWidth,
5970 oldy = gameInfo.boardHeight,
5971 oldh = gameInfo.holdingsWidth;
5974 if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5976 /* [AS] Initialize pv info list [HGM] and game status */
5978 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5979 pvInfoList[i].depth = 0;
5980 boards[i][EP_STATUS] = EP_NONE;
5981 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5984 initialRulePlies = 0; /* 50-move counter start */
5986 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5987 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5991 /* [HGM] logic here is completely changed. In stead of full positions */
5992 /* the initialized data only consist of the two backranks. The switch */
5993 /* selects which one we will use, which is than copied to the Board */
5994 /* initialPosition, which for the rest is initialized by Pawns and */
5995 /* empty squares. This initial position is then copied to boards[0], */
5996 /* possibly after shuffling, so that it remains available. */
5998 gameInfo.holdingsWidth = 0; /* default board sizes */
5999 gameInfo.boardWidth = 8;
6000 gameInfo.boardHeight = 8;
6001 gameInfo.holdingsSize = 0;
6002 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6003 for(i=0; i<BOARD_FILES-2; i++)
6004 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6005 initialPosition[EP_STATUS] = EP_NONE;
6006 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6007 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6008 SetCharTable(pieceNickName, appData.pieceNickNames);
6009 else SetCharTable(pieceNickName, "............");
6012 switch (gameInfo.variant) {
6013 case VariantFischeRandom:
6014 shuffleOpenings = TRUE;
6015 appData.fischerCastling = TRUE;
6018 case VariantShatranj:
6019 pieces = ShatranjArray;
6020 nrCastlingRights = 0;
6021 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6024 pieces = makrukArray;
6025 nrCastlingRights = 0;
6026 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6029 pieces = aseanArray;
6030 nrCastlingRights = 0;
6031 SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6033 case VariantTwoKings:
6034 pieces = twoKingsArray;
6037 pieces = GrandArray;
6038 nrCastlingRights = 0;
6039 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6040 gameInfo.boardWidth = 10;
6041 gameInfo.boardHeight = 10;
6042 gameInfo.holdingsSize = 7;
6044 case VariantCapaRandom:
6045 shuffleOpenings = TRUE;
6046 appData.fischerCastling = TRUE;
6047 case VariantCapablanca:
6048 pieces = CapablancaArray;
6049 gameInfo.boardWidth = 10;
6050 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6053 pieces = GothicArray;
6054 gameInfo.boardWidth = 10;
6055 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6058 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6059 gameInfo.holdingsSize = 7;
6060 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6063 pieces = JanusArray;
6064 gameInfo.boardWidth = 10;
6065 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6066 nrCastlingRights = 6;
6067 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6068 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6069 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6070 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6071 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6072 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6075 pieces = FalconArray;
6076 gameInfo.boardWidth = 10;
6077 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6079 case VariantXiangqi:
6080 pieces = XiangqiArray;
6081 gameInfo.boardWidth = 9;
6082 gameInfo.boardHeight = 10;
6083 nrCastlingRights = 0;
6084 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6087 pieces = ShogiArray;
6088 gameInfo.boardWidth = 9;
6089 gameInfo.boardHeight = 9;
6090 gameInfo.holdingsSize = 7;
6091 nrCastlingRights = 0;
6092 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6095 pieces = ChuArray; pieceRows = 3;
6096 gameInfo.boardWidth = 12;
6097 gameInfo.boardHeight = 12;
6098 nrCastlingRights = 0;
6099 SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6100 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6102 case VariantCourier:
6103 pieces = CourierArray;
6104 gameInfo.boardWidth = 12;
6105 nrCastlingRights = 0;
6106 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6108 case VariantKnightmate:
6109 pieces = KnightmateArray;
6110 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6112 case VariantSpartan:
6113 pieces = SpartanArray;
6114 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6118 SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6120 case VariantChuChess:
6121 pieces = ChuChessArray;
6122 gameInfo.boardWidth = 10;
6123 gameInfo.boardHeight = 10;
6124 SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6127 pieces = fairyArray;
6128 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6131 pieces = GreatArray;
6132 gameInfo.boardWidth = 10;
6133 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6134 gameInfo.holdingsSize = 8;
6138 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6139 gameInfo.holdingsSize = 8;
6140 startedFromSetupPosition = TRUE;
6142 case VariantCrazyhouse:
6143 case VariantBughouse:
6145 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6146 gameInfo.holdingsSize = 5;
6148 case VariantWildCastle:
6150 /* !!?shuffle with kings guaranteed to be on d or e file */
6151 shuffleOpenings = 1;
6153 case VariantNoCastle:
6155 nrCastlingRights = 0;
6156 /* !!?unconstrained back-rank shuffle */
6157 shuffleOpenings = 1;
6162 if(appData.NrFiles >= 0) {
6163 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6164 gameInfo.boardWidth = appData.NrFiles;
6166 if(appData.NrRanks >= 0) {
6167 gameInfo.boardHeight = appData.NrRanks;
6169 if(appData.holdingsSize >= 0) {
6170 i = appData.holdingsSize;
6171 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6172 gameInfo.holdingsSize = i;
6174 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6175 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6176 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6178 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6179 if(pawnRow < 1) pawnRow = 1;
6180 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6181 gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6182 if(gameInfo.variant == VariantChu) pawnRow = 3;
6184 /* User pieceToChar list overrules defaults */
6185 if(appData.pieceToCharTable != NULL)
6186 SetCharTable(pieceToChar, appData.pieceToCharTable);
6188 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6190 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6191 s = (ChessSquare) 0; /* account holding counts in guard band */
6192 for( i=0; i<BOARD_HEIGHT; i++ )
6193 initialPosition[i][j] = s;
6195 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6196 initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6197 initialPosition[pawnRow][j] = WhitePawn;
6198 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6199 if(gameInfo.variant == VariantXiangqi) {
6201 initialPosition[pawnRow][j] =
6202 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6203 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6204 initialPosition[2][j] = WhiteCannon;
6205 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6209 if(gameInfo.variant == VariantChu) {
6210 if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6211 initialPosition[pawnRow+1][j] = WhiteCobra,
6212 initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6213 for(i=1; i<pieceRows; i++) {
6214 initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6215 initialPosition[BOARD_HEIGHT-1-i][j] = pieces[2*i+1][j-gameInfo.holdingsWidth];
6218 if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6219 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6220 initialPosition[0][j] = WhiteRook;
6221 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6224 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] = pieces[1][j-gameInfo.holdingsWidth];
6226 if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6227 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6230 initialPosition[1][j] = WhiteBishop;
6231 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6233 initialPosition[1][j] = WhiteRook;
6234 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6237 if( nrCastlingRights == -1) {
6238 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6239 /* This sets default castling rights from none to normal corners */
6240 /* Variants with other castling rights must set them themselves above */
6241 nrCastlingRights = 6;
6243 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6244 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6245 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6246 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6247 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6248 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6251 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6252 if(gameInfo.variant == VariantGreat) { // promotion commoners
6253 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6254 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6255 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6256 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6258 if( gameInfo.variant == VariantSChess ) {
6259 initialPosition[1][0] = BlackMarshall;
6260 initialPosition[2][0] = BlackAngel;
6261 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6262 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6263 initialPosition[1][1] = initialPosition[2][1] =
6264 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6266 if (appData.debugMode) {
6267 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6269 if(shuffleOpenings) {
6270 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6271 startedFromSetupPosition = TRUE;
6273 if(startedFromPositionFile) {
6274 /* [HGM] loadPos: use PositionFile for every new game */
6275 CopyBoard(initialPosition, filePosition);
6276 for(i=0; i<nrCastlingRights; i++)
6277 initialRights[i] = filePosition[CASTLING][i];
6278 startedFromSetupPosition = TRUE;
6281 CopyBoard(boards[0], initialPosition);
6283 if(oldx != gameInfo.boardWidth ||
6284 oldy != gameInfo.boardHeight ||
6285 oldv != gameInfo.variant ||
6286 oldh != gameInfo.holdingsWidth
6288 InitDrawingSizes(-2 ,0);
6290 oldv = gameInfo.variant;
6292 DrawPosition(TRUE, boards[currentMove]);
6296 SendBoard (ChessProgramState *cps, int moveNum)
6298 char message[MSG_SIZ];
6300 if (cps->useSetboard) {
6301 char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6302 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6303 SendToProgram(message, cps);
6308 int i, j, left=0, right=BOARD_WIDTH;
6309 /* Kludge to set black to move, avoiding the troublesome and now
6310 * deprecated "black" command.
6312 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6313 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6315 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6317 SendToProgram("edit\n", cps);
6318 SendToProgram("#\n", cps);
6319 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6320 bp = &boards[moveNum][i][left];
6321 for (j = left; j < right; j++, bp++) {
6322 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6323 if ((int) *bp < (int) BlackPawn) {
6324 if(j == BOARD_RGHT+1)
6325 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6326 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6327 if(message[0] == '+' || message[0] == '~') {
6328 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6329 PieceToChar((ChessSquare)(DEMOTED *bp)),
6332 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6333 message[1] = BOARD_RGHT - 1 - j + '1';
6334 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6336 SendToProgram(message, cps);
6341 SendToProgram("c\n", cps);
6342 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6343 bp = &boards[moveNum][i][left];
6344 for (j = left; j < right; j++, bp++) {
6345 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6346 if (((int) *bp != (int) EmptySquare)
6347 && ((int) *bp >= (int) BlackPawn)) {
6348 if(j == BOARD_LEFT-2)
6349 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6350 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6352 if(message[0] == '+' || message[0] == '~') {
6353 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6354 PieceToChar((ChessSquare)(DEMOTED *bp)),
6357 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6358 message[1] = BOARD_RGHT - 1 - j + '1';
6359 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6361 SendToProgram(message, cps);
6366 SendToProgram(".\n", cps);
6368 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6371 char exclusionHeader[MSG_SIZ];
6372 int exCnt, excludePtr;
6373 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6374 static Exclusion excluTab[200];
6375 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6381 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6382 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6388 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6389 excludePtr = 24; exCnt = 0;
6394 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6395 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6396 char buf[2*MOVE_LEN], *p;
6397 Exclusion *e = excluTab;
6399 for(i=0; i<exCnt; i++)
6400 if(e[i].ff == fromX && e[i].fr == fromY &&
6401 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6402 if(i == exCnt) { // was not in exclude list; add it
6403 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6404 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6405 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6408 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6409 excludePtr++; e[i].mark = excludePtr++;
6410 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6413 exclusionHeader[e[i].mark] = state;
6417 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6418 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6422 if((signed char)promoChar == -1) { // kludge to indicate best move
6423 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6424 return 1; // if unparsable, abort
6426 // update exclusion map (resolving toggle by consulting existing state)
6427 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6429 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6430 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6431 excludeMap[k] |= 1<<j;
6432 else excludeMap[k] &= ~(1<<j);
6434 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6436 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6437 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6439 return (state == '+');
6443 ExcludeClick (int index)
6446 Exclusion *e = excluTab;
6447 if(index < 25) { // none, best or tail clicked
6448 if(index < 13) { // none: include all
6449 WriteMap(0); // clear map
6450 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6451 SendToBoth("include all\n"); // and inform engine
6452 } else if(index > 18) { // tail
6453 if(exclusionHeader[19] == '-') { // tail was excluded
6454 SendToBoth("include all\n");
6455 WriteMap(0); // clear map completely
6456 // now re-exclude selected moves
6457 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6458 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6459 } else { // tail was included or in mixed state
6460 SendToBoth("exclude all\n");
6461 WriteMap(0xFF); // fill map completely
6462 // now re-include selected moves
6463 j = 0; // count them
6464 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6465 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6466 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6469 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6472 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6473 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6474 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6481 DefaultPromoChoice (int white)
6484 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6485 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6486 result = WhiteFerz; // no choice
6487 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6488 result= WhiteKing; // in Suicide Q is the last thing we want
6489 else if(gameInfo.variant == VariantSpartan)
6490 result = white ? WhiteQueen : WhiteAngel;
6491 else result = WhiteQueen;
6492 if(!white) result = WHITE_TO_BLACK result;
6496 static int autoQueen; // [HGM] oneclick
6499 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6501 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6502 /* [HGM] add Shogi promotions */
6503 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6504 ChessSquare piece, partner;
6508 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6509 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6511 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6512 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6515 piece = boards[currentMove][fromY][fromX];
6516 if(gameInfo.variant == VariantChu) {
6517 int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6518 promotionZoneSize = BOARD_HEIGHT/3;
6519 highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6520 } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6521 promotionZoneSize = BOARD_HEIGHT/3;
6522 highestPromotingPiece = (int)WhiteAlfil;
6523 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6524 promotionZoneSize = 3;
6527 // Treat Lance as Pawn when it is not representing Amazon or Lance
6528 if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6529 if(piece == WhiteLance) piece = WhitePawn; else
6530 if(piece == BlackLance) piece = BlackPawn;
6533 // next weed out all moves that do not touch the promotion zone at all
6534 if((int)piece >= BlackPawn) {
6535 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6537 if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6538 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6540 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6541 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6542 if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6546 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6548 // weed out mandatory Shogi promotions
6549 if(gameInfo.variant == VariantShogi) {
6550 if(piece >= BlackPawn) {
6551 if(toY == 0 && piece == BlackPawn ||
6552 toY == 0 && piece == BlackQueen ||
6553 toY <= 1 && piece == BlackKnight) {
6558 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6559 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6560 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6567 // weed out obviously illegal Pawn moves
6568 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6569 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6570 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6571 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6572 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6573 // note we are not allowed to test for valid (non-)capture, due to premove
6576 // we either have a choice what to promote to, or (in Shogi) whether to promote
6577 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6578 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6579 ChessSquare p=BlackFerz; // no choice
6580 while(p < EmptySquare) { //but make sure we use piece that exists
6581 *promoChoice = PieceToChar(p++);
6582 if(*promoChoice != '.') break;
6586 // no sense asking what we must promote to if it is going to explode...
6587 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6588 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6591 // give caller the default choice even if we will not make it
6592 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6593 partner = piece; // pieces can promote if the pieceToCharTable says so
6594 if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6595 else if(Partner(&partner)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6596 if( sweepSelect && gameInfo.variant != VariantGreat
6597 && gameInfo.variant != VariantGrand
6598 && gameInfo.variant != VariantSuper) return FALSE;
6599 if(autoQueen) return FALSE; // predetermined
6601 // suppress promotion popup on illegal moves that are not premoves
6602 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6603 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6604 if(appData.testLegality && !premove) {
6605 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6606 fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6607 if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6608 if(moveType != WhitePromotion && moveType != BlackPromotion)
6616 InPalace (int row, int column)
6617 { /* [HGM] for Xiangqi */
6618 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6619 column < (BOARD_WIDTH + 4)/2 &&
6620 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6625 PieceForSquare (int x, int y)
6627 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6630 return boards[currentMove][y][x];
6634 OKToStartUserMove (int x, int y)
6636 ChessSquare from_piece;
6639 if (matchMode) return FALSE;
6640 if (gameMode == EditPosition) return TRUE;
6642 if (x >= 0 && y >= 0)
6643 from_piece = boards[currentMove][y][x];
6645 from_piece = EmptySquare;
6647 if (from_piece == EmptySquare) return FALSE;
6649 white_piece = (int)from_piece >= (int)WhitePawn &&
6650 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6654 case TwoMachinesPlay:
6662 case MachinePlaysWhite:
6663 case IcsPlayingBlack:
6664 if (appData.zippyPlay) return FALSE;
6666 DisplayMoveError(_("You are playing Black"));
6671 case MachinePlaysBlack:
6672 case IcsPlayingWhite:
6673 if (appData.zippyPlay) return FALSE;
6675 DisplayMoveError(_("You are playing White"));
6680 case PlayFromGameFile:
6681 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6683 if (!white_piece && WhiteOnMove(currentMove)) {
6684 DisplayMoveError(_("It is White's turn"));
6687 if (white_piece && !WhiteOnMove(currentMove)) {
6688 DisplayMoveError(_("It is Black's turn"));
6691 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6692 /* Editing correspondence game history */
6693 /* Could disallow this or prompt for confirmation */
6698 case BeginningOfGame:
6699 if (appData.icsActive) return FALSE;
6700 if (!appData.noChessProgram) {
6702 DisplayMoveError(_("You are playing White"));
6709 if (!white_piece && WhiteOnMove(currentMove)) {
6710 DisplayMoveError(_("It is White's turn"));
6713 if (white_piece && !WhiteOnMove(currentMove)) {
6714 DisplayMoveError(_("It is Black's turn"));
6723 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6724 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6725 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6726 && gameMode != AnalyzeFile && gameMode != Training) {
6727 DisplayMoveError(_("Displayed position is not current"));
6734 OnlyMove (int *x, int *y, Boolean captures)
6736 DisambiguateClosure cl;
6737 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6739 case MachinePlaysBlack:
6740 case IcsPlayingWhite:
6741 case BeginningOfGame:
6742 if(!WhiteOnMove(currentMove)) return FALSE;
6744 case MachinePlaysWhite:
6745 case IcsPlayingBlack:
6746 if(WhiteOnMove(currentMove)) return FALSE;
6753 cl.pieceIn = EmptySquare;
6758 cl.promoCharIn = NULLCHAR;
6759 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6760 if( cl.kind == NormalMove ||
6761 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6762 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6763 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6770 if(cl.kind != ImpossibleMove) return FALSE;
6771 cl.pieceIn = EmptySquare;
6776 cl.promoCharIn = NULLCHAR;
6777 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6778 if( cl.kind == NormalMove ||
6779 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6780 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6781 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6786 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6792 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6793 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6794 int lastLoadGameUseList = FALSE;
6795 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6796 ChessMove lastLoadGameStart = EndOfFile;
6800 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6804 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6806 /* Check if the user is playing in turn. This is complicated because we
6807 let the user "pick up" a piece before it is his turn. So the piece he
6808 tried to pick up may have been captured by the time he puts it down!
6809 Therefore we use the color the user is supposed to be playing in this
6810 test, not the color of the piece that is currently on the starting
6811 square---except in EditGame mode, where the user is playing both
6812 sides; fortunately there the capture race can't happen. (It can
6813 now happen in IcsExamining mode, but that's just too bad. The user
6814 will get a somewhat confusing message in that case.)
6819 case TwoMachinesPlay:
6823 /* We switched into a game mode where moves are not accepted,
6824 perhaps while the mouse button was down. */
6827 case MachinePlaysWhite:
6828 /* User is moving for Black */
6829 if (WhiteOnMove(currentMove)) {
6830 DisplayMoveError(_("It is White's turn"));
6835 case MachinePlaysBlack:
6836 /* User is moving for White */
6837 if (!WhiteOnMove(currentMove)) {
6838 DisplayMoveError(_("It is Black's turn"));
6843 case PlayFromGameFile:
6844 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6847 case BeginningOfGame:
6850 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6851 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6852 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6853 /* User is moving for Black */
6854 if (WhiteOnMove(currentMove)) {
6855 DisplayMoveError(_("It is White's turn"));
6859 /* User is moving for White */
6860 if (!WhiteOnMove(currentMove)) {
6861 DisplayMoveError(_("It is Black's turn"));
6867 case IcsPlayingBlack:
6868 /* User is moving for Black */
6869 if (WhiteOnMove(currentMove)) {
6870 if (!appData.premove) {
6871 DisplayMoveError(_("It is White's turn"));
6872 } else if (toX >= 0 && toY >= 0) {
6875 premoveFromX = fromX;
6876 premoveFromY = fromY;
6877 premovePromoChar = promoChar;
6879 if (appData.debugMode)
6880 fprintf(debugFP, "Got premove: fromX %d,"
6881 "fromY %d, toX %d, toY %d\n",
6882 fromX, fromY, toX, toY);
6888 case IcsPlayingWhite:
6889 /* User is moving for White */
6890 if (!WhiteOnMove(currentMove)) {
6891 if (!appData.premove) {
6892 DisplayMoveError(_("It is Black's turn"));
6893 } else if (toX >= 0 && toY >= 0) {
6896 premoveFromX = fromX;
6897 premoveFromY = fromY;
6898 premovePromoChar = promoChar;
6900 if (appData.debugMode)
6901 fprintf(debugFP, "Got premove: fromX %d,"
6902 "fromY %d, toX %d, toY %d\n",
6903 fromX, fromY, toX, toY);
6913 /* EditPosition, empty square, or different color piece;
6914 click-click move is possible */
6915 if (toX == -2 || toY == -2) {
6916 boards[0][fromY][fromX] = EmptySquare;
6917 DrawPosition(FALSE, boards[currentMove]);
6919 } else if (toX >= 0 && toY >= 0) {
6920 boards[0][toY][toX] = boards[0][fromY][fromX];
6921 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6922 if(boards[0][fromY][0] != EmptySquare) {
6923 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6924 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6927 if(fromX == BOARD_RGHT+1) {
6928 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6929 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6930 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6933 boards[0][fromY][fromX] = gatingPiece;
6934 DrawPosition(FALSE, boards[currentMove]);
6940 if(toX < 0 || toY < 0) return;
6941 pup = boards[currentMove][toY][toX];
6943 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6944 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6945 if( pup != EmptySquare ) return;
6946 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6947 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6948 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6949 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6950 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6951 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6952 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6956 /* [HGM] always test for legality, to get promotion info */
6957 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6958 fromY, fromX, toY, toX, promoChar);
6960 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6962 /* [HGM] but possibly ignore an IllegalMove result */
6963 if (appData.testLegality) {
6964 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6965 DisplayMoveError(_("Illegal move"));
6970 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6971 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6972 ClearPremoveHighlights(); // was included
6973 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
6977 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6980 /* Common tail of UserMoveEvent and DropMenuEvent */
6982 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6986 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6987 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6988 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6989 if(WhiteOnMove(currentMove)) {
6990 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6992 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6996 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6997 move type in caller when we know the move is a legal promotion */
6998 if(moveType == NormalMove && promoChar)
6999 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7001 /* [HGM] <popupFix> The following if has been moved here from
7002 UserMoveEvent(). Because it seemed to belong here (why not allow
7003 piece drops in training games?), and because it can only be
7004 performed after it is known to what we promote. */
7005 if (gameMode == Training) {
7006 /* compare the move played on the board to the next move in the
7007 * game. If they match, display the move and the opponent's response.
7008 * If they don't match, display an error message.
7012 CopyBoard(testBoard, boards[currentMove]);
7013 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7015 if (CompareBoards(testBoard, boards[currentMove+1])) {
7016 ForwardInner(currentMove+1);
7018 /* Autoplay the opponent's response.
7019 * if appData.animate was TRUE when Training mode was entered,
7020 * the response will be animated.
7022 saveAnimate = appData.animate;
7023 appData.animate = animateTraining;
7024 ForwardInner(currentMove+1);
7025 appData.animate = saveAnimate;
7027 /* check for the end of the game */
7028 if (currentMove >= forwardMostMove) {
7029 gameMode = PlayFromGameFile;
7031 SetTrainingModeOff();
7032 DisplayInformation(_("End of game"));
7035 DisplayError(_("Incorrect move"), 0);
7040 /* Ok, now we know that the move is good, so we can kill
7041 the previous line in Analysis Mode */
7042 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7043 && currentMove < forwardMostMove) {
7044 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7045 else forwardMostMove = currentMove;
7050 /* If we need the chess program but it's dead, restart it */
7051 ResurrectChessProgram();
7053 /* A user move restarts a paused game*/
7057 thinkOutput[0] = NULLCHAR;
7059 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7061 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7062 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7066 if (gameMode == BeginningOfGame) {
7067 if (appData.noChessProgram) {
7068 gameMode = EditGame;
7072 gameMode = MachinePlaysBlack;
7075 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7077 if (first.sendName) {
7078 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7079 SendToProgram(buf, &first);
7086 /* Relay move to ICS or chess engine */
7087 if (appData.icsActive) {
7088 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7089 gameMode == IcsExamining) {
7090 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7091 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7093 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7095 // also send plain move, in case ICS does not understand atomic claims
7096 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7100 if (first.sendTime && (gameMode == BeginningOfGame ||
7101 gameMode == MachinePlaysWhite ||
7102 gameMode == MachinePlaysBlack)) {
7103 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7105 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7106 // [HGM] book: if program might be playing, let it use book
7107 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7108 first.maybeThinking = TRUE;
7109 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7110 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7111 SendBoard(&first, currentMove+1);
7112 if(second.analyzing) {
7113 if(!second.useSetboard) SendToProgram("undo\n", &second);
7114 SendBoard(&second, currentMove+1);
7117 SendMoveToProgram(forwardMostMove-1, &first);
7118 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7120 if (currentMove == cmailOldMove + 1) {
7121 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7125 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7129 if(appData.testLegality)
7130 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7136 if (WhiteOnMove(currentMove)) {
7137 GameEnds(BlackWins, "Black mates", GE_PLAYER);
7139 GameEnds(WhiteWins, "White mates", GE_PLAYER);
7143 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7148 case MachinePlaysBlack:
7149 case MachinePlaysWhite:
7150 /* disable certain menu options while machine is thinking */
7151 SetMachineThinkingEnables();
7158 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7159 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7161 if(bookHit) { // [HGM] book: simulate book reply
7162 static char bookMove[MSG_SIZ]; // a bit generous?
7164 programStats.nodes = programStats.depth = programStats.time =
7165 programStats.score = programStats.got_only_move = 0;
7166 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7168 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7169 strcat(bookMove, bookHit);
7170 HandleMachineMove(bookMove, &first);
7176 MarkByFEN(char *fen)
7179 if(!appData.markers || !appData.highlightDragging) return;
7180 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7181 r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7185 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7186 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7187 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7188 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7189 if(*fen == 'T') marker[r][f++] = 0; else
7190 if(*fen == 'Y') marker[r][f++] = 1; else
7191 if(*fen == 'G') marker[r][f++] = 3; else
7192 if(*fen == 'B') marker[r][f++] = 4; else
7193 if(*fen == 'C') marker[r][f++] = 5; else
7194 if(*fen == 'M') marker[r][f++] = 6; else
7195 if(*fen == 'W') marker[r][f++] = 7; else
7196 if(*fen == 'D') marker[r][f++] = 8; else
7197 if(*fen == 'R') marker[r][f++] = 2; else {
7198 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7201 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7205 DrawPosition(TRUE, NULL);
7208 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7211 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7213 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7214 Markers *m = (Markers *) closure;
7215 if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7216 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7217 || kind == WhiteCapturesEnPassant
7218 || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7219 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7222 static int hoverSavedValid;
7225 MarkTargetSquares (int clear)
7228 if(clear) { // no reason to ever suppress clearing
7229 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7230 hoverSavedValid = 0;
7231 if(!sum) return; // nothing was cleared,no redraw needed
7234 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7235 !appData.testLegality || gameMode == EditPosition) return;
7236 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7237 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7238 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7240 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7243 DrawPosition(FALSE, NULL);
7247 Explode (Board board, int fromX, int fromY, int toX, int toY)
7249 if(gameInfo.variant == VariantAtomic &&
7250 (board[toY][toX] != EmptySquare || // capture?
7251 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7252 board[fromY][fromX] == BlackPawn )
7254 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7260 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7263 CanPromote (ChessSquare piece, int y)
7265 int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7266 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7267 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7268 if(IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantXiangqi ||
7269 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7270 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7271 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) return FALSE;
7272 return (piece == BlackPawn && y <= zone ||
7273 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7274 piece == BlackLance && y == 1 ||
7275 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7279 HoverEvent (int xPix, int yPix, int x, int y)
7281 static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7283 if(!first.highlight) return;
7284 if(fromX != oldFromX || fromY != oldFromY) oldX = oldY = -1; // kludge to fake entry on from-click
7285 if(x == oldX && y == oldY) return; // only do something if we enter new square
7286 oldFromX = fromX; oldFromY = fromY;
7287 if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7288 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7289 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7290 hoverSavedValid = 1;
7291 } else if(oldX != x || oldY != y) {
7292 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7293 if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7294 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7295 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7296 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7298 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7299 SendToProgram(buf, &first);
7302 // SetHighlights(fromX, fromY, x, y);
7306 void ReportClick(char *action, int x, int y)
7308 char buf[MSG_SIZ]; // Inform engine of what user does
7310 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7311 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7312 if(!first.highlight || gameMode == EditPosition) return;
7313 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7314 SendToProgram(buf, &first);
7318 LeftClick (ClickType clickType, int xPix, int yPix)
7321 Boolean saveAnimate;
7322 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7323 char promoChoice = NULLCHAR;
7325 static TimeMark lastClickTime, prevClickTime;
7327 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7329 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7331 if (clickType == Press) ErrorPopDown();
7332 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7334 x = EventToSquare(xPix, BOARD_WIDTH);
7335 y = EventToSquare(yPix, BOARD_HEIGHT);
7336 if (!flipView && y >= 0) {
7337 y = BOARD_HEIGHT - 1 - y;
7339 if (flipView && x >= 0) {
7340 x = BOARD_WIDTH - 1 - x;
7343 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7344 defaultPromoChoice = promoSweep;
7345 promoSweep = EmptySquare; // terminate sweep
7346 promoDefaultAltered = TRUE;
7347 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7350 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7351 if(clickType == Release) return; // ignore upclick of click-click destination
7352 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7353 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7354 if(gameInfo.holdingsWidth &&
7355 (WhiteOnMove(currentMove)
7356 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7357 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7358 // click in right holdings, for determining promotion piece
7359 ChessSquare p = boards[currentMove][y][x];
7360 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7361 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7362 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7363 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7368 DrawPosition(FALSE, boards[currentMove]);
7372 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7373 if(clickType == Press
7374 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7375 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7376 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7379 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7380 // could be static click on premove from-square: abort premove
7382 ClearPremoveHighlights();
7385 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7386 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7388 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7389 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7390 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7391 defaultPromoChoice = DefaultPromoChoice(side);
7394 autoQueen = appData.alwaysPromoteToQueen;
7398 gatingPiece = EmptySquare;
7399 if (clickType != Press) {
7400 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7401 DragPieceEnd(xPix, yPix); dragging = 0;
7402 DrawPosition(FALSE, NULL);
7406 doubleClick = FALSE;
7407 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7408 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7410 fromX = x; fromY = y; toX = toY = killX = killY = -1;
7411 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7412 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7413 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7415 if (OKToStartUserMove(fromX, fromY)) {
7417 ReportClick("lift", x, y);
7418 MarkTargetSquares(0);
7419 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7420 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7421 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7422 promoSweep = defaultPromoChoice;
7423 selectFlag = 0; lastX = xPix; lastY = yPix;
7424 Sweep(0); // Pawn that is going to promote: preview promotion piece
7425 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7427 if (appData.highlightDragging) {
7428 SetHighlights(fromX, fromY, -1, -1);
7432 } else fromX = fromY = -1;
7438 if (clickType == Press && gameMode != EditPosition) {
7443 // ignore off-board to clicks
7444 if(y < 0 || x < 0) return;
7446 /* Check if clicking again on the same color piece */
7447 fromP = boards[currentMove][fromY][fromX];
7448 toP = boards[currentMove][y][x];
7449 frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7450 if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7451 ((WhitePawn <= fromP && fromP <= WhiteKing &&
7452 WhitePawn <= toP && toP <= WhiteKing &&
7453 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7454 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7455 (BlackPawn <= fromP && fromP <= BlackKing &&
7456 BlackPawn <= toP && toP <= BlackKing &&
7457 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7458 !(fromP == BlackKing && toP == BlackRook && frc)))) {
7459 /* Clicked again on same color piece -- changed his mind */
7460 second = (x == fromX && y == fromY);
7462 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7463 second = FALSE; // first double-click rather than scond click
7464 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7466 promoDefaultAltered = FALSE;
7467 MarkTargetSquares(1);
7468 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7469 if (appData.highlightDragging) {
7470 SetHighlights(x, y, -1, -1);
7474 if (OKToStartUserMove(x, y)) {
7475 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7476 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7477 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7478 gatingPiece = boards[currentMove][fromY][fromX];
7479 else gatingPiece = doubleClick ? fromP : EmptySquare;
7481 fromY = y; dragging = 1;
7482 ReportClick("lift", x, y);
7483 MarkTargetSquares(0);
7484 DragPieceBegin(xPix, yPix, FALSE);
7485 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7486 promoSweep = defaultPromoChoice;
7487 selectFlag = 0; lastX = xPix; lastY = yPix;
7488 Sweep(0); // Pawn that is going to promote: preview promotion piece
7492 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7495 // ignore clicks on holdings
7496 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7499 if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7500 DragPieceEnd(xPix, yPix); dragging = 0;
7502 // a deferred attempt to click-click move an empty square on top of a piece
7503 boards[currentMove][y][x] = EmptySquare;
7505 DrawPosition(FALSE, boards[currentMove]);
7506 fromX = fromY = -1; clearFlag = 0;
7509 if (appData.animateDragging) {
7510 /* Undo animation damage if any */
7511 DrawPosition(FALSE, NULL);
7513 if (second || sweepSelecting) {
7514 /* Second up/down in same square; just abort move */
7515 if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7516 second = sweepSelecting = 0;
7518 gatingPiece = EmptySquare;
7519 MarkTargetSquares(1);
7522 ClearPremoveHighlights();
7524 /* First upclick in same square; start click-click mode */
7525 SetHighlights(x, y, -1, -1);
7532 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7533 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7534 DisplayMessage(_("only marked squares are legal"),"");
7535 DrawPosition(TRUE, NULL);
7536 return; // ignore to-click
7539 /* we now have a different from- and (possibly off-board) to-square */
7540 /* Completed move */
7541 if(!sweepSelecting) {
7546 piece = boards[currentMove][fromY][fromX];
7548 saveAnimate = appData.animate;
7549 if (clickType == Press) {
7550 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7551 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7552 // must be Edit Position mode with empty-square selected
7553 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7554 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7557 if(dragging == 2) { // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7560 if(x == killX && y == killY) { // second click on this square, which was selected as first-leg target
7561 killX = killY = -1; // this informs us no second leg is coming, so treat as to-click without intermediate
7563 if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7564 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7565 if(appData.sweepSelect) {
7566 promoSweep = defaultPromoChoice;
7567 if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7568 selectFlag = 0; lastX = xPix; lastY = yPix;
7569 Sweep(0); // Pawn that is going to promote: preview promotion piece
7571 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7572 MarkTargetSquares(1);
7574 return; // promo popup appears on up-click
7576 /* Finish clickclick move */
7577 if (appData.animate || appData.highlightLastMove) {
7578 SetHighlights(fromX, fromY, toX, toY);
7582 } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7583 sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7584 if (appData.animate || appData.highlightLastMove) {
7585 SetHighlights(fromX, fromY, toX, toY);
7591 // [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
7592 /* Finish drag move */
7593 if (appData.highlightLastMove) {
7594 SetHighlights(fromX, fromY, toX, toY);
7599 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7600 if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7601 dragging *= 2; // flag button-less dragging if we are dragging
7602 MarkTargetSquares(1);
7603 if(x == killX && y == killY) killX = killY = -1; else {
7604 killX = x; killY = y; //remeber this square as intermediate
7605 ReportClick("put", x, y); // and inform engine
7606 ReportClick("lift", x, y);
7607 MarkTargetSquares(0);
7611 DragPieceEnd(xPix, yPix); dragging = 0;
7612 /* Don't animate move and drag both */
7613 appData.animate = FALSE;
7616 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7617 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7618 ChessSquare piece = boards[currentMove][fromY][fromX];
7619 if(gameMode == EditPosition && piece != EmptySquare &&
7620 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7623 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7624 n = PieceToNumber(piece - (int)BlackPawn);
7625 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7626 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7627 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7629 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7630 n = PieceToNumber(piece);
7631 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7632 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7633 boards[currentMove][n][BOARD_WIDTH-2]++;
7635 boards[currentMove][fromY][fromX] = EmptySquare;
7639 MarkTargetSquares(1);
7640 DrawPosition(TRUE, boards[currentMove]);
7644 // off-board moves should not be highlighted
7645 if(x < 0 || y < 0) ClearHighlights();
7646 else ReportClick("put", x, y);
7648 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7650 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7651 SetHighlights(fromX, fromY, toX, toY);
7652 MarkTargetSquares(1);
7653 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7654 // [HGM] super: promotion to captured piece selected from holdings
7655 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7656 promotionChoice = TRUE;
7657 // kludge follows to temporarily execute move on display, without promoting yet
7658 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7659 boards[currentMove][toY][toX] = p;
7660 DrawPosition(FALSE, boards[currentMove]);
7661 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7662 boards[currentMove][toY][toX] = q;
7663 DisplayMessage("Click in holdings to choose piece", "");
7666 PromotionPopUp(promoChoice);
7668 int oldMove = currentMove;
7669 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7670 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7671 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7672 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7673 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7674 DrawPosition(TRUE, boards[currentMove]);
7675 MarkTargetSquares(1);
7678 appData.animate = saveAnimate;
7679 if (appData.animate || appData.animateDragging) {
7680 /* Undo animation damage if needed */
7681 DrawPosition(FALSE, NULL);
7686 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7687 { // front-end-free part taken out of PieceMenuPopup
7688 int whichMenu; int xSqr, ySqr;
7690 if(seekGraphUp) { // [HGM] seekgraph
7691 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7692 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7696 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7697 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7698 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7699 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7700 if(action == Press) {
7701 originalFlip = flipView;
7702 flipView = !flipView; // temporarily flip board to see game from partners perspective
7703 DrawPosition(TRUE, partnerBoard);
7704 DisplayMessage(partnerStatus, "");
7706 } else if(action == Release) {
7707 flipView = originalFlip;
7708 DrawPosition(TRUE, boards[currentMove]);
7714 xSqr = EventToSquare(x, BOARD_WIDTH);
7715 ySqr = EventToSquare(y, BOARD_HEIGHT);
7716 if (action == Release) {
7717 if(pieceSweep != EmptySquare) {
7718 EditPositionMenuEvent(pieceSweep, toX, toY);
7719 pieceSweep = EmptySquare;
7720 } else UnLoadPV(); // [HGM] pv
7722 if (action != Press) return -2; // return code to be ignored
7725 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7727 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7728 if (xSqr < 0 || ySqr < 0) return -1;
7729 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7730 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7731 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7732 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7736 if(!appData.icsEngineAnalyze) return -1;
7737 case IcsPlayingWhite:
7738 case IcsPlayingBlack:
7739 if(!appData.zippyPlay) goto noZip;
7742 case MachinePlaysWhite:
7743 case MachinePlaysBlack:
7744 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7745 if (!appData.dropMenu) {
7747 return 2; // flag front-end to grab mouse events
7749 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7750 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7753 if (xSqr < 0 || ySqr < 0) return -1;
7754 if (!appData.dropMenu || appData.testLegality &&
7755 gameInfo.variant != VariantBughouse &&
7756 gameInfo.variant != VariantCrazyhouse) return -1;
7757 whichMenu = 1; // drop menu
7763 if (((*fromX = xSqr) < 0) ||
7764 ((*fromY = ySqr) < 0)) {
7765 *fromX = *fromY = -1;
7769 *fromX = BOARD_WIDTH - 1 - *fromX;
7771 *fromY = BOARD_HEIGHT - 1 - *fromY;
7777 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7779 // char * hint = lastHint;
7780 FrontEndProgramStats stats;
7782 stats.which = cps == &first ? 0 : 1;
7783 stats.depth = cpstats->depth;
7784 stats.nodes = cpstats->nodes;
7785 stats.score = cpstats->score;
7786 stats.time = cpstats->time;
7787 stats.pv = cpstats->movelist;
7788 stats.hint = lastHint;
7789 stats.an_move_index = 0;
7790 stats.an_move_count = 0;
7792 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7793 stats.hint = cpstats->move_name;
7794 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7795 stats.an_move_count = cpstats->nr_moves;
7798 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
7800 SetProgramStats( &stats );
7804 ClearEngineOutputPane (int which)
7806 static FrontEndProgramStats dummyStats;
7807 dummyStats.which = which;
7808 dummyStats.pv = "#";
7809 SetProgramStats( &dummyStats );
7812 #define MAXPLAYERS 500
7815 TourneyStandings (int display)
7817 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7818 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7819 char result, *p, *names[MAXPLAYERS];
7821 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7822 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7823 names[0] = p = strdup(appData.participants);
7824 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7826 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7828 while(result = appData.results[nr]) {
7829 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7830 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7831 wScore = bScore = 0;
7833 case '+': wScore = 2; break;
7834 case '-': bScore = 2; break;
7835 case '=': wScore = bScore = 1; break;
7837 case '*': return strdup("busy"); // tourney not finished
7845 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7846 for(w=0; w<nPlayers; w++) {
7848 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7849 ranking[w] = b; points[w] = bScore; score[b] = -2;
7851 p = malloc(nPlayers*34+1);
7852 for(w=0; w<nPlayers && w<display; w++)
7853 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7859 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7860 { // count all piece types
7862 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7863 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7864 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7867 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7868 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7869 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7870 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7871 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7872 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7877 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7879 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7880 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7882 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7883 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7884 if(myPawns == 2 && nMine == 3) // KPP
7885 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7886 if(myPawns == 1 && nMine == 2) // KP
7887 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7888 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7889 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7890 if(myPawns) return FALSE;
7891 if(pCnt[WhiteRook+side])
7892 return pCnt[BlackRook-side] ||
7893 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7894 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7895 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7896 if(pCnt[WhiteCannon+side]) {
7897 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7898 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7900 if(pCnt[WhiteKnight+side])
7901 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7906 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7908 VariantClass v = gameInfo.variant;
7910 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7911 if(v == VariantShatranj) return TRUE; // always winnable through baring
7912 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7913 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7915 if(v == VariantXiangqi) {
7916 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7918 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7919 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7920 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7921 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7922 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7923 if(stale) // we have at least one last-rank P plus perhaps C
7924 return majors // KPKX
7925 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7927 return pCnt[WhiteFerz+side] // KCAK
7928 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7929 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7930 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7932 } else if(v == VariantKnightmate) {
7933 if(nMine == 1) return FALSE;
7934 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7935 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7936 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7938 if(nMine == 1) return FALSE; // bare King
7939 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
7940 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7941 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7942 // by now we have King + 1 piece (or multiple Bishops on the same color)
7943 if(pCnt[WhiteKnight+side])
7944 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7945 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7946 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7948 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7949 if(pCnt[WhiteAlfil+side])
7950 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7951 if(pCnt[WhiteWazir+side])
7952 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7959 CompareWithRights (Board b1, Board b2)
7962 if(!CompareBoards(b1, b2)) return FALSE;
7963 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7964 /* compare castling rights */
7965 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7966 rights++; /* King lost rights, while rook still had them */
7967 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7968 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7969 rights++; /* but at least one rook lost them */
7971 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7973 if( b1[CASTLING][5] != NoRights ) {
7974 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7981 Adjudicate (ChessProgramState *cps)
7982 { // [HGM] some adjudications useful with buggy engines
7983 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7984 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7985 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7986 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7987 int k, drop, count = 0; static int bare = 1;
7988 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7989 Boolean canAdjudicate = !appData.icsActive;
7991 // most tests only when we understand the game, i.e. legality-checking on
7992 if( appData.testLegality )
7993 { /* [HGM] Some more adjudications for obstinate engines */
7994 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7995 static int moveCount = 6;
7997 char *reason = NULL;
7999 /* Count what is on board. */
8000 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8002 /* Some material-based adjudications that have to be made before stalemate test */
8003 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8004 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8005 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8006 if(canAdjudicate && appData.checkMates) {
8008 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8009 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8010 "Xboard adjudication: King destroyed", GE_XBOARD );
8015 /* Bare King in Shatranj (loses) or Losers (wins) */
8016 if( nrW == 1 || nrB == 1) {
8017 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8018 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
8019 if(canAdjudicate && appData.checkMates) {
8021 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8022 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8023 "Xboard adjudication: Bare king", GE_XBOARD );
8027 if( gameInfo.variant == VariantShatranj && --bare < 0)
8029 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8030 if(canAdjudicate && appData.checkMates) {
8031 /* but only adjudicate if adjudication enabled */
8033 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8034 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8035 "Xboard adjudication: Bare king", GE_XBOARD );
8042 // don't wait for engine to announce game end if we can judge ourselves
8043 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8045 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8046 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
8047 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8048 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8051 reason = "Xboard adjudication: 3rd check";
8052 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8063 reason = "Xboard adjudication: Stalemate";
8064 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8065 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
8066 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8067 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
8068 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8069 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8070 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8071 EP_CHECKMATE : EP_WINS);
8072 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8073 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8077 reason = "Xboard adjudication: Checkmate";
8078 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8079 if(gameInfo.variant == VariantShogi) {
8080 if(forwardMostMove > backwardMostMove
8081 && moveList[forwardMostMove-1][1] == '@'
8082 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8083 reason = "XBoard adjudication: pawn-drop mate";
8084 boards[forwardMostMove][EP_STATUS] = EP_WINS;
8090 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8092 result = GameIsDrawn; break;
8094 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8096 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8100 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8102 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8103 GameEnds( result, reason, GE_XBOARD );
8107 /* Next absolutely insufficient mating material. */
8108 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8109 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8110 { /* includes KBK, KNK, KK of KBKB with like Bishops */
8112 /* always flag draws, for judging claims */
8113 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8115 if(canAdjudicate && appData.materialDraws) {
8116 /* but only adjudicate them if adjudication enabled */
8117 if(engineOpponent) {
8118 SendToProgram("force\n", engineOpponent); // suppress reply
8119 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8121 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8126 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8127 if(gameInfo.variant == VariantXiangqi ?
8128 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8130 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8131 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
8132 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
8133 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8135 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8136 { /* if the first 3 moves do not show a tactical win, declare draw */
8137 if(engineOpponent) {
8138 SendToProgram("force\n", engineOpponent); // suppress reply
8139 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8141 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8144 } else moveCount = 6;
8147 // Repetition draws and 50-move rule can be applied independently of legality testing
8149 /* Check for rep-draws */
8151 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8152 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8153 for(k = forwardMostMove-2;
8154 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8155 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8156 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8159 if(CompareBoards(boards[k], boards[forwardMostMove])) {
8160 /* compare castling rights */
8161 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8162 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8163 rights++; /* King lost rights, while rook still had them */
8164 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8165 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8166 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8167 rights++; /* but at least one rook lost them */
8169 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8170 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8172 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8173 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8174 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8177 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8178 && appData.drawRepeats > 1) {
8179 /* adjudicate after user-specified nr of repeats */
8180 int result = GameIsDrawn;
8181 char *details = "XBoard adjudication: repetition draw";
8182 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8183 // [HGM] xiangqi: check for forbidden perpetuals
8184 int m, ourPerpetual = 1, hisPerpetual = 1;
8185 for(m=forwardMostMove; m>k; m-=2) {
8186 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8187 ourPerpetual = 0; // the current mover did not always check
8188 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8189 hisPerpetual = 0; // the opponent did not always check
8191 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8192 ourPerpetual, hisPerpetual);
8193 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8194 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8195 details = "Xboard adjudication: perpetual checking";
8197 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8198 break; // (or we would have caught him before). Abort repetition-checking loop.
8200 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8201 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8203 details = "Xboard adjudication: repetition";
8205 } else // it must be XQ
8206 // Now check for perpetual chases
8207 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8208 hisPerpetual = PerpetualChase(k, forwardMostMove);
8209 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8210 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8211 static char resdet[MSG_SIZ];
8212 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8214 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8216 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8217 break; // Abort repetition-checking loop.
8219 // if neither of us is checking or chasing all the time, or both are, it is draw
8221 if(engineOpponent) {
8222 SendToProgram("force\n", engineOpponent); // suppress reply
8223 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8225 GameEnds( result, details, GE_XBOARD );
8228 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8229 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8233 /* Now we test for 50-move draws. Determine ply count */
8234 count = forwardMostMove;
8235 /* look for last irreversble move */
8236 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8238 /* if we hit starting position, add initial plies */
8239 if( count == backwardMostMove )
8240 count -= initialRulePlies;
8241 count = forwardMostMove - count;
8242 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8243 // adjust reversible move counter for checks in Xiangqi
8244 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8245 if(i < backwardMostMove) i = backwardMostMove;
8246 while(i <= forwardMostMove) {
8247 lastCheck = inCheck; // check evasion does not count
8248 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8249 if(inCheck || lastCheck) count--; // check does not count
8254 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8255 /* this is used to judge if draw claims are legal */
8256 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8257 if(engineOpponent) {
8258 SendToProgram("force\n", engineOpponent); // suppress reply
8259 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8261 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8265 /* if draw offer is pending, treat it as a draw claim
8266 * when draw condition present, to allow engines a way to
8267 * claim draws before making their move to avoid a race
8268 * condition occurring after their move
8270 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8272 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8273 p = "Draw claim: 50-move rule";
8274 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8275 p = "Draw claim: 3-fold repetition";
8276 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8277 p = "Draw claim: insufficient mating material";
8278 if( p != NULL && canAdjudicate) {
8279 if(engineOpponent) {
8280 SendToProgram("force\n", engineOpponent); // suppress reply
8281 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8283 GameEnds( GameIsDrawn, p, GE_XBOARD );
8288 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8289 if(engineOpponent) {
8290 SendToProgram("force\n", engineOpponent); // suppress reply
8291 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8293 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8299 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8300 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8301 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8306 int pieces[10], squares[10], cnt=0, r, f, res;
8308 static PPROBE_EGBB probeBB;
8309 if(!appData.testLegality) return 10;
8310 if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8311 if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8312 if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8313 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8314 ChessSquare piece = boards[forwardMostMove][r][f];
8315 int black = (piece >= BlackPawn);
8316 int type = piece - black*BlackPawn;
8317 if(piece == EmptySquare) continue;
8318 if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8319 if(type == WhiteKing) type = WhiteQueen + 1;
8320 type = egbbCode[type];
8321 squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8322 pieces[cnt] = type + black*6;
8323 if(++cnt > 5) return 11;
8325 pieces[cnt] = squares[cnt] = 0;
8327 if(loaded == 2) return 13; // loading failed before
8329 loaded = 2; // prepare for failure
8330 char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8333 if(!path) return 13; // no egbb installed
8334 strncpy(buf, path + 8, MSG_SIZ);
8335 if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8336 snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8337 lib = LoadLibrary(buf);
8338 if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8339 loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8340 probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8341 if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8342 p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8343 loaded = 1; // success!
8345 res = probeBB(forwardMostMove & 1, pieces, squares);
8346 return res > 0 ? 1 : res < 0 ? -1 : 0;
8350 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8351 { // [HGM] book: this routine intercepts moves to simulate book replies
8352 char *bookHit = NULL;
8354 if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8356 snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8357 SendToProgram(buf, cps);
8359 //first determine if the incoming move brings opponent into his book
8360 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8361 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8362 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8363 if(bookHit != NULL && !cps->bookSuspend) {
8364 // make sure opponent is not going to reply after receiving move to book position
8365 SendToProgram("force\n", cps);
8366 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8368 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8369 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8370 // now arrange restart after book miss
8372 // after a book hit we never send 'go', and the code after the call to this routine
8373 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8374 char buf[MSG_SIZ], *move = bookHit;
8376 int fromX, fromY, toX, toY;
8380 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8381 &fromX, &fromY, &toX, &toY, &promoChar)) {
8382 (void) CoordsToAlgebraic(boards[forwardMostMove],
8383 PosFlags(forwardMostMove),
8384 fromY, fromX, toY, toX, promoChar, move);
8386 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8390 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8391 SendToProgram(buf, cps);
8392 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8393 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8394 SendToProgram("go\n", cps);
8395 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8396 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8397 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8398 SendToProgram("go\n", cps);
8399 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8401 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8405 LoadError (char *errmess, ChessProgramState *cps)
8406 { // unloads engine and switches back to -ncp mode if it was first
8407 if(cps->initDone) return FALSE;
8408 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8409 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8412 appData.noChessProgram = TRUE;
8413 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8414 gameMode = BeginningOfGame; ModeHighlight();
8417 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8418 DisplayMessage("", ""); // erase waiting message
8419 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8424 ChessProgramState *savedState;
8426 DeferredBookMove (void)
8428 if(savedState->lastPing != savedState->lastPong)
8429 ScheduleDelayedEvent(DeferredBookMove, 10);
8431 HandleMachineMove(savedMessage, savedState);
8434 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8435 static ChessProgramState *stalledEngine;
8436 static char stashedInputMove[MSG_SIZ];
8439 HandleMachineMove (char *message, ChessProgramState *cps)
8441 static char firstLeg[20];
8442 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8443 char realname[MSG_SIZ];
8444 int fromX, fromY, toX, toY;
8446 char promoChar, roar;
8448 int machineWhite, oldError;
8451 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8452 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8453 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8454 DisplayError(_("Invalid pairing from pairing engine"), 0);
8457 pairingReceived = 1;
8459 return; // Skim the pairing messages here.
8462 oldError = cps->userError; cps->userError = 0;
8464 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8466 * Kludge to ignore BEL characters
8468 while (*message == '\007') message++;
8471 * [HGM] engine debug message: ignore lines starting with '#' character
8473 if(cps->debug && *message == '#') return;
8476 * Look for book output
8478 if (cps == &first && bookRequested) {
8479 if (message[0] == '\t' || message[0] == ' ') {
8480 /* Part of the book output is here; append it */
8481 strcat(bookOutput, message);
8482 strcat(bookOutput, " \n");
8484 } else if (bookOutput[0] != NULLCHAR) {
8485 /* All of book output has arrived; display it */
8486 char *p = bookOutput;
8487 while (*p != NULLCHAR) {
8488 if (*p == '\t') *p = ' ';
8491 DisplayInformation(bookOutput);
8492 bookRequested = FALSE;
8493 /* Fall through to parse the current output */
8498 * Look for machine move.
8500 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8501 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8503 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8504 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8505 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8506 stalledEngine = cps;
8507 if(appData.ponderNextMove) { // bring opponent out of ponder
8508 if(gameMode == TwoMachinesPlay) {
8509 if(cps->other->pause)
8510 PauseEngine(cps->other);
8512 SendToProgram("easy\n", cps->other);
8519 /* This method is only useful on engines that support ping */
8520 if (cps->lastPing != cps->lastPong) {
8521 if (gameMode == BeginningOfGame) {
8522 /* Extra move from before last new; ignore */
8523 if (appData.debugMode) {
8524 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8527 if (appData.debugMode) {
8528 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8529 cps->which, gameMode);
8532 SendToProgram("undo\n", cps);
8538 case BeginningOfGame:
8539 /* Extra move from before last reset; ignore */
8540 if (appData.debugMode) {
8541 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8548 /* Extra move after we tried to stop. The mode test is
8549 not a reliable way of detecting this problem, but it's
8550 the best we can do on engines that don't support ping.
8552 if (appData.debugMode) {
8553 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8554 cps->which, gameMode);
8556 SendToProgram("undo\n", cps);
8559 case MachinePlaysWhite:
8560 case IcsPlayingWhite:
8561 machineWhite = TRUE;
8564 case MachinePlaysBlack:
8565 case IcsPlayingBlack:
8566 machineWhite = FALSE;
8569 case TwoMachinesPlay:
8570 machineWhite = (cps->twoMachinesColor[0] == 'w');
8573 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8574 if (appData.debugMode) {
8576 "Ignoring move out of turn by %s, gameMode %d"
8577 ", forwardMost %d\n",
8578 cps->which, gameMode, forwardMostMove);
8583 if(cps->alphaRank) AlphaRank(machineMove, 4);
8585 // [HGM] lion: (some very limited) support for Alien protocol
8587 if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8588 safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8590 } else if(firstLeg[0]) { // there was a previous leg;
8591 // only support case where same piece makes two step (and don't even test that!)
8592 char buf[20], *p = machineMove+1, *q = buf+1, f;
8593 safeStrCpy(buf, machineMove, 20);
8594 while(isdigit(*q)) q++; // find start of to-square
8595 safeStrCpy(machineMove, firstLeg, 20);
8596 while(isdigit(*p)) p++;
8597 safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8598 sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8599 firstLeg[0] = NULLCHAR;
8602 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8603 &fromX, &fromY, &toX, &toY, &promoChar)) {
8604 /* Machine move could not be parsed; ignore it. */
8605 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8606 machineMove, _(cps->which));
8607 DisplayMoveError(buf1);
8608 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8609 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8610 if (gameMode == TwoMachinesPlay) {
8611 GameEnds(machineWhite ? BlackWins : WhiteWins,
8617 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8618 /* So we have to redo legality test with true e.p. status here, */
8619 /* to make sure an illegal e.p. capture does not slip through, */
8620 /* to cause a forfeit on a justified illegal-move complaint */
8621 /* of the opponent. */
8622 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8624 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8625 fromY, fromX, toY, toX, promoChar);
8626 if(moveType == IllegalMove) {
8627 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8628 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8629 GameEnds(machineWhite ? BlackWins : WhiteWins,
8632 } else if(!appData.fischerCastling)
8633 /* [HGM] Kludge to handle engines that send FRC-style castling
8634 when they shouldn't (like TSCP-Gothic) */
8636 case WhiteASideCastleFR:
8637 case BlackASideCastleFR:
8639 currentMoveString[2]++;
8641 case WhiteHSideCastleFR:
8642 case BlackHSideCastleFR:
8644 currentMoveString[2]--;
8646 default: ; // nothing to do, but suppresses warning of pedantic compilers
8649 hintRequested = FALSE;
8650 lastHint[0] = NULLCHAR;
8651 bookRequested = FALSE;
8652 /* Program may be pondering now */
8653 cps->maybeThinking = TRUE;
8654 if (cps->sendTime == 2) cps->sendTime = 1;
8655 if (cps->offeredDraw) cps->offeredDraw--;
8657 /* [AS] Save move info*/
8658 pvInfoList[ forwardMostMove ].score = programStats.score;
8659 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8660 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8662 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8664 /* Test suites abort the 'game' after one move */
8665 if(*appData.finger) {
8667 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8668 if(!f) f = fopen(appData.finger, "w");
8669 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8670 else { DisplayFatalError("Bad output file", errno, 0); return; }
8672 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8675 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8676 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8679 while( count < adjudicateLossPlies ) {
8680 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8683 score = -score; /* Flip score for winning side */
8686 if( score > adjudicateLossThreshold ) {
8693 if( count >= adjudicateLossPlies ) {
8694 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8696 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8697 "Xboard adjudication",
8704 if(Adjudicate(cps)) {
8705 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8706 return; // [HGM] adjudicate: for all automatic game ends
8710 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8712 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8713 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8715 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8717 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8719 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8720 char buf[3*MSG_SIZ];
8722 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8723 programStats.score / 100.,
8725 programStats.time / 100.,
8726 (unsigned int)programStats.nodes,
8727 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8728 programStats.movelist);
8730 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8735 /* [AS] Clear stats for next move */
8736 ClearProgramStats();
8737 thinkOutput[0] = NULLCHAR;
8738 hiddenThinkOutputState = 0;
8741 if (gameMode == TwoMachinesPlay) {
8742 /* [HGM] relaying draw offers moved to after reception of move */
8743 /* and interpreting offer as claim if it brings draw condition */
8744 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8745 SendToProgram("draw\n", cps->other);
8747 if (cps->other->sendTime) {
8748 SendTimeRemaining(cps->other,
8749 cps->other->twoMachinesColor[0] == 'w');
8751 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8752 if (firstMove && !bookHit) {
8754 if (cps->other->useColors) {
8755 SendToProgram(cps->other->twoMachinesColor, cps->other);
8757 SendToProgram("go\n", cps->other);
8759 cps->other->maybeThinking = TRUE;
8762 roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8764 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8766 if (!pausing && appData.ringBellAfterMoves) {
8767 if(!roar) RingBell();
8771 * Reenable menu items that were disabled while
8772 * machine was thinking
8774 if (gameMode != TwoMachinesPlay)
8775 SetUserThinkingEnables();
8777 // [HGM] book: after book hit opponent has received move and is now in force mode
8778 // force the book reply into it, and then fake that it outputted this move by jumping
8779 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8781 static char bookMove[MSG_SIZ]; // a bit generous?
8783 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8784 strcat(bookMove, bookHit);
8787 programStats.nodes = programStats.depth = programStats.time =
8788 programStats.score = programStats.got_only_move = 0;
8789 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8791 if(cps->lastPing != cps->lastPong) {
8792 savedMessage = message; // args for deferred call
8794 ScheduleDelayedEvent(DeferredBookMove, 10);
8803 /* Set special modes for chess engines. Later something general
8804 * could be added here; for now there is just one kludge feature,
8805 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8806 * when "xboard" is given as an interactive command.
8808 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8809 cps->useSigint = FALSE;
8810 cps->useSigterm = FALSE;
8812 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8813 ParseFeatures(message+8, cps);
8814 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8817 if (!strncmp(message, "setup ", 6) &&
8818 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8819 NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8820 ) { // [HGM] allow first engine to define opening position
8821 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8822 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8824 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8825 if(startedFromSetupPosition) return;
8826 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8828 while(message[s] && message[s++] != ' ');
8829 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8830 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8831 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8832 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
8833 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8834 if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8837 ParseFEN(boards[0], &dummy, message+s, FALSE);
8838 DrawPosition(TRUE, boards[0]);
8839 startedFromSetupPosition = TRUE;
8842 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8843 * want this, I was asked to put it in, and obliged.
8845 if (!strncmp(message, "setboard ", 9)) {
8846 Board initial_position;
8848 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8850 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8851 DisplayError(_("Bad FEN received from engine"), 0);
8855 CopyBoard(boards[0], initial_position);
8856 initialRulePlies = FENrulePlies;
8857 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8858 else gameMode = MachinePlaysBlack;
8859 DrawPosition(FALSE, boards[currentMove]);
8865 * Look for communication commands
8867 if (!strncmp(message, "telluser ", 9)) {
8868 if(message[9] == '\\' && message[10] == '\\')
8869 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8871 DisplayNote(message + 9);
8874 if (!strncmp(message, "tellusererror ", 14)) {
8876 if(message[14] == '\\' && message[15] == '\\')
8877 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8879 DisplayError(message + 14, 0);
8882 if (!strncmp(message, "tellopponent ", 13)) {
8883 if (appData.icsActive) {
8885 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8889 DisplayNote(message + 13);
8893 if (!strncmp(message, "tellothers ", 11)) {
8894 if (appData.icsActive) {
8896 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8899 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8902 if (!strncmp(message, "tellall ", 8)) {
8903 if (appData.icsActive) {
8905 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8909 DisplayNote(message + 8);
8913 if (strncmp(message, "warning", 7) == 0) {
8914 /* Undocumented feature, use tellusererror in new code */
8915 DisplayError(message, 0);
8918 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8919 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8920 strcat(realname, " query");
8921 AskQuestion(realname, buf2, buf1, cps->pr);
8924 /* Commands from the engine directly to ICS. We don't allow these to be
8925 * sent until we are logged on. Crafty kibitzes have been known to
8926 * interfere with the login process.
8929 if (!strncmp(message, "tellics ", 8)) {
8930 SendToICS(message + 8);
8934 if (!strncmp(message, "tellicsnoalias ", 15)) {
8935 SendToICS(ics_prefix);
8936 SendToICS(message + 15);
8940 /* The following are for backward compatibility only */
8941 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8942 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8943 SendToICS(ics_prefix);
8949 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8950 if(initPing == cps->lastPong) {
8951 if(gameInfo.variant == VariantUnknown) {
8952 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8953 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8954 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8960 if(!strncmp(message, "highlight ", 10)) {
8961 if(appData.testLegality && appData.markers) return;
8962 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8965 if(!strncmp(message, "click ", 6)) {
8966 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8967 if(appData.testLegality || !appData.oneClick) return;
8968 sscanf(message+6, "%c%d%c", &f, &y, &c);
8969 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8970 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8971 x = x*squareSize + (x+1)*lineGap + squareSize/2;
8972 y = y*squareSize + (y+1)*lineGap + squareSize/2;
8973 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8974 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8975 LeftClick(Release, lastLeftX, lastLeftY);
8976 controlKey = (c == ',');
8977 LeftClick(Press, x, y);
8978 LeftClick(Release, x, y);
8979 first.highlight = f;
8983 * If the move is illegal, cancel it and redraw the board.
8984 * Also deal with other error cases. Matching is rather loose
8985 * here to accommodate engines written before the spec.
8987 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8988 strncmp(message, "Error", 5) == 0) {
8989 if (StrStr(message, "name") ||
8990 StrStr(message, "rating") || StrStr(message, "?") ||
8991 StrStr(message, "result") || StrStr(message, "board") ||
8992 StrStr(message, "bk") || StrStr(message, "computer") ||
8993 StrStr(message, "variant") || StrStr(message, "hint") ||
8994 StrStr(message, "random") || StrStr(message, "depth") ||
8995 StrStr(message, "accepted")) {
8998 if (StrStr(message, "protover")) {
8999 /* Program is responding to input, so it's apparently done
9000 initializing, and this error message indicates it is
9001 protocol version 1. So we don't need to wait any longer
9002 for it to initialize and send feature commands. */
9003 FeatureDone(cps, 1);
9004 cps->protocolVersion = 1;
9007 cps->maybeThinking = FALSE;
9009 if (StrStr(message, "draw")) {
9010 /* Program doesn't have "draw" command */
9011 cps->sendDrawOffers = 0;
9014 if (cps->sendTime != 1 &&
9015 (StrStr(message, "time") || StrStr(message, "otim"))) {
9016 /* Program apparently doesn't have "time" or "otim" command */
9020 if (StrStr(message, "analyze")) {
9021 cps->analysisSupport = FALSE;
9022 cps->analyzing = FALSE;
9023 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9024 EditGameEvent(); // [HGM] try to preserve loaded game
9025 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9026 DisplayError(buf2, 0);
9029 if (StrStr(message, "(no matching move)st")) {
9030 /* Special kludge for GNU Chess 4 only */
9031 cps->stKludge = TRUE;
9032 SendTimeControl(cps, movesPerSession, timeControl,
9033 timeIncrement, appData.searchDepth,
9037 if (StrStr(message, "(no matching move)sd")) {
9038 /* Special kludge for GNU Chess 4 only */
9039 cps->sdKludge = TRUE;
9040 SendTimeControl(cps, movesPerSession, timeControl,
9041 timeIncrement, appData.searchDepth,
9045 if (!StrStr(message, "llegal")) {
9048 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9049 gameMode == IcsIdle) return;
9050 if (forwardMostMove <= backwardMostMove) return;
9051 if (pausing) PauseEvent();
9052 if(appData.forceIllegal) {
9053 // [HGM] illegal: machine refused move; force position after move into it
9054 SendToProgram("force\n", cps);
9055 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9056 // we have a real problem now, as SendBoard will use the a2a3 kludge
9057 // when black is to move, while there might be nothing on a2 or black
9058 // might already have the move. So send the board as if white has the move.
9059 // But first we must change the stm of the engine, as it refused the last move
9060 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9061 if(WhiteOnMove(forwardMostMove)) {
9062 SendToProgram("a7a6\n", cps); // for the engine black still had the move
9063 SendBoard(cps, forwardMostMove); // kludgeless board
9065 SendToProgram("a2a3\n", cps); // for the engine white still had the move
9066 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9067 SendBoard(cps, forwardMostMove+1); // kludgeless board
9069 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9070 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9071 gameMode == TwoMachinesPlay)
9072 SendToProgram("go\n", cps);
9075 if (gameMode == PlayFromGameFile) {
9076 /* Stop reading this game file */
9077 gameMode = EditGame;
9080 /* [HGM] illegal-move claim should forfeit game when Xboard */
9081 /* only passes fully legal moves */
9082 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9083 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9084 "False illegal-move claim", GE_XBOARD );
9085 return; // do not take back move we tested as valid
9087 currentMove = forwardMostMove-1;
9088 DisplayMove(currentMove-1); /* before DisplayMoveError */
9089 SwitchClocks(forwardMostMove-1); // [HGM] race
9090 DisplayBothClocks();
9091 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9092 parseList[currentMove], _(cps->which));
9093 DisplayMoveError(buf1);
9094 DrawPosition(FALSE, boards[currentMove]);
9096 SetUserThinkingEnables();
9099 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9100 /* Program has a broken "time" command that
9101 outputs a string not ending in newline.
9107 * If chess program startup fails, exit with an error message.
9108 * Attempts to recover here are futile. [HGM] Well, we try anyway
9110 if ((StrStr(message, "unknown host") != NULL)
9111 || (StrStr(message, "No remote directory") != NULL)
9112 || (StrStr(message, "not found") != NULL)
9113 || (StrStr(message, "No such file") != NULL)
9114 || (StrStr(message, "can't alloc") != NULL)
9115 || (StrStr(message, "Permission denied") != NULL)) {
9117 cps->maybeThinking = FALSE;
9118 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9119 _(cps->which), cps->program, cps->host, message);
9120 RemoveInputSource(cps->isr);
9121 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9122 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9123 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9129 * Look for hint output
9131 if (sscanf(message, "Hint: %s", buf1) == 1) {
9132 if (cps == &first && hintRequested) {
9133 hintRequested = FALSE;
9134 if (ParseOneMove(buf1, forwardMostMove, &moveType,
9135 &fromX, &fromY, &toX, &toY, &promoChar)) {
9136 (void) CoordsToAlgebraic(boards[forwardMostMove],
9137 PosFlags(forwardMostMove),
9138 fromY, fromX, toY, toX, promoChar, buf1);
9139 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9140 DisplayInformation(buf2);
9142 /* Hint move could not be parsed!? */
9143 snprintf(buf2, sizeof(buf2),
9144 _("Illegal hint move \"%s\"\nfrom %s chess program"),
9145 buf1, _(cps->which));
9146 DisplayError(buf2, 0);
9149 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9155 * Ignore other messages if game is not in progress
9157 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9158 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9161 * look for win, lose, draw, or draw offer
9163 if (strncmp(message, "1-0", 3) == 0) {
9164 char *p, *q, *r = "";
9165 p = strchr(message, '{');
9173 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9175 } else if (strncmp(message, "0-1", 3) == 0) {
9176 char *p, *q, *r = "";
9177 p = strchr(message, '{');
9185 /* Kludge for Arasan 4.1 bug */
9186 if (strcmp(r, "Black resigns") == 0) {
9187 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9190 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9192 } else if (strncmp(message, "1/2", 3) == 0) {
9193 char *p, *q, *r = "";
9194 p = strchr(message, '{');
9203 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9206 } else if (strncmp(message, "White resign", 12) == 0) {
9207 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9209 } else if (strncmp(message, "Black resign", 12) == 0) {
9210 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9212 } else if (strncmp(message, "White matches", 13) == 0 ||
9213 strncmp(message, "Black matches", 13) == 0 ) {
9214 /* [HGM] ignore GNUShogi noises */
9216 } else if (strncmp(message, "White", 5) == 0 &&
9217 message[5] != '(' &&
9218 StrStr(message, "Black") == NULL) {
9219 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9221 } else if (strncmp(message, "Black", 5) == 0 &&
9222 message[5] != '(') {
9223 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9225 } else if (strcmp(message, "resign") == 0 ||
9226 strcmp(message, "computer resigns") == 0) {
9228 case MachinePlaysBlack:
9229 case IcsPlayingBlack:
9230 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9232 case MachinePlaysWhite:
9233 case IcsPlayingWhite:
9234 GameEnds(BlackWins, "White resigns", GE_ENGINE);
9236 case TwoMachinesPlay:
9237 if (cps->twoMachinesColor[0] == 'w')
9238 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9240 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9247 } else if (strncmp(message, "opponent mates", 14) == 0) {
9249 case MachinePlaysBlack:
9250 case IcsPlayingBlack:
9251 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9253 case MachinePlaysWhite:
9254 case IcsPlayingWhite:
9255 GameEnds(BlackWins, "Black mates", GE_ENGINE);
9257 case TwoMachinesPlay:
9258 if (cps->twoMachinesColor[0] == 'w')
9259 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9261 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9268 } else if (strncmp(message, "computer mates", 14) == 0) {
9270 case MachinePlaysBlack:
9271 case IcsPlayingBlack:
9272 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9274 case MachinePlaysWhite:
9275 case IcsPlayingWhite:
9276 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9278 case TwoMachinesPlay:
9279 if (cps->twoMachinesColor[0] == 'w')
9280 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9282 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9289 } else if (strncmp(message, "checkmate", 9) == 0) {
9290 if (WhiteOnMove(forwardMostMove)) {
9291 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9293 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9296 } else if (strstr(message, "Draw") != NULL ||
9297 strstr(message, "game is a draw") != NULL) {
9298 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9300 } else if (strstr(message, "offer") != NULL &&
9301 strstr(message, "draw") != NULL) {
9303 if (appData.zippyPlay && first.initDone) {
9304 /* Relay offer to ICS */
9305 SendToICS(ics_prefix);
9306 SendToICS("draw\n");
9309 cps->offeredDraw = 2; /* valid until this engine moves twice */
9310 if (gameMode == TwoMachinesPlay) {
9311 if (cps->other->offeredDraw) {
9312 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9313 /* [HGM] in two-machine mode we delay relaying draw offer */
9314 /* until after we also have move, to see if it is really claim */
9316 } else if (gameMode == MachinePlaysWhite ||
9317 gameMode == MachinePlaysBlack) {
9318 if (userOfferedDraw) {
9319 DisplayInformation(_("Machine accepts your draw offer"));
9320 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9322 DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9329 * Look for thinking output
9331 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9332 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9334 int plylev, mvleft, mvtot, curscore, time;
9335 char mvname[MOVE_LEN];
9339 int prefixHint = FALSE;
9340 mvname[0] = NULLCHAR;
9343 case MachinePlaysBlack:
9344 case IcsPlayingBlack:
9345 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9347 case MachinePlaysWhite:
9348 case IcsPlayingWhite:
9349 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9354 case IcsObserving: /* [DM] icsEngineAnalyze */
9355 if (!appData.icsEngineAnalyze) ignore = TRUE;
9357 case TwoMachinesPlay:
9358 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9368 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9370 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9371 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9373 if (plyext != ' ' && plyext != '\t') {
9377 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9378 if( cps->scoreIsAbsolute &&
9379 ( gameMode == MachinePlaysBlack ||
9380 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9381 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9382 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9383 !WhiteOnMove(currentMove)
9386 curscore = -curscore;
9389 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9391 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9394 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9395 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9396 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9397 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9398 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9399 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9403 /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9404 DisplayError(_("failed writing PV"), 0);
9407 tempStats.depth = plylev;
9408 tempStats.nodes = nodes;
9409 tempStats.time = time;
9410 tempStats.score = curscore;
9411 tempStats.got_only_move = 0;
9413 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9416 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9417 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9418 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9419 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9420 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9421 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9422 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9423 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9426 /* Buffer overflow protection */
9427 if (pv[0] != NULLCHAR) {
9428 if (strlen(pv) >= sizeof(tempStats.movelist)
9429 && appData.debugMode) {
9431 "PV is too long; using the first %u bytes.\n",
9432 (unsigned) sizeof(tempStats.movelist) - 1);
9435 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9437 sprintf(tempStats.movelist, " no PV\n");
9440 if (tempStats.seen_stat) {
9441 tempStats.ok_to_send = 1;
9444 if (strchr(tempStats.movelist, '(') != NULL) {
9445 tempStats.line_is_book = 1;
9446 tempStats.nr_moves = 0;
9447 tempStats.moves_left = 0;
9449 tempStats.line_is_book = 0;
9452 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9453 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9455 SendProgramStatsToFrontend( cps, &tempStats );
9458 [AS] Protect the thinkOutput buffer from overflow... this
9459 is only useful if buf1 hasn't overflowed first!
9461 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9463 (gameMode == TwoMachinesPlay ?
9464 ToUpper(cps->twoMachinesColor[0]) : ' '),
9465 ((double) curscore) / 100.0,
9466 prefixHint ? lastHint : "",
9467 prefixHint ? " " : "" );
9469 if( buf1[0] != NULLCHAR ) {
9470 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9472 if( strlen(pv) > max_len ) {
9473 if( appData.debugMode) {
9474 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9476 pv[max_len+1] = '\0';
9479 strcat( thinkOutput, pv);
9482 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9483 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9484 DisplayMove(currentMove - 1);
9488 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9489 /* crafty (9.25+) says "(only move) <move>"
9490 * if there is only 1 legal move
9492 sscanf(p, "(only move) %s", buf1);
9493 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9494 sprintf(programStats.movelist, "%s (only move)", buf1);
9495 programStats.depth = 1;
9496 programStats.nr_moves = 1;
9497 programStats.moves_left = 1;
9498 programStats.nodes = 1;
9499 programStats.time = 1;
9500 programStats.got_only_move = 1;
9502 /* Not really, but we also use this member to
9503 mean "line isn't going to change" (Crafty
9504 isn't searching, so stats won't change) */
9505 programStats.line_is_book = 1;
9507 SendProgramStatsToFrontend( cps, &programStats );
9509 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9510 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9511 DisplayMove(currentMove - 1);
9514 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9515 &time, &nodes, &plylev, &mvleft,
9516 &mvtot, mvname) >= 5) {
9517 /* The stat01: line is from Crafty (9.29+) in response
9518 to the "." command */
9519 programStats.seen_stat = 1;
9520 cps->maybeThinking = TRUE;
9522 if (programStats.got_only_move || !appData.periodicUpdates)
9525 programStats.depth = plylev;
9526 programStats.time = time;
9527 programStats.nodes = nodes;
9528 programStats.moves_left = mvleft;
9529 programStats.nr_moves = mvtot;
9530 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9531 programStats.ok_to_send = 1;
9532 programStats.movelist[0] = '\0';
9534 SendProgramStatsToFrontend( cps, &programStats );
9538 } else if (strncmp(message,"++",2) == 0) {
9539 /* Crafty 9.29+ outputs this */
9540 programStats.got_fail = 2;
9543 } else if (strncmp(message,"--",2) == 0) {
9544 /* Crafty 9.29+ outputs this */
9545 programStats.got_fail = 1;
9548 } else if (thinkOutput[0] != NULLCHAR &&
9549 strncmp(message, " ", 4) == 0) {
9550 unsigned message_len;
9553 while (*p && *p == ' ') p++;
9555 message_len = strlen( p );
9557 /* [AS] Avoid buffer overflow */
9558 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9559 strcat(thinkOutput, " ");
9560 strcat(thinkOutput, p);
9563 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9564 strcat(programStats.movelist, " ");
9565 strcat(programStats.movelist, p);
9568 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9569 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9570 DisplayMove(currentMove - 1);
9578 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9579 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9581 ChessProgramStats cpstats;
9583 if (plyext != ' ' && plyext != '\t') {
9587 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9588 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9589 curscore = -curscore;
9592 cpstats.depth = plylev;
9593 cpstats.nodes = nodes;
9594 cpstats.time = time;
9595 cpstats.score = curscore;
9596 cpstats.got_only_move = 0;
9597 cpstats.movelist[0] = '\0';
9599 if (buf1[0] != NULLCHAR) {
9600 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9603 cpstats.ok_to_send = 0;
9604 cpstats.line_is_book = 0;
9605 cpstats.nr_moves = 0;
9606 cpstats.moves_left = 0;
9608 SendProgramStatsToFrontend( cps, &cpstats );
9615 /* Parse a game score from the character string "game", and
9616 record it as the history of the current game. The game
9617 score is NOT assumed to start from the standard position.
9618 The display is not updated in any way.
9621 ParseGameHistory (char *game)
9624 int fromX, fromY, toX, toY, boardIndex;
9629 if (appData.debugMode)
9630 fprintf(debugFP, "Parsing game history: %s\n", game);
9632 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9633 gameInfo.site = StrSave(appData.icsHost);
9634 gameInfo.date = PGNDate();
9635 gameInfo.round = StrSave("-");
9637 /* Parse out names of players */
9638 while (*game == ' ') game++;
9640 while (*game != ' ') *p++ = *game++;
9642 gameInfo.white = StrSave(buf);
9643 while (*game == ' ') game++;
9645 while (*game != ' ' && *game != '\n') *p++ = *game++;
9647 gameInfo.black = StrSave(buf);
9650 boardIndex = blackPlaysFirst ? 1 : 0;
9653 yyboardindex = boardIndex;
9654 moveType = (ChessMove) Myylex();
9656 case IllegalMove: /* maybe suicide chess, etc. */
9657 if (appData.debugMode) {
9658 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9659 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9660 setbuf(debugFP, NULL);
9662 case WhitePromotion:
9663 case BlackPromotion:
9664 case WhiteNonPromotion:
9665 case BlackNonPromotion:
9668 case WhiteCapturesEnPassant:
9669 case BlackCapturesEnPassant:
9670 case WhiteKingSideCastle:
9671 case WhiteQueenSideCastle:
9672 case BlackKingSideCastle:
9673 case BlackQueenSideCastle:
9674 case WhiteKingSideCastleWild:
9675 case WhiteQueenSideCastleWild:
9676 case BlackKingSideCastleWild:
9677 case BlackQueenSideCastleWild:
9679 case WhiteHSideCastleFR:
9680 case WhiteASideCastleFR:
9681 case BlackHSideCastleFR:
9682 case BlackASideCastleFR:
9684 fromX = currentMoveString[0] - AAA;
9685 fromY = currentMoveString[1] - ONE;
9686 toX = currentMoveString[2] - AAA;
9687 toY = currentMoveString[3] - ONE;
9688 promoChar = currentMoveString[4];
9692 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9693 fromX = moveType == WhiteDrop ?
9694 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9695 (int) CharToPiece(ToLower(currentMoveString[0]));
9697 toX = currentMoveString[2] - AAA;
9698 toY = currentMoveString[3] - ONE;
9699 promoChar = NULLCHAR;
9703 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9704 if (appData.debugMode) {
9705 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9706 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9707 setbuf(debugFP, NULL);
9709 DisplayError(buf, 0);
9711 case ImpossibleMove:
9713 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9714 if (appData.debugMode) {
9715 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9716 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9717 setbuf(debugFP, NULL);
9719 DisplayError(buf, 0);
9722 if (boardIndex < backwardMostMove) {
9723 /* Oops, gap. How did that happen? */
9724 DisplayError(_("Gap in move list"), 0);
9727 backwardMostMove = blackPlaysFirst ? 1 : 0;
9728 if (boardIndex > forwardMostMove) {
9729 forwardMostMove = boardIndex;
9733 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9734 strcat(parseList[boardIndex-1], " ");
9735 strcat(parseList[boardIndex-1], yy_text);
9747 case GameUnfinished:
9748 if (gameMode == IcsExamining) {
9749 if (boardIndex < backwardMostMove) {
9750 /* Oops, gap. How did that happen? */
9753 backwardMostMove = blackPlaysFirst ? 1 : 0;
9756 gameInfo.result = moveType;
9757 p = strchr(yy_text, '{');
9758 if (p == NULL) p = strchr(yy_text, '(');
9761 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9763 q = strchr(p, *p == '{' ? '}' : ')');
9764 if (q != NULL) *q = NULLCHAR;
9767 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9768 gameInfo.resultDetails = StrSave(p);
9771 if (boardIndex >= forwardMostMove &&
9772 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9773 backwardMostMove = blackPlaysFirst ? 1 : 0;
9776 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9777 fromY, fromX, toY, toX, promoChar,
9778 parseList[boardIndex]);
9779 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9780 /* currentMoveString is set as a side-effect of yylex */
9781 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9782 strcat(moveList[boardIndex], "\n");
9784 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9785 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9791 if(!IS_SHOGI(gameInfo.variant))
9792 strcat(parseList[boardIndex - 1], "+");
9796 strcat(parseList[boardIndex - 1], "#");
9803 /* Apply a move to the given board */
9805 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9807 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9808 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9810 /* [HGM] compute & store e.p. status and castling rights for new position */
9811 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9813 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9814 oldEP = (signed char)board[EP_STATUS];
9815 board[EP_STATUS] = EP_NONE;
9817 if (fromY == DROP_RANK) {
9819 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9820 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9823 piece = board[toY][toX] = (ChessSquare) fromX;
9828 if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9829 victim = board[killY][killX],
9830 board[killY][killX] = EmptySquare,
9831 board[EP_STATUS] = EP_CAPTURE;
9833 if( board[toY][toX] != EmptySquare ) {
9834 board[EP_STATUS] = EP_CAPTURE;
9835 if( (fromX != toX || fromY != toY) && // not igui!
9836 (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9837 captured == BlackLion && board[fromY][fromX] != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules
9838 board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9842 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9843 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9844 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9846 if( board[fromY][fromX] == WhitePawn ) {
9847 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9848 board[EP_STATUS] = EP_PAWN_MOVE;
9850 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9851 gameInfo.variant != VariantBerolina || toX < fromX)
9852 board[EP_STATUS] = toX | berolina;
9853 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9854 gameInfo.variant != VariantBerolina || toX > fromX)
9855 board[EP_STATUS] = toX;
9858 if( board[fromY][fromX] == BlackPawn ) {
9859 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9860 board[EP_STATUS] = EP_PAWN_MOVE;
9861 if( toY-fromY== -2) {
9862 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9863 gameInfo.variant != VariantBerolina || toX < fromX)
9864 board[EP_STATUS] = toX | berolina;
9865 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9866 gameInfo.variant != VariantBerolina || toX > fromX)
9867 board[EP_STATUS] = toX;
9871 for(i=0; i<nrCastlingRights; i++) {
9872 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9873 board[CASTLING][i] == toX && castlingRank[i] == toY
9874 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9877 if(gameInfo.variant == VariantSChess) { // update virginity
9878 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9879 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9880 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
9881 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
9884 if (fromX == toX && fromY == toY) return;
9886 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9887 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9888 if(gameInfo.variant == VariantKnightmate)
9889 king += (int) WhiteUnicorn - (int) WhiteKing;
9891 /* Code added by Tord: */
9892 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9893 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9894 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9895 board[fromY][fromX] = EmptySquare;
9896 board[toY][toX] = EmptySquare;
9897 if((toX > fromX) != (piece == WhiteRook)) {
9898 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9900 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9902 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9903 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9904 board[fromY][fromX] = EmptySquare;
9905 board[toY][toX] = EmptySquare;
9906 if((toX > fromX) != (piece == BlackRook)) {
9907 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9909 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9911 /* End of code added by Tord */
9913 } else if (board[fromY][fromX] == king
9914 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9915 && toY == fromY && toX > fromX+1) {
9916 board[fromY][fromX] = EmptySquare;
9917 board[toY][toX] = king;
9918 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9919 board[fromY][BOARD_RGHT-1] = EmptySquare;
9920 } else if (board[fromY][fromX] == king
9921 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9922 && toY == fromY && toX < fromX-1) {
9923 board[fromY][fromX] = EmptySquare;
9924 board[toY][toX] = king;
9925 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9926 board[fromY][BOARD_LEFT] = EmptySquare;
9927 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9928 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9929 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9931 /* white pawn promotion */
9932 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9933 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9934 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9935 board[fromY][fromX] = EmptySquare;
9936 } else if ((fromY >= BOARD_HEIGHT>>1)
9937 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9939 && gameInfo.variant != VariantXiangqi
9940 && gameInfo.variant != VariantBerolina
9941 && (board[fromY][fromX] == WhitePawn)
9942 && (board[toY][toX] == EmptySquare)) {
9943 board[fromY][fromX] = EmptySquare;
9944 board[toY][toX] = WhitePawn;
9945 captured = board[toY - 1][toX];
9946 board[toY - 1][toX] = EmptySquare;
9947 } else if ((fromY == BOARD_HEIGHT-4)
9949 && gameInfo.variant == VariantBerolina
9950 && (board[fromY][fromX] == WhitePawn)
9951 && (board[toY][toX] == EmptySquare)) {
9952 board[fromY][fromX] = EmptySquare;
9953 board[toY][toX] = WhitePawn;
9954 if(oldEP & EP_BEROLIN_A) {
9955 captured = board[fromY][fromX-1];
9956 board[fromY][fromX-1] = EmptySquare;
9957 }else{ captured = board[fromY][fromX+1];
9958 board[fromY][fromX+1] = EmptySquare;
9960 } else if (board[fromY][fromX] == king
9961 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9962 && toY == fromY && toX > fromX+1) {
9963 board[fromY][fromX] = EmptySquare;
9964 board[toY][toX] = king;
9965 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9966 board[fromY][BOARD_RGHT-1] = EmptySquare;
9967 } else if (board[fromY][fromX] == king
9968 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9969 && toY == fromY && toX < fromX-1) {
9970 board[fromY][fromX] = EmptySquare;
9971 board[toY][toX] = king;
9972 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9973 board[fromY][BOARD_LEFT] = EmptySquare;
9974 } else if (fromY == 7 && fromX == 3
9975 && board[fromY][fromX] == BlackKing
9976 && toY == 7 && toX == 5) {
9977 board[fromY][fromX] = EmptySquare;
9978 board[toY][toX] = BlackKing;
9979 board[fromY][7] = EmptySquare;
9980 board[toY][4] = BlackRook;
9981 } else if (fromY == 7 && fromX == 3
9982 && board[fromY][fromX] == BlackKing
9983 && toY == 7 && toX == 1) {
9984 board[fromY][fromX] = EmptySquare;
9985 board[toY][toX] = BlackKing;
9986 board[fromY][0] = EmptySquare;
9987 board[toY][2] = BlackRook;
9988 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9989 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9990 && toY < promoRank && promoChar
9992 /* black pawn promotion */
9993 board[toY][toX] = CharToPiece(ToLower(promoChar));
9994 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9995 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9996 board[fromY][fromX] = EmptySquare;
9997 } else if ((fromY < BOARD_HEIGHT>>1)
9998 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
10000 && gameInfo.variant != VariantXiangqi
10001 && gameInfo.variant != VariantBerolina
10002 && (board[fromY][fromX] == BlackPawn)
10003 && (board[toY][toX] == EmptySquare)) {
10004 board[fromY][fromX] = EmptySquare;
10005 board[toY][toX] = BlackPawn;
10006 captured = board[toY + 1][toX];
10007 board[toY + 1][toX] = EmptySquare;
10008 } else if ((fromY == 3)
10010 && gameInfo.variant == VariantBerolina
10011 && (board[fromY][fromX] == BlackPawn)
10012 && (board[toY][toX] == EmptySquare)) {
10013 board[fromY][fromX] = EmptySquare;
10014 board[toY][toX] = BlackPawn;
10015 if(oldEP & EP_BEROLIN_A) {
10016 captured = board[fromY][fromX-1];
10017 board[fromY][fromX-1] = EmptySquare;
10018 }else{ captured = board[fromY][fromX+1];
10019 board[fromY][fromX+1] = EmptySquare;
10022 ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10023 board[fromY][fromX] = EmptySquare;
10024 board[toY][toX] = piece;
10028 if (gameInfo.holdingsWidth != 0) {
10030 /* !!A lot more code needs to be written to support holdings */
10031 /* [HGM] OK, so I have written it. Holdings are stored in the */
10032 /* penultimate board files, so they are automaticlly stored */
10033 /* in the game history. */
10034 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10035 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10036 /* Delete from holdings, by decreasing count */
10037 /* and erasing image if necessary */
10038 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10039 if(p < (int) BlackPawn) { /* white drop */
10040 p -= (int)WhitePawn;
10041 p = PieceToNumber((ChessSquare)p);
10042 if(p >= gameInfo.holdingsSize) p = 0;
10043 if(--board[p][BOARD_WIDTH-2] <= 0)
10044 board[p][BOARD_WIDTH-1] = EmptySquare;
10045 if((int)board[p][BOARD_WIDTH-2] < 0)
10046 board[p][BOARD_WIDTH-2] = 0;
10047 } else { /* black drop */
10048 p -= (int)BlackPawn;
10049 p = PieceToNumber((ChessSquare)p);
10050 if(p >= gameInfo.holdingsSize) p = 0;
10051 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10052 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10053 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10054 board[BOARD_HEIGHT-1-p][1] = 0;
10057 if (captured != EmptySquare && gameInfo.holdingsSize > 0
10058 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
10059 /* [HGM] holdings: Add to holdings, if holdings exist */
10060 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10061 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10062 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10064 p = (int) captured;
10065 if (p >= (int) BlackPawn) {
10066 p -= (int)BlackPawn;
10067 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10068 /* in Shogi restore piece to its original first */
10069 captured = (ChessSquare) (DEMOTED captured);
10072 p = PieceToNumber((ChessSquare)p);
10073 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10074 board[p][BOARD_WIDTH-2]++;
10075 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10077 p -= (int)WhitePawn;
10078 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10079 captured = (ChessSquare) (DEMOTED captured);
10082 p = PieceToNumber((ChessSquare)p);
10083 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10084 board[BOARD_HEIGHT-1-p][1]++;
10085 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10088 } else if (gameInfo.variant == VariantAtomic) {
10089 if (captured != EmptySquare) {
10091 for (y = toY-1; y <= toY+1; y++) {
10092 for (x = toX-1; x <= toX+1; x++) {
10093 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10094 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10095 board[y][x] = EmptySquare;
10099 board[toY][toX] = EmptySquare;
10103 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10104 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10106 if(promoChar == '+') {
10107 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10108 board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10109 if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10110 board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10111 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10112 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10113 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10114 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10115 board[toY][toX] = newPiece;
10117 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10118 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10119 // [HGM] superchess: take promotion piece out of holdings
10120 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10121 if((int)piece < (int)BlackPawn) { // determine stm from piece color
10122 if(!--board[k][BOARD_WIDTH-2])
10123 board[k][BOARD_WIDTH-1] = EmptySquare;
10125 if(!--board[BOARD_HEIGHT-1-k][1])
10126 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10131 /* Updates forwardMostMove */
10133 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10135 int x = toX, y = toY;
10136 char *s = parseList[forwardMostMove];
10137 ChessSquare p = boards[forwardMostMove][toY][toX];
10138 // forwardMostMove++; // [HGM] bare: moved downstream
10140 if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10141 (void) CoordsToAlgebraic(boards[forwardMostMove],
10142 PosFlags(forwardMostMove),
10143 fromY, fromX, y, x, promoChar,
10145 if(killX >= 0 && killY >= 0)
10146 sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10148 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10149 int timeLeft; static int lastLoadFlag=0; int king, piece;
10150 piece = boards[forwardMostMove][fromY][fromX];
10151 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10152 if(gameInfo.variant == VariantKnightmate)
10153 king += (int) WhiteUnicorn - (int) WhiteKing;
10154 if(forwardMostMove == 0) {
10155 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10156 fprintf(serverMoves, "%s;", UserName());
10157 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10158 fprintf(serverMoves, "%s;", second.tidy);
10159 fprintf(serverMoves, "%s;", first.tidy);
10160 if(gameMode == MachinePlaysWhite)
10161 fprintf(serverMoves, "%s;", UserName());
10162 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10163 fprintf(serverMoves, "%s;", second.tidy);
10164 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10165 lastLoadFlag = loadFlag;
10167 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10168 // print castling suffix
10169 if( toY == fromY && piece == king ) {
10171 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10173 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10176 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10177 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
10178 boards[forwardMostMove][toY][toX] == EmptySquare
10179 && fromX != toX && fromY != toY)
10180 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10181 // promotion suffix
10182 if(promoChar != NULLCHAR) {
10183 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10184 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10185 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10186 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10189 char buf[MOVE_LEN*2], *p; int len;
10190 fprintf(serverMoves, "/%d/%d",
10191 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10192 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10193 else timeLeft = blackTimeRemaining/1000;
10194 fprintf(serverMoves, "/%d", timeLeft);
10195 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10196 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10197 if(p = strchr(buf, '=')) *p = NULLCHAR;
10198 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10199 fprintf(serverMoves, "/%s", buf);
10201 fflush(serverMoves);
10204 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10205 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10208 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10209 if (commentList[forwardMostMove+1] != NULL) {
10210 free(commentList[forwardMostMove+1]);
10211 commentList[forwardMostMove+1] = NULL;
10213 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10214 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10215 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10216 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10217 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10218 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10219 adjustedClock = FALSE;
10220 gameInfo.result = GameUnfinished;
10221 if (gameInfo.resultDetails != NULL) {
10222 free(gameInfo.resultDetails);
10223 gameInfo.resultDetails = NULL;
10225 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10226 moveList[forwardMostMove - 1]);
10227 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10233 if(!IS_SHOGI(gameInfo.variant))
10234 strcat(parseList[forwardMostMove - 1], "+");
10238 strcat(parseList[forwardMostMove - 1], "#");
10243 /* Updates currentMove if not pausing */
10245 ShowMove (int fromX, int fromY, int toX, int toY)
10247 int instant = (gameMode == PlayFromGameFile) ?
10248 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10249 if(appData.noGUI) return;
10250 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10252 if (forwardMostMove == currentMove + 1) {
10253 AnimateMove(boards[forwardMostMove - 1],
10254 fromX, fromY, toX, toY);
10257 currentMove = forwardMostMove;
10260 killX = killY = -1; // [HGM] lion: used up
10262 if (instant) return;
10264 DisplayMove(currentMove - 1);
10265 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10266 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10267 SetHighlights(fromX, fromY, toX, toY);
10270 DrawPosition(FALSE, boards[currentMove]);
10271 DisplayBothClocks();
10272 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10276 SendEgtPath (ChessProgramState *cps)
10277 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10278 char buf[MSG_SIZ], name[MSG_SIZ], *p;
10280 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10283 char c, *q = name+1, *r, *s;
10285 name[0] = ','; // extract next format name from feature and copy with prefixed ','
10286 while(*p && *p != ',') *q++ = *p++;
10287 *q++ = ':'; *q = 0;
10288 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10289 strcmp(name, ",nalimov:") == 0 ) {
10290 // take nalimov path from the menu-changeable option first, if it is defined
10291 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10292 SendToProgram(buf,cps); // send egtbpath command for nalimov
10294 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10295 (s = StrStr(appData.egtFormats, name)) != NULL) {
10296 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10297 s = r = StrStr(s, ":") + 1; // beginning of path info
10298 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10299 c = *r; *r = 0; // temporarily null-terminate path info
10300 *--q = 0; // strip of trailig ':' from name
10301 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10303 SendToProgram(buf,cps); // send egtbpath command for this format
10305 if(*p == ',') p++; // read away comma to position for next format name
10310 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10312 int width = 8, height = 8, holdings = 0; // most common sizes
10313 if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10314 // correct the deviations default for each variant
10315 if( v == VariantXiangqi ) width = 9, height = 10;
10316 if( v == VariantShogi ) width = 9, height = 9, holdings = 7;
10317 if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10318 if( v == VariantCapablanca || v == VariantCapaRandom ||
10319 v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10321 if( v == VariantCourier ) width = 12;
10322 if( v == VariantSuper ) holdings = 8;
10323 if( v == VariantGreat ) width = 10, holdings = 8;
10324 if( v == VariantSChess ) holdings = 7;
10325 if( v == VariantGrand ) width = 10, height = 10, holdings = 7;
10326 if( v == VariantChuChess) width = 10, height = 10;
10327 if( v == VariantChu ) width = 12, height = 12;
10328 return boardWidth >= 0 && boardWidth != width || // -1 is default,
10329 boardHeight >= 0 && boardHeight != height || // and thus by definition OK
10330 holdingsSize >= 0 && holdingsSize != holdings;
10333 char variantError[MSG_SIZ];
10336 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10337 { // returns error message (recognizable by upper-case) if engine does not support the variant
10338 char *p, *variant = VariantName(v);
10339 static char b[MSG_SIZ];
10340 if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10341 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10342 holdingsSize, variant); // cook up sized variant name
10343 /* [HGM] varsize: try first if this deviant size variant is specifically known */
10344 if(StrStr(list, b) == NULL) {
10345 // specific sized variant not known, check if general sizing allowed
10346 if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10347 snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10348 boardWidth, boardHeight, holdingsSize, engine);
10351 /* [HGM] here we really should compare with the maximum supported board size */
10353 } else snprintf(b, MSG_SIZ,"%s", variant);
10354 if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10355 p = StrStr(list, b);
10356 while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10358 // occurs not at all in list, or only as sub-string
10359 snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10360 if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10361 int l = strlen(variantError);
10363 while(p != list && p[-1] != ',') p--;
10364 q = strchr(p, ',');
10365 if(q) *q = NULLCHAR;
10366 snprintf(variantError + l, MSG_SIZ - l, _(", but %s is"), p);
10375 InitChessProgram (ChessProgramState *cps, int setup)
10376 /* setup needed to setup FRC opening position */
10378 char buf[MSG_SIZ], *b;
10379 if (appData.noChessProgram) return;
10380 hintRequested = FALSE;
10381 bookRequested = FALSE;
10383 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10384 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10385 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10386 if(cps->memSize) { /* [HGM] memory */
10387 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10388 SendToProgram(buf, cps);
10390 SendEgtPath(cps); /* [HGM] EGT */
10391 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10392 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10393 SendToProgram(buf, cps);
10396 SendToProgram(cps->initString, cps);
10397 if (gameInfo.variant != VariantNormal &&
10398 gameInfo.variant != VariantLoadable
10399 /* [HGM] also send variant if board size non-standard */
10400 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10402 b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10403 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10405 DisplayFatalError(variantError, 0, 1);
10409 snprintf(buf, MSG_SIZ, "variant %s\n", b);
10410 SendToProgram(buf, cps);
10412 currentlyInitializedVariant = gameInfo.variant;
10414 /* [HGM] send opening position in FRC to first engine */
10416 SendToProgram("force\n", cps);
10418 /* engine is now in force mode! Set flag to wake it up after first move. */
10419 setboardSpoiledMachineBlack = 1;
10422 if (cps->sendICS) {
10423 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10424 SendToProgram(buf, cps);
10426 cps->maybeThinking = FALSE;
10427 cps->offeredDraw = 0;
10428 if (!appData.icsActive) {
10429 SendTimeControl(cps, movesPerSession, timeControl,
10430 timeIncrement, appData.searchDepth,
10433 if (appData.showThinking
10434 // [HGM] thinking: four options require thinking output to be sent
10435 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10437 SendToProgram("post\n", cps);
10439 SendToProgram("hard\n", cps);
10440 if (!appData.ponderNextMove) {
10441 /* Warning: "easy" is a toggle in GNU Chess, so don't send
10442 it without being sure what state we are in first. "hard"
10443 is not a toggle, so that one is OK.
10445 SendToProgram("easy\n", cps);
10447 if (cps->usePing) {
10448 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10449 SendToProgram(buf, cps);
10451 cps->initDone = TRUE;
10452 ClearEngineOutputPane(cps == &second);
10457 ResendOptions (ChessProgramState *cps)
10458 { // send the stored value of the options
10461 Option *opt = cps->option;
10462 for(i=0; i<cps->nrOptions; i++, opt++) {
10463 switch(opt->type) {
10467 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10470 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10473 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10479 SendToProgram(buf, cps);
10484 StartChessProgram (ChessProgramState *cps)
10489 if (appData.noChessProgram) return;
10490 cps->initDone = FALSE;
10492 if (strcmp(cps->host, "localhost") == 0) {
10493 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10494 } else if (*appData.remoteShell == NULLCHAR) {
10495 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10497 if (*appData.remoteUser == NULLCHAR) {
10498 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10501 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10502 cps->host, appData.remoteUser, cps->program);
10504 err = StartChildProcess(buf, "", &cps->pr);
10508 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10509 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10510 if(cps != &first) return;
10511 appData.noChessProgram = TRUE;
10514 // DisplayFatalError(buf, err, 1);
10515 // cps->pr = NoProc;
10516 // cps->isr = NULL;
10520 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10521 if (cps->protocolVersion > 1) {
10522 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10523 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10524 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10525 cps->comboCnt = 0; // and values of combo boxes
10527 SendToProgram(buf, cps);
10528 if(cps->reload) ResendOptions(cps);
10530 SendToProgram("xboard\n", cps);
10535 TwoMachinesEventIfReady P((void))
10537 static int curMess = 0;
10538 if (first.lastPing != first.lastPong) {
10539 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10540 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10543 if (second.lastPing != second.lastPong) {
10544 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10545 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10548 DisplayMessage("", ""); curMess = 0;
10549 TwoMachinesEvent();
10553 MakeName (char *template)
10557 static char buf[MSG_SIZ];
10561 clock = time((time_t *)NULL);
10562 tm = localtime(&clock);
10564 while(*p++ = *template++) if(p[-1] == '%') {
10565 switch(*template++) {
10566 case 0: *p = 0; return buf;
10567 case 'Y': i = tm->tm_year+1900; break;
10568 case 'y': i = tm->tm_year-100; break;
10569 case 'M': i = tm->tm_mon+1; break;
10570 case 'd': i = tm->tm_mday; break;
10571 case 'h': i = tm->tm_hour; break;
10572 case 'm': i = tm->tm_min; break;
10573 case 's': i = tm->tm_sec; break;
10576 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10582 CountPlayers (char *p)
10585 while(p = strchr(p, '\n')) p++, n++; // count participants
10590 WriteTourneyFile (char *results, FILE *f)
10591 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10592 if(f == NULL) f = fopen(appData.tourneyFile, "w");
10593 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10594 // create a file with tournament description
10595 fprintf(f, "-participants {%s}\n", appData.participants);
10596 fprintf(f, "-seedBase %d\n", appData.seedBase);
10597 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10598 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10599 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10600 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10601 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10602 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10603 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10604 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10605 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10606 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10607 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10608 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10609 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10610 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10611 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10612 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10613 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10614 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10615 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10616 fprintf(f, "-smpCores %d\n", appData.smpCores);
10618 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10620 fprintf(f, "-mps %d\n", appData.movesPerSession);
10621 fprintf(f, "-tc %s\n", appData.timeControl);
10622 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10624 fprintf(f, "-results \"%s\"\n", results);
10629 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10632 Substitute (char *participants, int expunge)
10634 int i, changed, changes=0, nPlayers=0;
10635 char *p, *q, *r, buf[MSG_SIZ];
10636 if(participants == NULL) return;
10637 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10638 r = p = participants; q = appData.participants;
10639 while(*p && *p == *q) {
10640 if(*p == '\n') r = p+1, nPlayers++;
10643 if(*p) { // difference
10644 while(*p && *p++ != '\n');
10645 while(*q && *q++ != '\n');
10646 changed = nPlayers;
10647 changes = 1 + (strcmp(p, q) != 0);
10649 if(changes == 1) { // a single engine mnemonic was changed
10650 q = r; while(*q) nPlayers += (*q++ == '\n');
10651 p = buf; while(*r && (*p = *r++) != '\n') p++;
10653 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10654 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10655 if(mnemonic[i]) { // The substitute is valid
10657 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10658 flock(fileno(f), LOCK_EX);
10659 ParseArgsFromFile(f);
10660 fseek(f, 0, SEEK_SET);
10661 FREE(appData.participants); appData.participants = participants;
10662 if(expunge) { // erase results of replaced engine
10663 int len = strlen(appData.results), w, b, dummy;
10664 for(i=0; i<len; i++) {
10665 Pairing(i, nPlayers, &w, &b, &dummy);
10666 if((w == changed || b == changed) && appData.results[i] == '*') {
10667 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10672 for(i=0; i<len; i++) {
10673 Pairing(i, nPlayers, &w, &b, &dummy);
10674 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10677 WriteTourneyFile(appData.results, f);
10678 fclose(f); // release lock
10681 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10683 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10684 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10685 free(participants);
10690 CheckPlayers (char *participants)
10693 char buf[MSG_SIZ], *p;
10694 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10695 while(p = strchr(participants, '\n')) {
10697 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10699 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10701 DisplayError(buf, 0);
10705 participants = p + 1;
10711 CreateTourney (char *name)
10714 if(matchMode && strcmp(name, appData.tourneyFile)) {
10715 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10717 if(name[0] == NULLCHAR) {
10718 if(appData.participants[0])
10719 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10722 f = fopen(name, "r");
10723 if(f) { // file exists
10724 ASSIGN(appData.tourneyFile, name);
10725 ParseArgsFromFile(f); // parse it
10727 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10728 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10729 DisplayError(_("Not enough participants"), 0);
10732 if(CheckPlayers(appData.participants)) return 0;
10733 ASSIGN(appData.tourneyFile, name);
10734 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10735 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10738 appData.noChessProgram = FALSE;
10739 appData.clockMode = TRUE;
10745 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10747 char buf[MSG_SIZ], *p, *q;
10748 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10749 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10750 skip = !all && group[0]; // if group requested, we start in skip mode
10751 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10752 p = names; q = buf; header = 0;
10753 while(*p && *p != '\n') *q++ = *p++;
10755 if(*p == '\n') p++;
10756 if(buf[0] == '#') {
10757 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10758 depth++; // we must be entering a new group
10759 if(all) continue; // suppress printing group headers when complete list requested
10761 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10763 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10764 if(engineList[i]) free(engineList[i]);
10765 engineList[i] = strdup(buf);
10766 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10767 if(engineMnemonic[i]) free(engineMnemonic[i]);
10768 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10770 sscanf(q + 8, "%s", buf + strlen(buf));
10773 engineMnemonic[i] = strdup(buf);
10776 engineList[i] = engineMnemonic[i] = NULL;
10780 // following implemented as macro to avoid type limitations
10781 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10784 SwapEngines (int n)
10785 { // swap settings for first engine and other engine (so far only some selected options)
10790 SWAP(chessProgram, p)
10792 SWAP(hasOwnBookUCI, h)
10793 SWAP(protocolVersion, h)
10795 SWAP(scoreIsAbsolute, h)
10800 SWAP(engOptions, p)
10801 SWAP(engInitString, p)
10802 SWAP(computerString, p)
10804 SWAP(fenOverride, p)
10806 SWAP(accumulateTC, h)
10812 GetEngineLine (char *s, int n)
10816 extern char *icsNames;
10817 if(!s || !*s) return 0;
10818 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10819 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10820 if(!mnemonic[i]) return 0;
10821 if(n == 11) return 1; // just testing if there was a match
10822 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10823 if(n == 1) SwapEngines(n);
10824 ParseArgsFromString(buf);
10825 if(n == 1) SwapEngines(n);
10826 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10827 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10828 ParseArgsFromString(buf);
10834 SetPlayer (int player, char *p)
10835 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10837 char buf[MSG_SIZ], *engineName;
10838 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10839 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10840 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10842 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10843 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10844 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10845 ParseArgsFromString(buf);
10846 } else { // no engine with this nickname is installed!
10847 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10848 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10849 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10851 DisplayError(buf, 0);
10858 char *recentEngines;
10861 RecentEngineEvent (int nr)
10864 // SwapEngines(1); // bump first to second
10865 // ReplaceEngine(&second, 1); // and load it there
10866 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10867 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10868 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10869 ReplaceEngine(&first, 0);
10870 FloatToFront(&appData.recentEngineList, command[n]);
10875 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10876 { // determine players from game number
10877 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10879 if(appData.tourneyType == 0) {
10880 roundsPerCycle = (nPlayers - 1) | 1;
10881 pairingsPerRound = nPlayers / 2;
10882 } else if(appData.tourneyType > 0) {
10883 roundsPerCycle = nPlayers - appData.tourneyType;
10884 pairingsPerRound = appData.tourneyType;
10886 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10887 gamesPerCycle = gamesPerRound * roundsPerCycle;
10888 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10889 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10890 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10891 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10892 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10893 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10895 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10896 if(appData.roundSync) *syncInterval = gamesPerRound;
10898 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10900 if(appData.tourneyType == 0) {
10901 if(curPairing == (nPlayers-1)/2 ) {
10902 *whitePlayer = curRound;
10903 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10905 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10906 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10907 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10908 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10910 } else if(appData.tourneyType > 1) {
10911 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10912 *whitePlayer = curRound + appData.tourneyType;
10913 } else if(appData.tourneyType > 0) {
10914 *whitePlayer = curPairing;
10915 *blackPlayer = curRound + appData.tourneyType;
10918 // take care of white/black alternation per round.
10919 // For cycles and games this is already taken care of by default, derived from matchGame!
10920 return curRound & 1;
10924 NextTourneyGame (int nr, int *swapColors)
10925 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10927 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10929 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10930 tf = fopen(appData.tourneyFile, "r");
10931 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10932 ParseArgsFromFile(tf); fclose(tf);
10933 InitTimeControls(); // TC might be altered from tourney file
10935 nPlayers = CountPlayers(appData.participants); // count participants
10936 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10937 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10940 p = q = appData.results;
10941 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10942 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10943 DisplayMessage(_("Waiting for other game(s)"),"");
10944 waitingForGame = TRUE;
10945 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10948 waitingForGame = FALSE;
10951 if(appData.tourneyType < 0) {
10952 if(nr>=0 && !pairingReceived) {
10954 if(pairing.pr == NoProc) {
10955 if(!appData.pairingEngine[0]) {
10956 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10959 StartChessProgram(&pairing); // starts the pairing engine
10961 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10962 SendToProgram(buf, &pairing);
10963 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10964 SendToProgram(buf, &pairing);
10965 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10967 pairingReceived = 0; // ... so we continue here
10969 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10970 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10971 matchGame = 1; roundNr = nr / syncInterval + 1;
10974 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10976 // redefine engines, engine dir, etc.
10977 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10978 if(first.pr == NoProc) {
10979 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10980 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10982 if(second.pr == NoProc) {
10984 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10985 SwapEngines(1); // and make that valid for second engine by swapping
10986 InitEngine(&second, 1);
10988 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10989 UpdateLogos(FALSE); // leave display to ModeHiglight()
10995 { // performs game initialization that does not invoke engines, and then tries to start the game
10996 int res, firstWhite, swapColors = 0;
10997 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10998 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
11000 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11001 if(strcmp(buf, currentDebugFile)) { // name has changed
11002 FILE *f = fopen(buf, "w");
11003 if(f) { // if opening the new file failed, just keep using the old one
11004 ASSIGN(currentDebugFile, buf);
11008 if(appData.serverFileName) {
11009 if(serverFP) fclose(serverFP);
11010 serverFP = fopen(appData.serverFileName, "w");
11011 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11012 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11016 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11017 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11018 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
11019 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11020 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11021 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11022 Reset(FALSE, first.pr != NoProc);
11023 res = LoadGameOrPosition(matchGame); // setup game
11024 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11025 if(!res) return; // abort when bad game/pos file
11026 TwoMachinesEvent();
11030 UserAdjudicationEvent (int result)
11032 ChessMove gameResult = GameIsDrawn;
11035 gameResult = WhiteWins;
11037 else if( result < 0 ) {
11038 gameResult = BlackWins;
11041 if( gameMode == TwoMachinesPlay ) {
11042 GameEnds( gameResult, "User adjudication", GE_XBOARD );
11047 // [HGM] save: calculate checksum of game to make games easily identifiable
11049 StringCheckSum (char *s)
11052 if(s==NULL) return 0;
11053 while(*s) i = i*259 + *s++;
11061 for(i=backwardMostMove; i<forwardMostMove; i++) {
11062 sum += pvInfoList[i].depth;
11063 sum += StringCheckSum(parseList[i]);
11064 sum += StringCheckSum(commentList[i]);
11067 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11068 return sum + StringCheckSum(commentList[i]);
11069 } // end of save patch
11072 GameEnds (ChessMove result, char *resultDetails, int whosays)
11074 GameMode nextGameMode;
11076 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11078 if(endingGame) return; /* [HGM] crash: forbid recursion */
11080 if(twoBoards) { // [HGM] dual: switch back to one board
11081 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11082 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11084 if (appData.debugMode) {
11085 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11086 result, resultDetails ? resultDetails : "(null)", whosays);
11089 fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11091 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11093 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11094 /* If we are playing on ICS, the server decides when the
11095 game is over, but the engine can offer to draw, claim
11099 if (appData.zippyPlay && first.initDone) {
11100 if (result == GameIsDrawn) {
11101 /* In case draw still needs to be claimed */
11102 SendToICS(ics_prefix);
11103 SendToICS("draw\n");
11104 } else if (StrCaseStr(resultDetails, "resign")) {
11105 SendToICS(ics_prefix);
11106 SendToICS("resign\n");
11110 endingGame = 0; /* [HGM] crash */
11114 /* If we're loading the game from a file, stop */
11115 if (whosays == GE_FILE) {
11116 (void) StopLoadGameTimer();
11120 /* Cancel draw offers */
11121 first.offeredDraw = second.offeredDraw = 0;
11123 /* If this is an ICS game, only ICS can really say it's done;
11124 if not, anyone can. */
11125 isIcsGame = (gameMode == IcsPlayingWhite ||
11126 gameMode == IcsPlayingBlack ||
11127 gameMode == IcsObserving ||
11128 gameMode == IcsExamining);
11130 if (!isIcsGame || whosays == GE_ICS) {
11131 /* OK -- not an ICS game, or ICS said it was done */
11133 if (!isIcsGame && !appData.noChessProgram)
11134 SetUserThinkingEnables();
11136 /* [HGM] if a machine claims the game end we verify this claim */
11137 if(gameMode == TwoMachinesPlay && appData.testClaims) {
11138 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11140 ChessMove trueResult = (ChessMove) -1;
11142 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
11143 first.twoMachinesColor[0] :
11144 second.twoMachinesColor[0] ;
11146 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11147 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11148 /* [HGM] verify: engine mate claims accepted if they were flagged */
11149 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11151 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11152 /* [HGM] verify: engine mate claims accepted if they were flagged */
11153 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11155 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11156 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11159 // now verify win claims, but not in drop games, as we don't understand those yet
11160 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11161 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11162 (result == WhiteWins && claimer == 'w' ||
11163 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
11164 if (appData.debugMode) {
11165 fprintf(debugFP, "result=%d sp=%d move=%d\n",
11166 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11168 if(result != trueResult) {
11169 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11170 result = claimer == 'w' ? BlackWins : WhiteWins;
11171 resultDetails = buf;
11174 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11175 && (forwardMostMove <= backwardMostMove ||
11176 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11177 (claimer=='b')==(forwardMostMove&1))
11179 /* [HGM] verify: draws that were not flagged are false claims */
11180 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11181 result = claimer == 'w' ? BlackWins : WhiteWins;
11182 resultDetails = buf;
11184 /* (Claiming a loss is accepted no questions asked!) */
11185 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11186 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11187 result = GameUnfinished;
11188 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11190 /* [HGM] bare: don't allow bare King to win */
11191 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11192 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11193 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11194 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11195 && result != GameIsDrawn)
11196 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11197 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11198 int p = (signed char)boards[forwardMostMove][i][j] - color;
11199 if(p >= 0 && p <= (int)WhiteKing) k++;
11201 if (appData.debugMode) {
11202 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11203 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11206 result = GameIsDrawn;
11207 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11208 resultDetails = buf;
11214 if(serverMoves != NULL && !loadFlag) { char c = '=';
11215 if(result==WhiteWins) c = '+';
11216 if(result==BlackWins) c = '-';
11217 if(resultDetails != NULL)
11218 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11220 if (resultDetails != NULL) {
11221 gameInfo.result = result;
11222 gameInfo.resultDetails = StrSave(resultDetails);
11224 /* display last move only if game was not loaded from file */
11225 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11226 DisplayMove(currentMove - 1);
11228 if (forwardMostMove != 0) {
11229 if (gameMode != PlayFromGameFile && gameMode != EditGame
11230 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11232 if (*appData.saveGameFile != NULLCHAR) {
11233 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11234 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11236 SaveGameToFile(appData.saveGameFile, TRUE);
11237 } else if (appData.autoSaveGames) {
11238 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11240 if (*appData.savePositionFile != NULLCHAR) {
11241 SavePositionToFile(appData.savePositionFile);
11243 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11247 /* Tell program how game ended in case it is learning */
11248 /* [HGM] Moved this to after saving the PGN, just in case */
11249 /* engine died and we got here through time loss. In that */
11250 /* case we will get a fatal error writing the pipe, which */
11251 /* would otherwise lose us the PGN. */
11252 /* [HGM] crash: not needed anymore, but doesn't hurt; */
11253 /* output during GameEnds should never be fatal anymore */
11254 if (gameMode == MachinePlaysWhite ||
11255 gameMode == MachinePlaysBlack ||
11256 gameMode == TwoMachinesPlay ||
11257 gameMode == IcsPlayingWhite ||
11258 gameMode == IcsPlayingBlack ||
11259 gameMode == BeginningOfGame) {
11261 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11263 if (first.pr != NoProc) {
11264 SendToProgram(buf, &first);
11266 if (second.pr != NoProc &&
11267 gameMode == TwoMachinesPlay) {
11268 SendToProgram(buf, &second);
11273 if (appData.icsActive) {
11274 if (appData.quietPlay &&
11275 (gameMode == IcsPlayingWhite ||
11276 gameMode == IcsPlayingBlack)) {
11277 SendToICS(ics_prefix);
11278 SendToICS("set shout 1\n");
11280 nextGameMode = IcsIdle;
11281 ics_user_moved = FALSE;
11282 /* clean up premove. It's ugly when the game has ended and the
11283 * premove highlights are still on the board.
11286 gotPremove = FALSE;
11287 ClearPremoveHighlights();
11288 DrawPosition(FALSE, boards[currentMove]);
11290 if (whosays == GE_ICS) {
11293 if (gameMode == IcsPlayingWhite)
11295 else if(gameMode == IcsPlayingBlack)
11296 PlayIcsLossSound();
11299 if (gameMode == IcsPlayingBlack)
11301 else if(gameMode == IcsPlayingWhite)
11302 PlayIcsLossSound();
11305 PlayIcsDrawSound();
11308 PlayIcsUnfinishedSound();
11311 if(appData.quitNext) { ExitEvent(0); return; }
11312 } else if (gameMode == EditGame ||
11313 gameMode == PlayFromGameFile ||
11314 gameMode == AnalyzeMode ||
11315 gameMode == AnalyzeFile) {
11316 nextGameMode = gameMode;
11318 nextGameMode = EndOfGame;
11323 nextGameMode = gameMode;
11326 if (appData.noChessProgram) {
11327 gameMode = nextGameMode;
11329 endingGame = 0; /* [HGM] crash */
11334 /* Put first chess program into idle state */
11335 if (first.pr != NoProc &&
11336 (gameMode == MachinePlaysWhite ||
11337 gameMode == MachinePlaysBlack ||
11338 gameMode == TwoMachinesPlay ||
11339 gameMode == IcsPlayingWhite ||
11340 gameMode == IcsPlayingBlack ||
11341 gameMode == BeginningOfGame)) {
11342 SendToProgram("force\n", &first);
11343 if (first.usePing) {
11345 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11346 SendToProgram(buf, &first);
11349 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11350 /* Kill off first chess program */
11351 if (first.isr != NULL)
11352 RemoveInputSource(first.isr);
11355 if (first.pr != NoProc) {
11357 DoSleep( appData.delayBeforeQuit );
11358 SendToProgram("quit\n", &first);
11359 DestroyChildProcess(first.pr, 4 + first.useSigterm);
11360 first.reload = TRUE;
11364 if (second.reuse) {
11365 /* Put second chess program into idle state */
11366 if (second.pr != NoProc &&
11367 gameMode == TwoMachinesPlay) {
11368 SendToProgram("force\n", &second);
11369 if (second.usePing) {
11371 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11372 SendToProgram(buf, &second);
11375 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11376 /* Kill off second chess program */
11377 if (second.isr != NULL)
11378 RemoveInputSource(second.isr);
11381 if (second.pr != NoProc) {
11382 DoSleep( appData.delayBeforeQuit );
11383 SendToProgram("quit\n", &second);
11384 DestroyChildProcess(second.pr, 4 + second.useSigterm);
11385 second.reload = TRUE;
11387 second.pr = NoProc;
11390 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11391 char resChar = '=';
11395 if (first.twoMachinesColor[0] == 'w') {
11398 second.matchWins++;
11403 if (first.twoMachinesColor[0] == 'b') {
11406 second.matchWins++;
11409 case GameUnfinished:
11415 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11416 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11417 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11418 ReserveGame(nextGame, resChar); // sets nextGame
11419 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11420 else ranking = strdup("busy"); //suppress popup when aborted but not finished
11421 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11423 if (nextGame <= appData.matchGames && !abortMatch) {
11424 gameMode = nextGameMode;
11425 matchGame = nextGame; // this will be overruled in tourney mode!
11426 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11427 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11428 endingGame = 0; /* [HGM] crash */
11431 gameMode = nextGameMode;
11432 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11433 first.tidy, second.tidy,
11434 first.matchWins, second.matchWins,
11435 appData.matchGames - (first.matchWins + second.matchWins));
11436 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11437 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11438 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11439 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11440 first.twoMachinesColor = "black\n";
11441 second.twoMachinesColor = "white\n";
11443 first.twoMachinesColor = "white\n";
11444 second.twoMachinesColor = "black\n";
11448 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11449 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11451 gameMode = nextGameMode;
11453 endingGame = 0; /* [HGM] crash */
11454 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11455 if(matchMode == TRUE) { // match through command line: exit with or without popup
11457 ToNrEvent(forwardMostMove);
11458 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11460 } else DisplayFatalError(buf, 0, 0);
11461 } else { // match through menu; just stop, with or without popup
11462 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11465 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11466 } else DisplayNote(buf);
11468 if(ranking) free(ranking);
11472 /* Assumes program was just initialized (initString sent).
11473 Leaves program in force mode. */
11475 FeedMovesToProgram (ChessProgramState *cps, int upto)
11479 if (appData.debugMode)
11480 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11481 startedFromSetupPosition ? "position and " : "",
11482 backwardMostMove, upto, cps->which);
11483 if(currentlyInitializedVariant != gameInfo.variant) {
11485 // [HGM] variantswitch: make engine aware of new variant
11486 if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11487 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11488 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11489 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11490 SendToProgram(buf, cps);
11491 currentlyInitializedVariant = gameInfo.variant;
11493 SendToProgram("force\n", cps);
11494 if (startedFromSetupPosition) {
11495 SendBoard(cps, backwardMostMove);
11496 if (appData.debugMode) {
11497 fprintf(debugFP, "feedMoves\n");
11500 for (i = backwardMostMove; i < upto; i++) {
11501 SendMoveToProgram(i, cps);
11507 ResurrectChessProgram ()
11509 /* The chess program may have exited.
11510 If so, restart it and feed it all the moves made so far. */
11511 static int doInit = 0;
11513 if (appData.noChessProgram) return 1;
11515 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11516 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11517 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11518 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11520 if (first.pr != NoProc) return 1;
11521 StartChessProgram(&first);
11523 InitChessProgram(&first, FALSE);
11524 FeedMovesToProgram(&first, currentMove);
11526 if (!first.sendTime) {
11527 /* can't tell gnuchess what its clock should read,
11528 so we bow to its notion. */
11530 timeRemaining[0][currentMove] = whiteTimeRemaining;
11531 timeRemaining[1][currentMove] = blackTimeRemaining;
11534 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11535 appData.icsEngineAnalyze) && first.analysisSupport) {
11536 SendToProgram("analyze\n", &first);
11537 first.analyzing = TRUE;
11543 * Button procedures
11546 Reset (int redraw, int init)
11550 if (appData.debugMode) {
11551 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11552 redraw, init, gameMode);
11554 CleanupTail(); // [HGM] vari: delete any stored variations
11555 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11556 pausing = pauseExamInvalid = FALSE;
11557 startedFromSetupPosition = blackPlaysFirst = FALSE;
11559 whiteFlag = blackFlag = FALSE;
11560 userOfferedDraw = FALSE;
11561 hintRequested = bookRequested = FALSE;
11562 first.maybeThinking = FALSE;
11563 second.maybeThinking = FALSE;
11564 first.bookSuspend = FALSE; // [HGM] book
11565 second.bookSuspend = FALSE;
11566 thinkOutput[0] = NULLCHAR;
11567 lastHint[0] = NULLCHAR;
11568 ClearGameInfo(&gameInfo);
11569 gameInfo.variant = StringToVariant(appData.variant);
11570 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11571 ics_user_moved = ics_clock_paused = FALSE;
11572 ics_getting_history = H_FALSE;
11574 white_holding[0] = black_holding[0] = NULLCHAR;
11575 ClearProgramStats();
11576 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11580 flipView = appData.flipView;
11581 ClearPremoveHighlights();
11582 gotPremove = FALSE;
11583 alarmSounded = FALSE;
11584 killX = killY = -1; // [HGM] lion
11586 GameEnds(EndOfFile, NULL, GE_PLAYER);
11587 if(appData.serverMovesName != NULL) {
11588 /* [HGM] prepare to make moves file for broadcasting */
11589 clock_t t = clock();
11590 if(serverMoves != NULL) fclose(serverMoves);
11591 serverMoves = fopen(appData.serverMovesName, "r");
11592 if(serverMoves != NULL) {
11593 fclose(serverMoves);
11594 /* delay 15 sec before overwriting, so all clients can see end */
11595 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11597 serverMoves = fopen(appData.serverMovesName, "w");
11601 gameMode = BeginningOfGame;
11603 if(appData.icsActive) gameInfo.variant = VariantNormal;
11604 currentMove = forwardMostMove = backwardMostMove = 0;
11605 MarkTargetSquares(1);
11606 InitPosition(redraw);
11607 for (i = 0; i < MAX_MOVES; i++) {
11608 if (commentList[i] != NULL) {
11609 free(commentList[i]);
11610 commentList[i] = NULL;
11614 timeRemaining[0][0] = whiteTimeRemaining;
11615 timeRemaining[1][0] = blackTimeRemaining;
11617 if (first.pr == NoProc) {
11618 StartChessProgram(&first);
11621 InitChessProgram(&first, startedFromSetupPosition);
11624 DisplayMessage("", "");
11625 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11626 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11627 ClearMap(); // [HGM] exclude: invalidate map
11631 AutoPlayGameLoop ()
11634 if (!AutoPlayOneMove())
11636 if (matchMode || appData.timeDelay == 0)
11638 if (appData.timeDelay < 0)
11640 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11648 ReloadGame(1); // next game
11654 int fromX, fromY, toX, toY;
11656 if (appData.debugMode) {
11657 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11660 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11663 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11664 pvInfoList[currentMove].depth = programStats.depth;
11665 pvInfoList[currentMove].score = programStats.score;
11666 pvInfoList[currentMove].time = 0;
11667 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11668 else { // append analysis of final position as comment
11670 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11671 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11673 programStats.depth = 0;
11676 if (currentMove >= forwardMostMove) {
11677 if(gameMode == AnalyzeFile) {
11678 if(appData.loadGameIndex == -1) {
11679 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11680 ScheduleDelayedEvent(AnalyzeNextGame, 10);
11682 ExitAnalyzeMode(); SendToProgram("force\n", &first);
11685 // gameMode = EndOfGame;
11686 // ModeHighlight();
11688 /* [AS] Clear current move marker at the end of a game */
11689 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11694 toX = moveList[currentMove][2] - AAA;
11695 toY = moveList[currentMove][3] - ONE;
11697 if (moveList[currentMove][1] == '@') {
11698 if (appData.highlightLastMove) {
11699 SetHighlights(-1, -1, toX, toY);
11702 fromX = moveList[currentMove][0] - AAA;
11703 fromY = moveList[currentMove][1] - ONE;
11705 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11707 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11709 if (appData.highlightLastMove) {
11710 SetHighlights(fromX, fromY, toX, toY);
11713 DisplayMove(currentMove);
11714 SendMoveToProgram(currentMove++, &first);
11715 DisplayBothClocks();
11716 DrawPosition(FALSE, boards[currentMove]);
11717 // [HGM] PV info: always display, routine tests if empty
11718 DisplayComment(currentMove - 1, commentList[currentMove]);
11724 LoadGameOneMove (ChessMove readAhead)
11726 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11727 char promoChar = NULLCHAR;
11728 ChessMove moveType;
11729 char move[MSG_SIZ];
11732 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11733 gameMode != AnalyzeMode && gameMode != Training) {
11738 yyboardindex = forwardMostMove;
11739 if (readAhead != EndOfFile) {
11740 moveType = readAhead;
11742 if (gameFileFP == NULL)
11744 moveType = (ChessMove) Myylex();
11748 switch (moveType) {
11750 if (appData.debugMode)
11751 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11754 /* append the comment but don't display it */
11755 AppendComment(currentMove, p, FALSE);
11758 case WhiteCapturesEnPassant:
11759 case BlackCapturesEnPassant:
11760 case WhitePromotion:
11761 case BlackPromotion:
11762 case WhiteNonPromotion:
11763 case BlackNonPromotion:
11766 case WhiteKingSideCastle:
11767 case WhiteQueenSideCastle:
11768 case BlackKingSideCastle:
11769 case BlackQueenSideCastle:
11770 case WhiteKingSideCastleWild:
11771 case WhiteQueenSideCastleWild:
11772 case BlackKingSideCastleWild:
11773 case BlackQueenSideCastleWild:
11775 case WhiteHSideCastleFR:
11776 case WhiteASideCastleFR:
11777 case BlackHSideCastleFR:
11778 case BlackASideCastleFR:
11780 if (appData.debugMode)
11781 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11782 fromX = currentMoveString[0] - AAA;
11783 fromY = currentMoveString[1] - ONE;
11784 toX = currentMoveString[2] - AAA;
11785 toY = currentMoveString[3] - ONE;
11786 promoChar = currentMoveString[4];
11787 if(promoChar == ';') promoChar = NULLCHAR;
11792 if (appData.debugMode)
11793 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11794 fromX = moveType == WhiteDrop ?
11795 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11796 (int) CharToPiece(ToLower(currentMoveString[0]));
11798 toX = currentMoveString[2] - AAA;
11799 toY = currentMoveString[3] - ONE;
11805 case GameUnfinished:
11806 if (appData.debugMode)
11807 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11808 p = strchr(yy_text, '{');
11809 if (p == NULL) p = strchr(yy_text, '(');
11812 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11814 q = strchr(p, *p == '{' ? '}' : ')');
11815 if (q != NULL) *q = NULLCHAR;
11818 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11819 GameEnds(moveType, p, GE_FILE);
11821 if (cmailMsgLoaded) {
11823 flipView = WhiteOnMove(currentMove);
11824 if (moveType == GameUnfinished) flipView = !flipView;
11825 if (appData.debugMode)
11826 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11831 if (appData.debugMode)
11832 fprintf(debugFP, "Parser hit end of file\n");
11833 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11839 if (WhiteOnMove(currentMove)) {
11840 GameEnds(BlackWins, "Black mates", GE_FILE);
11842 GameEnds(WhiteWins, "White mates", GE_FILE);
11846 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11852 case MoveNumberOne:
11853 if (lastLoadGameStart == GNUChessGame) {
11854 /* GNUChessGames have numbers, but they aren't move numbers */
11855 if (appData.debugMode)
11856 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11857 yy_text, (int) moveType);
11858 return LoadGameOneMove(EndOfFile); /* tail recursion */
11860 /* else fall thru */
11865 /* Reached start of next game in file */
11866 if (appData.debugMode)
11867 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11868 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11874 if (WhiteOnMove(currentMove)) {
11875 GameEnds(BlackWins, "Black mates", GE_FILE);
11877 GameEnds(WhiteWins, "White mates", GE_FILE);
11881 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11887 case PositionDiagram: /* should not happen; ignore */
11888 case ElapsedTime: /* ignore */
11889 case NAG: /* ignore */
11890 if (appData.debugMode)
11891 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11892 yy_text, (int) moveType);
11893 return LoadGameOneMove(EndOfFile); /* tail recursion */
11896 if (appData.testLegality) {
11897 if (appData.debugMode)
11898 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11899 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11900 (forwardMostMove / 2) + 1,
11901 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11902 DisplayError(move, 0);
11905 if (appData.debugMode)
11906 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11907 yy_text, currentMoveString);
11908 fromX = currentMoveString[0] - AAA;
11909 fromY = currentMoveString[1] - ONE;
11910 toX = currentMoveString[2] - AAA;
11911 toY = currentMoveString[3] - ONE;
11912 promoChar = currentMoveString[4];
11916 case AmbiguousMove:
11917 if (appData.debugMode)
11918 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11919 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11920 (forwardMostMove / 2) + 1,
11921 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11922 DisplayError(move, 0);
11927 case ImpossibleMove:
11928 if (appData.debugMode)
11929 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11930 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11931 (forwardMostMove / 2) + 1,
11932 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11933 DisplayError(move, 0);
11939 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11940 DrawPosition(FALSE, boards[currentMove]);
11941 DisplayBothClocks();
11942 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11943 DisplayComment(currentMove - 1, commentList[currentMove]);
11945 (void) StopLoadGameTimer();
11947 cmailOldMove = forwardMostMove;
11950 /* currentMoveString is set as a side-effect of yylex */
11952 thinkOutput[0] = NULLCHAR;
11953 MakeMove(fromX, fromY, toX, toY, promoChar);
11954 killX = killY = -1; // [HGM] lion: used up
11955 currentMove = forwardMostMove;
11960 /* Load the nth game from the given file */
11962 LoadGameFromFile (char *filename, int n, char *title, int useList)
11967 if (strcmp(filename, "-") == 0) {
11971 f = fopen(filename, "rb");
11973 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11974 DisplayError(buf, errno);
11978 if (fseek(f, 0, 0) == -1) {
11979 /* f is not seekable; probably a pipe */
11982 if (useList && n == 0) {
11983 int error = GameListBuild(f);
11985 DisplayError(_("Cannot build game list"), error);
11986 } else if (!ListEmpty(&gameList) &&
11987 ((ListGame *) gameList.tailPred)->number > 1) {
11988 GameListPopUp(f, title);
11995 return LoadGame(f, n, title, FALSE);
12000 MakeRegisteredMove ()
12002 int fromX, fromY, toX, toY;
12004 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12005 switch (cmailMoveType[lastLoadGameNumber - 1]) {
12008 if (appData.debugMode)
12009 fprintf(debugFP, "Restoring %s for game %d\n",
12010 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12012 thinkOutput[0] = NULLCHAR;
12013 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12014 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12015 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12016 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12017 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12018 promoChar = cmailMove[lastLoadGameNumber - 1][4];
12019 MakeMove(fromX, fromY, toX, toY, promoChar);
12020 ShowMove(fromX, fromY, toX, toY);
12022 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12029 if (WhiteOnMove(currentMove)) {
12030 GameEnds(BlackWins, "Black mates", GE_PLAYER);
12032 GameEnds(WhiteWins, "White mates", GE_PLAYER);
12037 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12044 if (WhiteOnMove(currentMove)) {
12045 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12047 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12052 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12063 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12065 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12069 if (gameNumber > nCmailGames) {
12070 DisplayError(_("No more games in this message"), 0);
12073 if (f == lastLoadGameFP) {
12074 int offset = gameNumber - lastLoadGameNumber;
12076 cmailMsg[0] = NULLCHAR;
12077 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12078 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12079 nCmailMovesRegistered--;
12081 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12082 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12083 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12086 if (! RegisterMove()) return FALSE;
12090 retVal = LoadGame(f, gameNumber, title, useList);
12092 /* Make move registered during previous look at this game, if any */
12093 MakeRegisteredMove();
12095 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12096 commentList[currentMove]
12097 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12098 DisplayComment(currentMove - 1, commentList[currentMove]);
12104 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12106 ReloadGame (int offset)
12108 int gameNumber = lastLoadGameNumber + offset;
12109 if (lastLoadGameFP == NULL) {
12110 DisplayError(_("No game has been loaded yet"), 0);
12113 if (gameNumber <= 0) {
12114 DisplayError(_("Can't back up any further"), 0);
12117 if (cmailMsgLoaded) {
12118 return CmailLoadGame(lastLoadGameFP, gameNumber,
12119 lastLoadGameTitle, lastLoadGameUseList);
12121 return LoadGame(lastLoadGameFP, gameNumber,
12122 lastLoadGameTitle, lastLoadGameUseList);
12126 int keys[EmptySquare+1];
12129 PositionMatches (Board b1, Board b2)
12132 switch(appData.searchMode) {
12133 case 1: return CompareWithRights(b1, b2);
12135 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12136 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12140 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12141 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12142 sum += keys[b1[r][f]] - keys[b2[r][f]];
12146 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12147 sum += keys[b1[r][f]] - keys[b2[r][f]];
12159 int pieceList[256], quickBoard[256];
12160 ChessSquare pieceType[256] = { EmptySquare };
12161 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12162 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12163 int soughtTotal, turn;
12164 Boolean epOK, flipSearch;
12167 unsigned char piece, to;
12170 #define DSIZE (250000)
12172 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12173 Move *moveDatabase = initialSpace;
12174 unsigned int movePtr, dataSize = DSIZE;
12177 MakePieceList (Board board, int *counts)
12179 int r, f, n=Q_PROMO, total=0;
12180 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12181 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12182 int sq = f + (r<<4);
12183 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12184 quickBoard[sq] = ++n;
12186 pieceType[n] = board[r][f];
12187 counts[board[r][f]]++;
12188 if(board[r][f] == WhiteKing) pieceList[1] = n; else
12189 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12193 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12198 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12200 int sq = fromX + (fromY<<4);
12201 int piece = quickBoard[sq], rook;
12202 quickBoard[sq] = 0;
12203 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12204 if(piece == pieceList[1] && fromY == toY) {
12205 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12206 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12207 moveDatabase[movePtr++].piece = Q_WCASTL;
12208 quickBoard[sq] = piece;
12209 piece = quickBoard[from]; quickBoard[from] = 0;
12210 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12211 } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12212 quickBoard[sq] = 0; // remove Rook
12213 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12214 moveDatabase[movePtr++].piece = Q_WCASTL;
12215 quickBoard[sq] = pieceList[1]; // put King
12217 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12220 if(piece == pieceList[2] && fromY == toY) {
12221 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12222 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12223 moveDatabase[movePtr++].piece = Q_BCASTL;
12224 quickBoard[sq] = piece;
12225 piece = quickBoard[from]; quickBoard[from] = 0;
12226 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12227 } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12228 quickBoard[sq] = 0; // remove Rook
12229 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12230 moveDatabase[movePtr++].piece = Q_BCASTL;
12231 quickBoard[sq] = pieceList[2]; // put King
12233 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12236 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12237 quickBoard[(fromY<<4)+toX] = 0;
12238 moveDatabase[movePtr].piece = Q_EP;
12239 moveDatabase[movePtr++].to = (fromY<<4)+toX;
12240 moveDatabase[movePtr].to = sq;
12242 if(promoPiece != pieceType[piece]) {
12243 moveDatabase[movePtr++].piece = Q_PROMO;
12244 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12246 moveDatabase[movePtr].piece = piece;
12247 quickBoard[sq] = piece;
12252 PackGame (Board board)
12254 Move *newSpace = NULL;
12255 moveDatabase[movePtr].piece = 0; // terminate previous game
12256 if(movePtr > dataSize) {
12257 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12258 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12259 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12262 Move *p = moveDatabase, *q = newSpace;
12263 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
12264 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12265 moveDatabase = newSpace;
12266 } else { // calloc failed, we must be out of memory. Too bad...
12267 dataSize = 0; // prevent calloc events for all subsequent games
12268 return 0; // and signal this one isn't cached
12272 MakePieceList(board, counts);
12277 QuickCompare (Board board, int *minCounts, int *maxCounts)
12278 { // compare according to search mode
12280 switch(appData.searchMode)
12282 case 1: // exact position match
12283 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12284 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12285 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12288 case 2: // can have extra material on empty squares
12289 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12290 if(board[r][f] == EmptySquare) continue;
12291 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12294 case 3: // material with exact Pawn structure
12295 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12296 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12297 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12298 } // fall through to material comparison
12299 case 4: // exact material
12300 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12302 case 6: // material range with given imbalance
12303 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12304 // fall through to range comparison
12305 case 5: // material range
12306 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12312 QuickScan (Board board, Move *move)
12313 { // reconstruct game,and compare all positions in it
12314 int cnt=0, stretch=0, total = MakePieceList(board, counts);
12316 int piece = move->piece;
12317 int to = move->to, from = pieceList[piece];
12318 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12319 if(!piece) return -1;
12320 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12321 piece = (++move)->piece;
12322 from = pieceList[piece];
12323 counts[pieceType[piece]]--;
12324 pieceType[piece] = (ChessSquare) move->to;
12325 counts[move->to]++;
12326 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12327 counts[pieceType[quickBoard[to]]]--;
12328 quickBoard[to] = 0; total--;
12331 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12332 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12333 from = pieceList[piece]; // so this must be King
12334 quickBoard[from] = 0;
12335 pieceList[piece] = to;
12336 from = pieceList[(++move)->piece]; // for FRC this has to be done here
12337 quickBoard[from] = 0; // rook
12338 quickBoard[to] = piece;
12339 to = move->to; piece = move->piece;
12343 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12344 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12345 quickBoard[from] = 0;
12347 quickBoard[to] = piece;
12348 pieceList[piece] = to;
12350 if(QuickCompare(soughtBoard, minSought, maxSought) ||
12351 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12352 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12353 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12355 static int lastCounts[EmptySquare+1];
12357 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12358 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12359 } else stretch = 0;
12360 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12369 flipSearch = FALSE;
12370 CopyBoard(soughtBoard, boards[currentMove]);
12371 soughtTotal = MakePieceList(soughtBoard, maxSought);
12372 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12373 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12374 CopyBoard(reverseBoard, boards[currentMove]);
12375 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12376 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12377 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12378 reverseBoard[r][f] = piece;
12380 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12381 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12382 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12383 || (boards[currentMove][CASTLING][2] == NoRights ||
12384 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12385 && (boards[currentMove][CASTLING][5] == NoRights ||
12386 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12389 CopyBoard(flipBoard, soughtBoard);
12390 CopyBoard(rotateBoard, reverseBoard);
12391 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12392 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
12393 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12396 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12397 if(appData.searchMode >= 5) {
12398 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12399 MakePieceList(soughtBoard, minSought);
12400 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12402 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12403 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12406 GameInfo dummyInfo;
12407 static int creatingBook;
12410 GameContainsPosition (FILE *f, ListGame *lg)
12412 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12413 int fromX, fromY, toX, toY;
12415 static int initDone=FALSE;
12417 // weed out games based on numerical tag comparison
12418 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12419 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12420 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12421 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12423 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12426 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12427 else CopyBoard(boards[scratch], initialPosition); // default start position
12430 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12431 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12434 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12435 fseek(f, lg->offset, 0);
12438 yyboardindex = scratch;
12439 quickFlag = plyNr+1;
12444 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12450 if(plyNr) return -1; // after we have seen moves, this is for new game
12453 case AmbiguousMove: // we cannot reconstruct the game beyond these two
12454 case ImpossibleMove:
12455 case WhiteWins: // game ends here with these four
12458 case GameUnfinished:
12462 if(appData.testLegality) return -1;
12463 case WhiteCapturesEnPassant:
12464 case BlackCapturesEnPassant:
12465 case WhitePromotion:
12466 case BlackPromotion:
12467 case WhiteNonPromotion:
12468 case BlackNonPromotion:
12471 case WhiteKingSideCastle:
12472 case WhiteQueenSideCastle:
12473 case BlackKingSideCastle:
12474 case BlackQueenSideCastle:
12475 case WhiteKingSideCastleWild:
12476 case WhiteQueenSideCastleWild:
12477 case BlackKingSideCastleWild:
12478 case BlackQueenSideCastleWild:
12479 case WhiteHSideCastleFR:
12480 case WhiteASideCastleFR:
12481 case BlackHSideCastleFR:
12482 case BlackASideCastleFR:
12483 fromX = currentMoveString[0] - AAA;
12484 fromY = currentMoveString[1] - ONE;
12485 toX = currentMoveString[2] - AAA;
12486 toY = currentMoveString[3] - ONE;
12487 promoChar = currentMoveString[4];
12491 fromX = next == WhiteDrop ?
12492 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12493 (int) CharToPiece(ToLower(currentMoveString[0]));
12495 toX = currentMoveString[2] - AAA;
12496 toY = currentMoveString[3] - ONE;
12500 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12502 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12503 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12504 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12505 if(appData.findMirror) {
12506 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12507 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12512 /* Load the nth game from open file f */
12514 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12518 int gn = gameNumber;
12519 ListGame *lg = NULL;
12520 int numPGNTags = 0;
12522 GameMode oldGameMode;
12523 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12525 if (appData.debugMode)
12526 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12528 if (gameMode == Training )
12529 SetTrainingModeOff();
12531 oldGameMode = gameMode;
12532 if (gameMode != BeginningOfGame) {
12533 Reset(FALSE, TRUE);
12535 killX = killY = -1; // [HGM] lion: in case we did not Reset
12538 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12539 fclose(lastLoadGameFP);
12543 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12546 fseek(f, lg->offset, 0);
12547 GameListHighlight(gameNumber);
12548 pos = lg->position;
12552 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12553 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12555 DisplayError(_("Game number out of range"), 0);
12560 if (fseek(f, 0, 0) == -1) {
12561 if (f == lastLoadGameFP ?
12562 gameNumber == lastLoadGameNumber + 1 :
12566 DisplayError(_("Can't seek on game file"), 0);
12571 lastLoadGameFP = f;
12572 lastLoadGameNumber = gameNumber;
12573 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12574 lastLoadGameUseList = useList;
12578 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12579 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12580 lg->gameInfo.black);
12582 } else if (*title != NULLCHAR) {
12583 if (gameNumber > 1) {
12584 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12587 DisplayTitle(title);
12591 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12592 gameMode = PlayFromGameFile;
12596 currentMove = forwardMostMove = backwardMostMove = 0;
12597 CopyBoard(boards[0], initialPosition);
12601 * Skip the first gn-1 games in the file.
12602 * Also skip over anything that precedes an identifiable
12603 * start of game marker, to avoid being confused by
12604 * garbage at the start of the file. Currently
12605 * recognized start of game markers are the move number "1",
12606 * the pattern "gnuchess .* game", the pattern
12607 * "^[#;%] [^ ]* game file", and a PGN tag block.
12608 * A game that starts with one of the latter two patterns
12609 * will also have a move number 1, possibly
12610 * following a position diagram.
12611 * 5-4-02: Let's try being more lenient and allowing a game to
12612 * start with an unnumbered move. Does that break anything?
12614 cm = lastLoadGameStart = EndOfFile;
12616 yyboardindex = forwardMostMove;
12617 cm = (ChessMove) Myylex();
12620 if (cmailMsgLoaded) {
12621 nCmailGames = CMAIL_MAX_GAMES - gn;
12624 DisplayError(_("Game not found in file"), 0);
12631 lastLoadGameStart = cm;
12634 case MoveNumberOne:
12635 switch (lastLoadGameStart) {
12640 case MoveNumberOne:
12642 gn--; /* count this game */
12643 lastLoadGameStart = cm;
12652 switch (lastLoadGameStart) {
12655 case MoveNumberOne:
12657 gn--; /* count this game */
12658 lastLoadGameStart = cm;
12661 lastLoadGameStart = cm; /* game counted already */
12669 yyboardindex = forwardMostMove;
12670 cm = (ChessMove) Myylex();
12671 } while (cm == PGNTag || cm == Comment);
12678 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12679 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
12680 != CMAIL_OLD_RESULT) {
12682 cmailResult[ CMAIL_MAX_GAMES
12683 - gn - 1] = CMAIL_OLD_RESULT;
12690 /* Only a NormalMove can be at the start of a game
12691 * without a position diagram. */
12692 if (lastLoadGameStart == EndOfFile ) {
12694 lastLoadGameStart = MoveNumberOne;
12703 if (appData.debugMode)
12704 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12706 if (cm == XBoardGame) {
12707 /* Skip any header junk before position diagram and/or move 1 */
12709 yyboardindex = forwardMostMove;
12710 cm = (ChessMove) Myylex();
12712 if (cm == EndOfFile ||
12713 cm == GNUChessGame || cm == XBoardGame) {
12714 /* Empty game; pretend end-of-file and handle later */
12719 if (cm == MoveNumberOne || cm == PositionDiagram ||
12720 cm == PGNTag || cm == Comment)
12723 } else if (cm == GNUChessGame) {
12724 if (gameInfo.event != NULL) {
12725 free(gameInfo.event);
12727 gameInfo.event = StrSave(yy_text);
12730 startedFromSetupPosition = FALSE;
12731 while (cm == PGNTag) {
12732 if (appData.debugMode)
12733 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12734 err = ParsePGNTag(yy_text, &gameInfo);
12735 if (!err) numPGNTags++;
12737 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12738 if(gameInfo.variant != oldVariant) {
12739 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12740 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12741 InitPosition(TRUE);
12742 oldVariant = gameInfo.variant;
12743 if (appData.debugMode)
12744 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12748 if (gameInfo.fen != NULL) {
12749 Board initial_position;
12750 startedFromSetupPosition = TRUE;
12751 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12753 DisplayError(_("Bad FEN position in file"), 0);
12756 CopyBoard(boards[0], initial_position);
12757 if (blackPlaysFirst) {
12758 currentMove = forwardMostMove = backwardMostMove = 1;
12759 CopyBoard(boards[1], initial_position);
12760 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12761 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12762 timeRemaining[0][1] = whiteTimeRemaining;
12763 timeRemaining[1][1] = blackTimeRemaining;
12764 if (commentList[0] != NULL) {
12765 commentList[1] = commentList[0];
12766 commentList[0] = NULL;
12769 currentMove = forwardMostMove = backwardMostMove = 0;
12771 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12773 initialRulePlies = FENrulePlies;
12774 for( i=0; i< nrCastlingRights; i++ )
12775 initialRights[i] = initial_position[CASTLING][i];
12777 yyboardindex = forwardMostMove;
12778 free(gameInfo.fen);
12779 gameInfo.fen = NULL;
12782 yyboardindex = forwardMostMove;
12783 cm = (ChessMove) Myylex();
12785 /* Handle comments interspersed among the tags */
12786 while (cm == Comment) {
12788 if (appData.debugMode)
12789 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12791 AppendComment(currentMove, p, FALSE);
12792 yyboardindex = forwardMostMove;
12793 cm = (ChessMove) Myylex();
12797 /* don't rely on existence of Event tag since if game was
12798 * pasted from clipboard the Event tag may not exist
12800 if (numPGNTags > 0){
12802 if (gameInfo.variant == VariantNormal) {
12803 VariantClass v = StringToVariant(gameInfo.event);
12804 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12805 if(v < VariantShogi) gameInfo.variant = v;
12808 if( appData.autoDisplayTags ) {
12809 tags = PGNTags(&gameInfo);
12810 TagsPopUp(tags, CmailMsg());
12815 /* Make something up, but don't display it now */
12820 if (cm == PositionDiagram) {
12823 Board initial_position;
12825 if (appData.debugMode)
12826 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12828 if (!startedFromSetupPosition) {
12830 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12831 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12842 initial_position[i][j++] = CharToPiece(*p);
12845 while (*p == ' ' || *p == '\t' ||
12846 *p == '\n' || *p == '\r') p++;
12848 if (strncmp(p, "black", strlen("black"))==0)
12849 blackPlaysFirst = TRUE;
12851 blackPlaysFirst = FALSE;
12852 startedFromSetupPosition = TRUE;
12854 CopyBoard(boards[0], initial_position);
12855 if (blackPlaysFirst) {
12856 currentMove = forwardMostMove = backwardMostMove = 1;
12857 CopyBoard(boards[1], initial_position);
12858 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12859 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12860 timeRemaining[0][1] = whiteTimeRemaining;
12861 timeRemaining[1][1] = blackTimeRemaining;
12862 if (commentList[0] != NULL) {
12863 commentList[1] = commentList[0];
12864 commentList[0] = NULL;
12867 currentMove = forwardMostMove = backwardMostMove = 0;
12870 yyboardindex = forwardMostMove;
12871 cm = (ChessMove) Myylex();
12874 if(!creatingBook) {
12875 if (first.pr == NoProc) {
12876 StartChessProgram(&first);
12878 InitChessProgram(&first, FALSE);
12879 SendToProgram("force\n", &first);
12880 if (startedFromSetupPosition) {
12881 SendBoard(&first, forwardMostMove);
12882 if (appData.debugMode) {
12883 fprintf(debugFP, "Load Game\n");
12885 DisplayBothClocks();
12889 /* [HGM] server: flag to write setup moves in broadcast file as one */
12890 loadFlag = appData.suppressLoadMoves;
12892 while (cm == Comment) {
12894 if (appData.debugMode)
12895 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12897 AppendComment(currentMove, p, FALSE);
12898 yyboardindex = forwardMostMove;
12899 cm = (ChessMove) Myylex();
12902 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12903 cm == WhiteWins || cm == BlackWins ||
12904 cm == GameIsDrawn || cm == GameUnfinished) {
12905 DisplayMessage("", _("No moves in game"));
12906 if (cmailMsgLoaded) {
12907 if (appData.debugMode)
12908 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12912 DrawPosition(FALSE, boards[currentMove]);
12913 DisplayBothClocks();
12914 gameMode = EditGame;
12921 // [HGM] PV info: routine tests if comment empty
12922 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12923 DisplayComment(currentMove - 1, commentList[currentMove]);
12925 if (!matchMode && appData.timeDelay != 0)
12926 DrawPosition(FALSE, boards[currentMove]);
12928 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12929 programStats.ok_to_send = 1;
12932 /* if the first token after the PGN tags is a move
12933 * and not move number 1, retrieve it from the parser
12935 if (cm != MoveNumberOne)
12936 LoadGameOneMove(cm);
12938 /* load the remaining moves from the file */
12939 while (LoadGameOneMove(EndOfFile)) {
12940 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12941 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12944 /* rewind to the start of the game */
12945 currentMove = backwardMostMove;
12947 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12949 if (oldGameMode == AnalyzeFile) {
12950 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12951 AnalyzeFileEvent();
12953 if (oldGameMode == AnalyzeMode) {
12954 AnalyzeFileEvent();
12957 if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12958 long int w, b; // [HGM] adjourn: restore saved clock times
12959 char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12960 if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12961 timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12962 timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12966 if(creatingBook) return TRUE;
12967 if (!matchMode && pos > 0) {
12968 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12970 if (matchMode || appData.timeDelay == 0) {
12972 } else if (appData.timeDelay > 0) {
12973 AutoPlayGameLoop();
12976 if (appData.debugMode)
12977 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12979 loadFlag = 0; /* [HGM] true game starts */
12983 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12985 ReloadPosition (int offset)
12987 int positionNumber = lastLoadPositionNumber + offset;
12988 if (lastLoadPositionFP == NULL) {
12989 DisplayError(_("No position has been loaded yet"), 0);
12992 if (positionNumber <= 0) {
12993 DisplayError(_("Can't back up any further"), 0);
12996 return LoadPosition(lastLoadPositionFP, positionNumber,
12997 lastLoadPositionTitle);
13000 /* Load the nth position from the given file */
13002 LoadPositionFromFile (char *filename, int n, char *title)
13007 if (strcmp(filename, "-") == 0) {
13008 return LoadPosition(stdin, n, "stdin");
13010 f = fopen(filename, "rb");
13012 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13013 DisplayError(buf, errno);
13016 return LoadPosition(f, n, title);
13021 /* Load the nth position from the given open file, and close it */
13023 LoadPosition (FILE *f, int positionNumber, char *title)
13025 char *p, line[MSG_SIZ];
13026 Board initial_position;
13027 int i, j, fenMode, pn;
13029 if (gameMode == Training )
13030 SetTrainingModeOff();
13032 if (gameMode != BeginningOfGame) {
13033 Reset(FALSE, TRUE);
13035 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13036 fclose(lastLoadPositionFP);
13038 if (positionNumber == 0) positionNumber = 1;
13039 lastLoadPositionFP = f;
13040 lastLoadPositionNumber = positionNumber;
13041 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13042 if (first.pr == NoProc && !appData.noChessProgram) {
13043 StartChessProgram(&first);
13044 InitChessProgram(&first, FALSE);
13046 pn = positionNumber;
13047 if (positionNumber < 0) {
13048 /* Negative position number means to seek to that byte offset */
13049 if (fseek(f, -positionNumber, 0) == -1) {
13050 DisplayError(_("Can't seek on position file"), 0);
13055 if (fseek(f, 0, 0) == -1) {
13056 if (f == lastLoadPositionFP ?
13057 positionNumber == lastLoadPositionNumber + 1 :
13058 positionNumber == 1) {
13061 DisplayError(_("Can't seek on position file"), 0);
13066 /* See if this file is FEN or old-style xboard */
13067 if (fgets(line, MSG_SIZ, f) == NULL) {
13068 DisplayError(_("Position not found in file"), 0);
13071 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13072 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13075 if (fenMode || line[0] == '#') pn--;
13077 /* skip positions before number pn */
13078 if (fgets(line, MSG_SIZ, f) == NULL) {
13080 DisplayError(_("Position not found in file"), 0);
13083 if (fenMode || line[0] == '#') pn--;
13088 if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13089 DisplayError(_("Bad FEN position in file"), 0);
13093 (void) fgets(line, MSG_SIZ, f);
13094 (void) fgets(line, MSG_SIZ, f);
13096 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13097 (void) fgets(line, MSG_SIZ, f);
13098 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13101 initial_position[i][j++] = CharToPiece(*p);
13105 blackPlaysFirst = FALSE;
13107 (void) fgets(line, MSG_SIZ, f);
13108 if (strncmp(line, "black", strlen("black"))==0)
13109 blackPlaysFirst = TRUE;
13112 startedFromSetupPosition = TRUE;
13114 CopyBoard(boards[0], initial_position);
13115 if (blackPlaysFirst) {
13116 currentMove = forwardMostMove = backwardMostMove = 1;
13117 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13118 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13119 CopyBoard(boards[1], initial_position);
13120 DisplayMessage("", _("Black to play"));
13122 currentMove = forwardMostMove = backwardMostMove = 0;
13123 DisplayMessage("", _("White to play"));
13125 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13126 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13127 SendToProgram("force\n", &first);
13128 SendBoard(&first, forwardMostMove);
13130 if (appData.debugMode) {
13132 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13133 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13134 fprintf(debugFP, "Load Position\n");
13137 if (positionNumber > 1) {
13138 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13139 DisplayTitle(line);
13141 DisplayTitle(title);
13143 gameMode = EditGame;
13146 timeRemaining[0][1] = whiteTimeRemaining;
13147 timeRemaining[1][1] = blackTimeRemaining;
13148 DrawPosition(FALSE, boards[currentMove]);
13155 CopyPlayerNameIntoFileName (char **dest, char *src)
13157 while (*src != NULLCHAR && *src != ',') {
13162 *(*dest)++ = *src++;
13168 DefaultFileName (char *ext)
13170 static char def[MSG_SIZ];
13173 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13175 CopyPlayerNameIntoFileName(&p, gameInfo.white);
13177 CopyPlayerNameIntoFileName(&p, gameInfo.black);
13179 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13186 /* Save the current game to the given file */
13188 SaveGameToFile (char *filename, int append)
13192 int result, i, t,tot=0;
13194 if (strcmp(filename, "-") == 0) {
13195 return SaveGame(stdout, 0, NULL);
13197 for(i=0; i<10; i++) { // upto 10 tries
13198 f = fopen(filename, append ? "a" : "w");
13199 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13200 if(f || errno != 13) break;
13201 DoSleep(t = 5 + random()%11); // wait 5-15 msec
13205 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13206 DisplayError(buf, errno);
13209 safeStrCpy(buf, lastMsg, MSG_SIZ);
13210 DisplayMessage(_("Waiting for access to save file"), "");
13211 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13212 DisplayMessage(_("Saving game"), "");
13213 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
13214 result = SaveGame(f, 0, NULL);
13215 DisplayMessage(buf, "");
13222 SavePart (char *str)
13224 static char buf[MSG_SIZ];
13227 p = strchr(str, ' ');
13228 if (p == NULL) return str;
13229 strncpy(buf, str, p - str);
13230 buf[p - str] = NULLCHAR;
13234 #define PGN_MAX_LINE 75
13236 #define PGN_SIDE_WHITE 0
13237 #define PGN_SIDE_BLACK 1
13240 FindFirstMoveOutOfBook (int side)
13244 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13245 int index = backwardMostMove;
13246 int has_book_hit = 0;
13248 if( (index % 2) != side ) {
13252 while( index < forwardMostMove ) {
13253 /* Check to see if engine is in book */
13254 int depth = pvInfoList[index].depth;
13255 int score = pvInfoList[index].score;
13261 else if( score == 0 && depth == 63 ) {
13262 in_book = 1; /* Zappa */
13264 else if( score == 2 && depth == 99 ) {
13265 in_book = 1; /* Abrok */
13268 has_book_hit += in_book;
13284 GetOutOfBookInfo (char * buf)
13288 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13290 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13291 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13295 if( oob[0] >= 0 || oob[1] >= 0 ) {
13296 for( i=0; i<2; i++ ) {
13300 if( i > 0 && oob[0] >= 0 ) {
13301 strcat( buf, " " );
13304 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13305 sprintf( buf+strlen(buf), "%s%.2f",
13306 pvInfoList[idx].score >= 0 ? "+" : "",
13307 pvInfoList[idx].score / 100.0 );
13313 /* Save game in PGN style and close the file */
13315 SaveGamePGN (FILE *f)
13317 int i, offset, linelen, newblock;
13320 int movelen, numlen, blank;
13321 char move_buffer[100]; /* [AS] Buffer for move+PV info */
13323 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13325 PrintPGNTags(f, &gameInfo);
13327 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13329 if (backwardMostMove > 0 || startedFromSetupPosition) {
13330 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13331 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13332 fprintf(f, "\n{--------------\n");
13333 PrintPosition(f, backwardMostMove);
13334 fprintf(f, "--------------}\n");
13338 /* [AS] Out of book annotation */
13339 if( appData.saveOutOfBookInfo ) {
13342 GetOutOfBookInfo( buf );
13344 if( buf[0] != '\0' ) {
13345 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13352 i = backwardMostMove;
13356 while (i < forwardMostMove) {
13357 /* Print comments preceding this move */
13358 if (commentList[i] != NULL) {
13359 if (linelen > 0) fprintf(f, "\n");
13360 fprintf(f, "%s", commentList[i]);
13365 /* Format move number */
13367 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13370 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13372 numtext[0] = NULLCHAR;
13374 numlen = strlen(numtext);
13377 /* Print move number */
13378 blank = linelen > 0 && numlen > 0;
13379 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13388 fprintf(f, "%s", numtext);
13392 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13393 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13396 blank = linelen > 0 && movelen > 0;
13397 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13406 fprintf(f, "%s", move_buffer);
13407 linelen += movelen;
13409 /* [AS] Add PV info if present */
13410 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13411 /* [HGM] add time */
13412 char buf[MSG_SIZ]; int seconds;
13414 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13420 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13423 seconds = (seconds + 4)/10; // round to full seconds
13425 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13427 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13430 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13431 pvInfoList[i].score >= 0 ? "+" : "",
13432 pvInfoList[i].score / 100.0,
13433 pvInfoList[i].depth,
13436 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13438 /* Print score/depth */
13439 blank = linelen > 0 && movelen > 0;
13440 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13449 fprintf(f, "%s", move_buffer);
13450 linelen += movelen;
13456 /* Start a new line */
13457 if (linelen > 0) fprintf(f, "\n");
13459 /* Print comments after last move */
13460 if (commentList[i] != NULL) {
13461 fprintf(f, "%s\n", commentList[i]);
13465 if (gameInfo.resultDetails != NULL &&
13466 gameInfo.resultDetails[0] != NULLCHAR) {
13467 char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13468 if(gameInfo.result == GameUnfinished && appData.clockMode &&
13469 (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13470 snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13471 fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13473 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13477 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13481 /* Save game in old style and close the file */
13483 SaveGameOldStyle (FILE *f)
13488 tm = time((time_t *) NULL);
13490 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13493 if (backwardMostMove > 0 || startedFromSetupPosition) {
13494 fprintf(f, "\n[--------------\n");
13495 PrintPosition(f, backwardMostMove);
13496 fprintf(f, "--------------]\n");
13501 i = backwardMostMove;
13502 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13504 while (i < forwardMostMove) {
13505 if (commentList[i] != NULL) {
13506 fprintf(f, "[%s]\n", commentList[i]);
13509 if ((i % 2) == 1) {
13510 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
13513 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
13515 if (commentList[i] != NULL) {
13519 if (i >= forwardMostMove) {
13523 fprintf(f, "%s\n", parseList[i]);
13528 if (commentList[i] != NULL) {
13529 fprintf(f, "[%s]\n", commentList[i]);
13532 /* This isn't really the old style, but it's close enough */
13533 if (gameInfo.resultDetails != NULL &&
13534 gameInfo.resultDetails[0] != NULLCHAR) {
13535 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13536 gameInfo.resultDetails);
13538 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13545 /* Save the current game to open file f and close the file */
13547 SaveGame (FILE *f, int dummy, char *dummy2)
13549 if (gameMode == EditPosition) EditPositionDone(TRUE);
13550 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13551 if (appData.oldSaveStyle)
13552 return SaveGameOldStyle(f);
13554 return SaveGamePGN(f);
13557 /* Save the current position to the given file */
13559 SavePositionToFile (char *filename)
13564 if (strcmp(filename, "-") == 0) {
13565 return SavePosition(stdout, 0, NULL);
13567 f = fopen(filename, "a");
13569 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13570 DisplayError(buf, errno);
13573 safeStrCpy(buf, lastMsg, MSG_SIZ);
13574 DisplayMessage(_("Waiting for access to save file"), "");
13575 flock(fileno(f), LOCK_EX); // [HGM] lock
13576 DisplayMessage(_("Saving position"), "");
13577 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
13578 SavePosition(f, 0, NULL);
13579 DisplayMessage(buf, "");
13585 /* Save the current position to the given open file and close the file */
13587 SavePosition (FILE *f, int dummy, char *dummy2)
13592 if (gameMode == EditPosition) EditPositionDone(TRUE);
13593 if (appData.oldSaveStyle) {
13594 tm = time((time_t *) NULL);
13596 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13598 fprintf(f, "[--------------\n");
13599 PrintPosition(f, currentMove);
13600 fprintf(f, "--------------]\n");
13602 fen = PositionToFEN(currentMove, NULL, 1);
13603 fprintf(f, "%s\n", fen);
13611 ReloadCmailMsgEvent (int unregister)
13614 static char *inFilename = NULL;
13615 static char *outFilename;
13617 struct stat inbuf, outbuf;
13620 /* Any registered moves are unregistered if unregister is set, */
13621 /* i.e. invoked by the signal handler */
13623 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13624 cmailMoveRegistered[i] = FALSE;
13625 if (cmailCommentList[i] != NULL) {
13626 free(cmailCommentList[i]);
13627 cmailCommentList[i] = NULL;
13630 nCmailMovesRegistered = 0;
13633 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13634 cmailResult[i] = CMAIL_NOT_RESULT;
13638 if (inFilename == NULL) {
13639 /* Because the filenames are static they only get malloced once */
13640 /* and they never get freed */
13641 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13642 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13644 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13645 sprintf(outFilename, "%s.out", appData.cmailGameName);
13648 status = stat(outFilename, &outbuf);
13650 cmailMailedMove = FALSE;
13652 status = stat(inFilename, &inbuf);
13653 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13656 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13657 counts the games, notes how each one terminated, etc.
13659 It would be nice to remove this kludge and instead gather all
13660 the information while building the game list. (And to keep it
13661 in the game list nodes instead of having a bunch of fixed-size
13662 parallel arrays.) Note this will require getting each game's
13663 termination from the PGN tags, as the game list builder does
13664 not process the game moves. --mann
13666 cmailMsgLoaded = TRUE;
13667 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13669 /* Load first game in the file or popup game menu */
13670 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13672 #endif /* !WIN32 */
13680 char string[MSG_SIZ];
13682 if ( cmailMailedMove
13683 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13684 return TRUE; /* Allow free viewing */
13687 /* Unregister move to ensure that we don't leave RegisterMove */
13688 /* with the move registered when the conditions for registering no */
13690 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13691 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13692 nCmailMovesRegistered --;
13694 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13696 free(cmailCommentList[lastLoadGameNumber - 1]);
13697 cmailCommentList[lastLoadGameNumber - 1] = NULL;
13701 if (cmailOldMove == -1) {
13702 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13706 if (currentMove > cmailOldMove + 1) {
13707 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13711 if (currentMove < cmailOldMove) {
13712 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13716 if (forwardMostMove > currentMove) {
13717 /* Silently truncate extra moves */
13721 if ( (currentMove == cmailOldMove + 1)
13722 || ( (currentMove == cmailOldMove)
13723 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13724 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13725 if (gameInfo.result != GameUnfinished) {
13726 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13729 if (commentList[currentMove] != NULL) {
13730 cmailCommentList[lastLoadGameNumber - 1]
13731 = StrSave(commentList[currentMove]);
13733 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13735 if (appData.debugMode)
13736 fprintf(debugFP, "Saving %s for game %d\n",
13737 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13739 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13741 f = fopen(string, "w");
13742 if (appData.oldSaveStyle) {
13743 SaveGameOldStyle(f); /* also closes the file */
13745 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13746 f = fopen(string, "w");
13747 SavePosition(f, 0, NULL); /* also closes the file */
13749 fprintf(f, "{--------------\n");
13750 PrintPosition(f, currentMove);
13751 fprintf(f, "--------------}\n\n");
13753 SaveGame(f, 0, NULL); /* also closes the file*/
13756 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13757 nCmailMovesRegistered ++;
13758 } else if (nCmailGames == 1) {
13759 DisplayError(_("You have not made a move yet"), 0);
13770 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13771 FILE *commandOutput;
13772 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13773 int nBytes = 0; /* Suppress warnings on uninitialized variables */
13779 if (! cmailMsgLoaded) {
13780 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13784 if (nCmailGames == nCmailResults) {
13785 DisplayError(_("No unfinished games"), 0);
13789 #if CMAIL_PROHIBIT_REMAIL
13790 if (cmailMailedMove) {
13791 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);
13792 DisplayError(msg, 0);
13797 if (! (cmailMailedMove || RegisterMove())) return;
13799 if ( cmailMailedMove
13800 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13801 snprintf(string, MSG_SIZ, partCommandString,
13802 appData.debugMode ? " -v" : "", appData.cmailGameName);
13803 commandOutput = popen(string, "r");
13805 if (commandOutput == NULL) {
13806 DisplayError(_("Failed to invoke cmail"), 0);
13808 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13809 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13811 if (nBuffers > 1) {
13812 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13813 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13814 nBytes = MSG_SIZ - 1;
13816 (void) memcpy(msg, buffer, nBytes);
13818 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13820 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13821 cmailMailedMove = TRUE; /* Prevent >1 moves */
13824 for (i = 0; i < nCmailGames; i ++) {
13825 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13830 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13832 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13834 appData.cmailGameName,
13836 LoadGameFromFile(buffer, 1, buffer, FALSE);
13837 cmailMsgLoaded = FALSE;
13841 DisplayInformation(msg);
13842 pclose(commandOutput);
13845 if ((*cmailMsg) != '\0') {
13846 DisplayInformation(cmailMsg);
13851 #endif /* !WIN32 */
13860 int prependComma = 0;
13862 char string[MSG_SIZ]; /* Space for game-list */
13865 if (!cmailMsgLoaded) return "";
13867 if (cmailMailedMove) {
13868 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13870 /* Create a list of games left */
13871 snprintf(string, MSG_SIZ, "[");
13872 for (i = 0; i < nCmailGames; i ++) {
13873 if (! ( cmailMoveRegistered[i]
13874 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13875 if (prependComma) {
13876 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13878 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13882 strcat(string, number);
13885 strcat(string, "]");
13887 if (nCmailMovesRegistered + nCmailResults == 0) {
13888 switch (nCmailGames) {
13890 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13894 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13898 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13903 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13905 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13910 if (nCmailResults == nCmailGames) {
13911 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13913 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13918 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13930 if (gameMode == Training)
13931 SetTrainingModeOff();
13934 cmailMsgLoaded = FALSE;
13935 if (appData.icsActive) {
13936 SendToICS(ics_prefix);
13937 SendToICS("refresh\n");
13942 ExitEvent (int status)
13946 /* Give up on clean exit */
13950 /* Keep trying for clean exit */
13954 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13956 if (telnetISR != NULL) {
13957 RemoveInputSource(telnetISR);
13959 if (icsPR != NoProc) {
13960 DestroyChildProcess(icsPR, TRUE);
13963 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13964 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13966 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13967 /* make sure this other one finishes before killing it! */
13968 if(endingGame) { int count = 0;
13969 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13970 while(endingGame && count++ < 10) DoSleep(1);
13971 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13974 /* Kill off chess programs */
13975 if (first.pr != NoProc) {
13978 DoSleep( appData.delayBeforeQuit );
13979 SendToProgram("quit\n", &first);
13980 DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
13982 if (second.pr != NoProc) {
13983 DoSleep( appData.delayBeforeQuit );
13984 SendToProgram("quit\n", &second);
13985 DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
13987 if (first.isr != NULL) {
13988 RemoveInputSource(first.isr);
13990 if (second.isr != NULL) {
13991 RemoveInputSource(second.isr);
13994 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13995 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13997 ShutDownFrontEnd();
14002 PauseEngine (ChessProgramState *cps)
14004 SendToProgram("pause\n", cps);
14009 UnPauseEngine (ChessProgramState *cps)
14011 SendToProgram("resume\n", cps);
14018 if (appData.debugMode)
14019 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14023 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14025 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14026 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14027 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14029 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14030 HandleMachineMove(stashedInputMove, stalledEngine);
14031 stalledEngine = NULL;
14034 if (gameMode == MachinePlaysWhite ||
14035 gameMode == TwoMachinesPlay ||
14036 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14037 if(first.pause) UnPauseEngine(&first);
14038 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14039 if(second.pause) UnPauseEngine(&second);
14040 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14043 DisplayBothClocks();
14045 if (gameMode == PlayFromGameFile) {
14046 if (appData.timeDelay >= 0)
14047 AutoPlayGameLoop();
14048 } else if (gameMode == IcsExamining && pauseExamInvalid) {
14049 Reset(FALSE, TRUE);
14050 SendToICS(ics_prefix);
14051 SendToICS("refresh\n");
14052 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14053 ForwardInner(forwardMostMove);
14055 pauseExamInvalid = FALSE;
14057 switch (gameMode) {
14061 pauseExamForwardMostMove = forwardMostMove;
14062 pauseExamInvalid = FALSE;
14065 case IcsPlayingWhite:
14066 case IcsPlayingBlack:
14070 case PlayFromGameFile:
14071 (void) StopLoadGameTimer();
14075 case BeginningOfGame:
14076 if (appData.icsActive) return;
14077 /* else fall through */
14078 case MachinePlaysWhite:
14079 case MachinePlaysBlack:
14080 case TwoMachinesPlay:
14081 if (forwardMostMove == 0)
14082 return; /* don't pause if no one has moved */
14083 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14084 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14085 if(onMove->pause) { // thinking engine can be paused
14086 PauseEngine(onMove); // do it
14087 if(onMove->other->pause) // pondering opponent can always be paused immediately
14088 PauseEngine(onMove->other);
14090 SendToProgram("easy\n", onMove->other);
14092 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14093 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14095 PauseEngine(&first);
14097 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14098 } else { // human on move, pause pondering by either method
14100 PauseEngine(&first);
14101 else if(appData.ponderNextMove)
14102 SendToProgram("easy\n", &first);
14105 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14115 EditCommentEvent ()
14117 char title[MSG_SIZ];
14119 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14120 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14122 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14123 WhiteOnMove(currentMove - 1) ? " " : ".. ",
14124 parseList[currentMove - 1]);
14127 EditCommentPopUp(currentMove, title, commentList[currentMove]);
14134 char *tags = PGNTags(&gameInfo);
14136 EditTagsPopUp(tags, NULL);
14143 if(second.analyzing) {
14144 SendToProgram("exit\n", &second);
14145 second.analyzing = FALSE;
14147 if (second.pr == NoProc) StartChessProgram(&second);
14148 InitChessProgram(&second, FALSE);
14149 FeedMovesToProgram(&second, currentMove);
14151 SendToProgram("analyze\n", &second);
14152 second.analyzing = TRUE;
14156 /* Toggle ShowThinking */
14158 ToggleShowThinking()
14160 appData.showThinking = !appData.showThinking;
14161 ShowThinkingEvent();
14165 AnalyzeModeEvent ()
14169 if (!first.analysisSupport) {
14170 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14171 DisplayError(buf, 0);
14174 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14175 if (appData.icsActive) {
14176 if (gameMode != IcsObserving) {
14177 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14178 DisplayError(buf, 0);
14180 if (appData.icsEngineAnalyze) {
14181 if (appData.debugMode)
14182 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14188 /* if enable, user wants to disable icsEngineAnalyze */
14189 if (appData.icsEngineAnalyze) {
14194 appData.icsEngineAnalyze = TRUE;
14195 if (appData.debugMode)
14196 fprintf(debugFP, "ICS engine analyze starting... \n");
14199 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14200 if (appData.noChessProgram || gameMode == AnalyzeMode)
14203 if (gameMode != AnalyzeFile) {
14204 if (!appData.icsEngineAnalyze) {
14206 if (gameMode != EditGame) return 0;
14208 if (!appData.showThinking) ToggleShowThinking();
14209 ResurrectChessProgram();
14210 SendToProgram("analyze\n", &first);
14211 first.analyzing = TRUE;
14212 /*first.maybeThinking = TRUE;*/
14213 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14214 EngineOutputPopUp();
14216 if (!appData.icsEngineAnalyze) {
14217 gameMode = AnalyzeMode;
14218 ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14224 StartAnalysisClock();
14225 GetTimeMark(&lastNodeCountTime);
14231 AnalyzeFileEvent ()
14233 if (appData.noChessProgram || gameMode == AnalyzeFile)
14236 if (!first.analysisSupport) {
14238 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14239 DisplayError(buf, 0);
14243 if (gameMode != AnalyzeMode) {
14244 keepInfo = 1; // mere annotating should not alter PGN tags
14247 if (gameMode != EditGame) return;
14248 if (!appData.showThinking) ToggleShowThinking();
14249 ResurrectChessProgram();
14250 SendToProgram("analyze\n", &first);
14251 first.analyzing = TRUE;
14252 /*first.maybeThinking = TRUE;*/
14253 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14254 EngineOutputPopUp();
14256 gameMode = AnalyzeFile;
14260 StartAnalysisClock();
14261 GetTimeMark(&lastNodeCountTime);
14263 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14264 AnalysisPeriodicEvent(1);
14268 MachineWhiteEvent ()
14271 char *bookHit = NULL;
14273 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14277 if (gameMode == PlayFromGameFile ||
14278 gameMode == TwoMachinesPlay ||
14279 gameMode == Training ||
14280 gameMode == AnalyzeMode ||
14281 gameMode == EndOfGame)
14284 if (gameMode == EditPosition)
14285 EditPositionDone(TRUE);
14287 if (!WhiteOnMove(currentMove)) {
14288 DisplayError(_("It is not White's turn"), 0);
14292 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14295 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14296 gameMode == AnalyzeFile)
14299 ResurrectChessProgram(); /* in case it isn't running */
14300 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14301 gameMode = MachinePlaysWhite;
14304 gameMode = MachinePlaysWhite;
14308 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14310 if (first.sendName) {
14311 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14312 SendToProgram(buf, &first);
14314 if (first.sendTime) {
14315 if (first.useColors) {
14316 SendToProgram("black\n", &first); /*gnu kludge*/
14318 SendTimeRemaining(&first, TRUE);
14320 if (first.useColors) {
14321 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14323 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14324 SetMachineThinkingEnables();
14325 first.maybeThinking = TRUE;
14329 if (appData.autoFlipView && !flipView) {
14330 flipView = !flipView;
14331 DrawPosition(FALSE, NULL);
14332 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14335 if(bookHit) { // [HGM] book: simulate book reply
14336 static char bookMove[MSG_SIZ]; // a bit generous?
14338 programStats.nodes = programStats.depth = programStats.time =
14339 programStats.score = programStats.got_only_move = 0;
14340 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14342 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14343 strcat(bookMove, bookHit);
14344 HandleMachineMove(bookMove, &first);
14349 MachineBlackEvent ()
14352 char *bookHit = NULL;
14354 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14358 if (gameMode == PlayFromGameFile ||
14359 gameMode == TwoMachinesPlay ||
14360 gameMode == Training ||
14361 gameMode == AnalyzeMode ||
14362 gameMode == EndOfGame)
14365 if (gameMode == EditPosition)
14366 EditPositionDone(TRUE);
14368 if (WhiteOnMove(currentMove)) {
14369 DisplayError(_("It is not Black's turn"), 0);
14373 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14376 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14377 gameMode == AnalyzeFile)
14380 ResurrectChessProgram(); /* in case it isn't running */
14381 gameMode = MachinePlaysBlack;
14385 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14387 if (first.sendName) {
14388 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14389 SendToProgram(buf, &first);
14391 if (first.sendTime) {
14392 if (first.useColors) {
14393 SendToProgram("white\n", &first); /*gnu kludge*/
14395 SendTimeRemaining(&first, FALSE);
14397 if (first.useColors) {
14398 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14400 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14401 SetMachineThinkingEnables();
14402 first.maybeThinking = TRUE;
14405 if (appData.autoFlipView && flipView) {
14406 flipView = !flipView;
14407 DrawPosition(FALSE, NULL);
14408 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14410 if(bookHit) { // [HGM] book: simulate book reply
14411 static char bookMove[MSG_SIZ]; // a bit generous?
14413 programStats.nodes = programStats.depth = programStats.time =
14414 programStats.score = programStats.got_only_move = 0;
14415 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14417 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14418 strcat(bookMove, bookHit);
14419 HandleMachineMove(bookMove, &first);
14425 DisplayTwoMachinesTitle ()
14428 if (appData.matchGames > 0) {
14429 if(appData.tourneyFile[0]) {
14430 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14431 gameInfo.white, _("vs."), gameInfo.black,
14432 nextGame+1, appData.matchGames+1,
14433 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14435 if (first.twoMachinesColor[0] == 'w') {
14436 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14437 gameInfo.white, _("vs."), gameInfo.black,
14438 first.matchWins, second.matchWins,
14439 matchGame - 1 - (first.matchWins + second.matchWins));
14441 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14442 gameInfo.white, _("vs."), gameInfo.black,
14443 second.matchWins, first.matchWins,
14444 matchGame - 1 - (first.matchWins + second.matchWins));
14447 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14453 SettingsMenuIfReady ()
14455 if (second.lastPing != second.lastPong) {
14456 DisplayMessage("", _("Waiting for second chess program"));
14457 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14461 DisplayMessage("", "");
14462 SettingsPopUp(&second);
14466 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14469 if (cps->pr == NoProc) {
14470 StartChessProgram(cps);
14471 if (cps->protocolVersion == 1) {
14473 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14475 /* kludge: allow timeout for initial "feature" command */
14476 if(retry != TwoMachinesEventIfReady) FreezeUI();
14477 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14478 DisplayMessage("", buf);
14479 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14487 TwoMachinesEvent P((void))
14491 ChessProgramState *onmove;
14492 char *bookHit = NULL;
14493 static int stalling = 0;
14497 if (appData.noChessProgram) return;
14499 switch (gameMode) {
14500 case TwoMachinesPlay:
14502 case MachinePlaysWhite:
14503 case MachinePlaysBlack:
14504 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14505 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14509 case BeginningOfGame:
14510 case PlayFromGameFile:
14513 if (gameMode != EditGame) return;
14516 EditPositionDone(TRUE);
14527 // forwardMostMove = currentMove;
14528 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14529 startingEngine = TRUE;
14531 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14533 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14534 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14535 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14538 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14540 if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14541 gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14542 startingEngine = FALSE;
14543 DisplayError("second engine does not play this", 0);
14548 InitChessProgram(&second, FALSE); // unbalances ping of second engine
14549 SendToProgram("force\n", &second);
14551 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14554 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14555 if(appData.matchPause>10000 || appData.matchPause<10)
14556 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14557 wait = SubtractTimeMarks(&now, &pauseStart);
14558 if(wait < appData.matchPause) {
14559 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14562 // we are now committed to starting the game
14564 DisplayMessage("", "");
14565 if (startedFromSetupPosition) {
14566 SendBoard(&second, backwardMostMove);
14567 if (appData.debugMode) {
14568 fprintf(debugFP, "Two Machines\n");
14571 for (i = backwardMostMove; i < forwardMostMove; i++) {
14572 SendMoveToProgram(i, &second);
14575 gameMode = TwoMachinesPlay;
14576 pausing = startingEngine = FALSE;
14577 ModeHighlight(); // [HGM] logo: this triggers display update of logos
14579 DisplayTwoMachinesTitle();
14581 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14586 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14587 SendToProgram(first.computerString, &first);
14588 if (first.sendName) {
14589 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14590 SendToProgram(buf, &first);
14592 SendToProgram(second.computerString, &second);
14593 if (second.sendName) {
14594 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14595 SendToProgram(buf, &second);
14599 if (!first.sendTime || !second.sendTime) {
14600 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14601 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14603 if (onmove->sendTime) {
14604 if (onmove->useColors) {
14605 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14607 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14609 if (onmove->useColors) {
14610 SendToProgram(onmove->twoMachinesColor, onmove);
14612 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14613 // SendToProgram("go\n", onmove);
14614 onmove->maybeThinking = TRUE;
14615 SetMachineThinkingEnables();
14619 if(bookHit) { // [HGM] book: simulate book reply
14620 static char bookMove[MSG_SIZ]; // a bit generous?
14622 programStats.nodes = programStats.depth = programStats.time =
14623 programStats.score = programStats.got_only_move = 0;
14624 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14626 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14627 strcat(bookMove, bookHit);
14628 savedMessage = bookMove; // args for deferred call
14629 savedState = onmove;
14630 ScheduleDelayedEvent(DeferredBookMove, 1);
14637 if (gameMode == Training) {
14638 SetTrainingModeOff();
14639 gameMode = PlayFromGameFile;
14640 DisplayMessage("", _("Training mode off"));
14642 gameMode = Training;
14643 animateTraining = appData.animate;
14645 /* make sure we are not already at the end of the game */
14646 if (currentMove < forwardMostMove) {
14647 SetTrainingModeOn();
14648 DisplayMessage("", _("Training mode on"));
14650 gameMode = PlayFromGameFile;
14651 DisplayError(_("Already at end of game"), 0);
14660 if (!appData.icsActive) return;
14661 switch (gameMode) {
14662 case IcsPlayingWhite:
14663 case IcsPlayingBlack:
14666 case BeginningOfGame:
14674 EditPositionDone(TRUE);
14687 gameMode = IcsIdle;
14697 switch (gameMode) {
14699 SetTrainingModeOff();
14701 case MachinePlaysWhite:
14702 case MachinePlaysBlack:
14703 case BeginningOfGame:
14704 SendToProgram("force\n", &first);
14705 SetUserThinkingEnables();
14707 case PlayFromGameFile:
14708 (void) StopLoadGameTimer();
14709 if (gameFileFP != NULL) {
14714 EditPositionDone(TRUE);
14719 SendToProgram("force\n", &first);
14721 case TwoMachinesPlay:
14722 GameEnds(EndOfFile, NULL, GE_PLAYER);
14723 ResurrectChessProgram();
14724 SetUserThinkingEnables();
14727 ResurrectChessProgram();
14729 case IcsPlayingBlack:
14730 case IcsPlayingWhite:
14731 DisplayError(_("Warning: You are still playing a game"), 0);
14734 DisplayError(_("Warning: You are still observing a game"), 0);
14737 DisplayError(_("Warning: You are still examining a game"), 0);
14748 first.offeredDraw = second.offeredDraw = 0;
14750 if (gameMode == PlayFromGameFile) {
14751 whiteTimeRemaining = timeRemaining[0][currentMove];
14752 blackTimeRemaining = timeRemaining[1][currentMove];
14756 if (gameMode == MachinePlaysWhite ||
14757 gameMode == MachinePlaysBlack ||
14758 gameMode == TwoMachinesPlay ||
14759 gameMode == EndOfGame) {
14760 i = forwardMostMove;
14761 while (i > currentMove) {
14762 SendToProgram("undo\n", &first);
14765 if(!adjustedClock) {
14766 whiteTimeRemaining = timeRemaining[0][currentMove];
14767 blackTimeRemaining = timeRemaining[1][currentMove];
14768 DisplayBothClocks();
14770 if (whiteFlag || blackFlag) {
14771 whiteFlag = blackFlag = 0;
14776 gameMode = EditGame;
14783 EditPositionEvent ()
14785 if (gameMode == EditPosition) {
14791 if (gameMode != EditGame) return;
14793 gameMode = EditPosition;
14796 if (currentMove > 0)
14797 CopyBoard(boards[0], boards[currentMove]);
14799 blackPlaysFirst = !WhiteOnMove(currentMove);
14801 currentMove = forwardMostMove = backwardMostMove = 0;
14802 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14804 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14810 /* [DM] icsEngineAnalyze - possible call from other functions */
14811 if (appData.icsEngineAnalyze) {
14812 appData.icsEngineAnalyze = FALSE;
14814 DisplayMessage("",_("Close ICS engine analyze..."));
14816 if (first.analysisSupport && first.analyzing) {
14817 SendToBoth("exit\n");
14818 first.analyzing = second.analyzing = FALSE;
14820 thinkOutput[0] = NULLCHAR;
14824 EditPositionDone (Boolean fakeRights)
14826 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14828 startedFromSetupPosition = TRUE;
14829 InitChessProgram(&first, FALSE);
14830 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14831 boards[0][EP_STATUS] = EP_NONE;
14832 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14833 if(boards[0][0][BOARD_WIDTH>>1] == king) {
14834 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14835 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14836 } else boards[0][CASTLING][2] = NoRights;
14837 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14838 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14839 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14840 } else boards[0][CASTLING][5] = NoRights;
14841 if(gameInfo.variant == VariantSChess) {
14843 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14844 boards[0][VIRGIN][i] = 0;
14845 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14846 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14850 SendToProgram("force\n", &first);
14851 if (blackPlaysFirst) {
14852 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14853 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14854 currentMove = forwardMostMove = backwardMostMove = 1;
14855 CopyBoard(boards[1], boards[0]);
14857 currentMove = forwardMostMove = backwardMostMove = 0;
14859 SendBoard(&first, forwardMostMove);
14860 if (appData.debugMode) {
14861 fprintf(debugFP, "EditPosDone\n");
14864 DisplayMessage("", "");
14865 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14866 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14867 gameMode = EditGame;
14869 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14870 ClearHighlights(); /* [AS] */
14873 /* Pause for `ms' milliseconds */
14874 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14876 TimeDelay (long ms)
14883 } while (SubtractTimeMarks(&m2, &m1) < ms);
14886 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14888 SendMultiLineToICS (char *buf)
14890 char temp[MSG_SIZ+1], *p;
14897 strncpy(temp, buf, len);
14902 if (*p == '\n' || *p == '\r')
14907 strcat(temp, "\n");
14909 SendToPlayer(temp, strlen(temp));
14913 SetWhiteToPlayEvent ()
14915 if (gameMode == EditPosition) {
14916 blackPlaysFirst = FALSE;
14917 DisplayBothClocks(); /* works because currentMove is 0 */
14918 } else if (gameMode == IcsExamining) {
14919 SendToICS(ics_prefix);
14920 SendToICS("tomove white\n");
14925 SetBlackToPlayEvent ()
14927 if (gameMode == EditPosition) {
14928 blackPlaysFirst = TRUE;
14929 currentMove = 1; /* kludge */
14930 DisplayBothClocks();
14932 } else if (gameMode == IcsExamining) {
14933 SendToICS(ics_prefix);
14934 SendToICS("tomove black\n");
14939 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14942 ChessSquare piece = boards[0][y][x];
14943 static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14944 static int lastVariant;
14946 if (gameMode != EditPosition && gameMode != IcsExamining) return;
14948 switch (selection) {
14950 CopyBoard(currentBoard, boards[0]);
14951 CopyBoard(menuBoard, initialPosition);
14952 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14953 SendToICS(ics_prefix);
14954 SendToICS("bsetup clear\n");
14955 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14956 SendToICS(ics_prefix);
14957 SendToICS("clearboard\n");
14960 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14961 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14962 for (y = 0; y < BOARD_HEIGHT; y++) {
14963 if (gameMode == IcsExamining) {
14964 if (boards[currentMove][y][x] != EmptySquare) {
14965 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14970 if(boards[0][y][x] != p) nonEmpty++;
14971 boards[0][y][x] = p;
14974 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14976 if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14977 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
14978 ChessSquare p = menuBoard[0][x];
14979 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14980 p = menuBoard[BOARD_HEIGHT-1][x];
14981 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14983 DisplayMessage("Clicking clock again restores position", "");
14984 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14985 if(!nonEmpty) { // asked to clear an empty board
14986 CopyBoard(boards[0], menuBoard);
14988 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14989 CopyBoard(boards[0], initialPosition);
14991 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14992 && !CompareBoards(nullBoard, erasedBoard)) {
14993 CopyBoard(boards[0], erasedBoard);
14995 CopyBoard(erasedBoard, currentBoard);
14999 if (gameMode == EditPosition) {
15000 DrawPosition(FALSE, boards[0]);
15005 SetWhiteToPlayEvent();
15009 SetBlackToPlayEvent();
15013 if (gameMode == IcsExamining) {
15014 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15015 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15018 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15019 if(x == BOARD_LEFT-2) {
15020 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15021 boards[0][y][1] = 0;
15023 if(x == BOARD_RGHT+1) {
15024 if(y >= gameInfo.holdingsSize) break;
15025 boards[0][y][BOARD_WIDTH-2] = 0;
15028 boards[0][y][x] = EmptySquare;
15029 DrawPosition(FALSE, boards[0]);
15034 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15035 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
15036 selection = (ChessSquare) (PROMOTED piece);
15037 } else if(piece == EmptySquare) selection = WhiteSilver;
15038 else selection = (ChessSquare)((int)piece - 1);
15042 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15043 piece > (int)BlackMan && piece <= (int)BlackKing ) {
15044 selection = (ChessSquare) (DEMOTED piece);
15045 } else if(piece == EmptySquare) selection = BlackSilver;
15046 else selection = (ChessSquare)((int)piece + 1);
15051 if(gameInfo.variant == VariantShatranj ||
15052 gameInfo.variant == VariantXiangqi ||
15053 gameInfo.variant == VariantCourier ||
15054 gameInfo.variant == VariantASEAN ||
15055 gameInfo.variant == VariantMakruk )
15056 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15061 if(gameInfo.variant == VariantXiangqi)
15062 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15063 if(gameInfo.variant == VariantKnightmate)
15064 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15067 if (gameMode == IcsExamining) {
15068 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15069 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15070 PieceToChar(selection), AAA + x, ONE + y);
15073 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15075 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15076 n = PieceToNumber(selection - BlackPawn);
15077 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15078 boards[0][BOARD_HEIGHT-1-n][0] = selection;
15079 boards[0][BOARD_HEIGHT-1-n][1]++;
15081 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15082 n = PieceToNumber(selection);
15083 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15084 boards[0][n][BOARD_WIDTH-1] = selection;
15085 boards[0][n][BOARD_WIDTH-2]++;
15088 boards[0][y][x] = selection;
15089 DrawPosition(TRUE, boards[0]);
15091 fromX = fromY = -1;
15099 DropMenuEvent (ChessSquare selection, int x, int y)
15101 ChessMove moveType;
15103 switch (gameMode) {
15104 case IcsPlayingWhite:
15105 case MachinePlaysBlack:
15106 if (!WhiteOnMove(currentMove)) {
15107 DisplayMoveError(_("It is Black's turn"));
15110 moveType = WhiteDrop;
15112 case IcsPlayingBlack:
15113 case MachinePlaysWhite:
15114 if (WhiteOnMove(currentMove)) {
15115 DisplayMoveError(_("It is White's turn"));
15118 moveType = BlackDrop;
15121 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15127 if (moveType == BlackDrop && selection < BlackPawn) {
15128 selection = (ChessSquare) ((int) selection
15129 + (int) BlackPawn - (int) WhitePawn);
15131 if (boards[currentMove][y][x] != EmptySquare) {
15132 DisplayMoveError(_("That square is occupied"));
15136 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15142 /* Accept a pending offer of any kind from opponent */
15144 if (appData.icsActive) {
15145 SendToICS(ics_prefix);
15146 SendToICS("accept\n");
15147 } else if (cmailMsgLoaded) {
15148 if (currentMove == cmailOldMove &&
15149 commentList[cmailOldMove] != NULL &&
15150 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15151 "Black offers a draw" : "White offers a draw")) {
15153 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15154 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15156 DisplayError(_("There is no pending offer on this move"), 0);
15157 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15160 /* Not used for offers from chess program */
15167 /* Decline a pending offer of any kind from opponent */
15169 if (appData.icsActive) {
15170 SendToICS(ics_prefix);
15171 SendToICS("decline\n");
15172 } else if (cmailMsgLoaded) {
15173 if (currentMove == cmailOldMove &&
15174 commentList[cmailOldMove] != NULL &&
15175 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15176 "Black offers a draw" : "White offers a draw")) {
15178 AppendComment(cmailOldMove, "Draw declined", TRUE);
15179 DisplayComment(cmailOldMove - 1, "Draw declined");
15182 DisplayError(_("There is no pending offer on this move"), 0);
15185 /* Not used for offers from chess program */
15192 /* Issue ICS rematch command */
15193 if (appData.icsActive) {
15194 SendToICS(ics_prefix);
15195 SendToICS("rematch\n");
15202 /* Call your opponent's flag (claim a win on time) */
15203 if (appData.icsActive) {
15204 SendToICS(ics_prefix);
15205 SendToICS("flag\n");
15207 switch (gameMode) {
15210 case MachinePlaysWhite:
15213 GameEnds(GameIsDrawn, "Both players ran out of time",
15216 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15218 DisplayError(_("Your opponent is not out of time"), 0);
15221 case MachinePlaysBlack:
15224 GameEnds(GameIsDrawn, "Both players ran out of time",
15227 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15229 DisplayError(_("Your opponent is not out of time"), 0);
15237 ClockClick (int which)
15238 { // [HGM] code moved to back-end from winboard.c
15239 if(which) { // black clock
15240 if (gameMode == EditPosition || gameMode == IcsExamining) {
15241 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15242 SetBlackToPlayEvent();
15243 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15244 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15245 } else if (shiftKey) {
15246 AdjustClock(which, -1);
15247 } else if (gameMode == IcsPlayingWhite ||
15248 gameMode == MachinePlaysBlack) {
15251 } else { // white clock
15252 if (gameMode == EditPosition || gameMode == IcsExamining) {
15253 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15254 SetWhiteToPlayEvent();
15255 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15256 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15257 } else if (shiftKey) {
15258 AdjustClock(which, -1);
15259 } else if (gameMode == IcsPlayingBlack ||
15260 gameMode == MachinePlaysWhite) {
15269 /* Offer draw or accept pending draw offer from opponent */
15271 if (appData.icsActive) {
15272 /* Note: tournament rules require draw offers to be
15273 made after you make your move but before you punch
15274 your clock. Currently ICS doesn't let you do that;
15275 instead, you immediately punch your clock after making
15276 a move, but you can offer a draw at any time. */
15278 SendToICS(ics_prefix);
15279 SendToICS("draw\n");
15280 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15281 } else if (cmailMsgLoaded) {
15282 if (currentMove == cmailOldMove &&
15283 commentList[cmailOldMove] != NULL &&
15284 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15285 "Black offers a draw" : "White offers a draw")) {
15286 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15287 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15288 } else if (currentMove == cmailOldMove + 1) {
15289 char *offer = WhiteOnMove(cmailOldMove) ?
15290 "White offers a draw" : "Black offers a draw";
15291 AppendComment(currentMove, offer, TRUE);
15292 DisplayComment(currentMove - 1, offer);
15293 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15295 DisplayError(_("You must make your move before offering a draw"), 0);
15296 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15298 } else if (first.offeredDraw) {
15299 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15301 if (first.sendDrawOffers) {
15302 SendToProgram("draw\n", &first);
15303 userOfferedDraw = TRUE;
15311 /* Offer Adjourn or accept pending Adjourn offer from opponent */
15313 if (appData.icsActive) {
15314 SendToICS(ics_prefix);
15315 SendToICS("adjourn\n");
15317 /* Currently GNU Chess doesn't offer or accept Adjourns */
15325 /* Offer Abort or accept pending Abort offer from opponent */
15327 if (appData.icsActive) {
15328 SendToICS(ics_prefix);
15329 SendToICS("abort\n");
15331 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15338 /* Resign. You can do this even if it's not your turn. */
15340 if (appData.icsActive) {
15341 SendToICS(ics_prefix);
15342 SendToICS("resign\n");
15344 switch (gameMode) {
15345 case MachinePlaysWhite:
15346 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15348 case MachinePlaysBlack:
15349 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15352 if (cmailMsgLoaded) {
15354 if (WhiteOnMove(cmailOldMove)) {
15355 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15357 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15359 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15370 StopObservingEvent ()
15372 /* Stop observing current games */
15373 SendToICS(ics_prefix);
15374 SendToICS("unobserve\n");
15378 StopExaminingEvent ()
15380 /* Stop observing current game */
15381 SendToICS(ics_prefix);
15382 SendToICS("unexamine\n");
15386 ForwardInner (int target)
15388 int limit; int oldSeekGraphUp = seekGraphUp;
15390 if (appData.debugMode)
15391 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15392 target, currentMove, forwardMostMove);
15394 if (gameMode == EditPosition)
15397 seekGraphUp = FALSE;
15398 MarkTargetSquares(1);
15400 if (gameMode == PlayFromGameFile && !pausing)
15403 if (gameMode == IcsExamining && pausing)
15404 limit = pauseExamForwardMostMove;
15406 limit = forwardMostMove;
15408 if (target > limit) target = limit;
15410 if (target > 0 && moveList[target - 1][0]) {
15411 int fromX, fromY, toX, toY;
15412 toX = moveList[target - 1][2] - AAA;
15413 toY = moveList[target - 1][3] - ONE;
15414 if (moveList[target - 1][1] == '@') {
15415 if (appData.highlightLastMove) {
15416 SetHighlights(-1, -1, toX, toY);
15419 fromX = moveList[target - 1][0] - AAA;
15420 fromY = moveList[target - 1][1] - ONE;
15421 if (target == currentMove + 1) {
15422 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15424 if (appData.highlightLastMove) {
15425 SetHighlights(fromX, fromY, toX, toY);
15429 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15430 gameMode == Training || gameMode == PlayFromGameFile ||
15431 gameMode == AnalyzeFile) {
15432 while (currentMove < target) {
15433 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15434 SendMoveToProgram(currentMove++, &first);
15437 currentMove = target;
15440 if (gameMode == EditGame || gameMode == EndOfGame) {
15441 whiteTimeRemaining = timeRemaining[0][currentMove];
15442 blackTimeRemaining = timeRemaining[1][currentMove];
15444 DisplayBothClocks();
15445 DisplayMove(currentMove - 1);
15446 DrawPosition(oldSeekGraphUp, boards[currentMove]);
15447 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15448 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15449 DisplayComment(currentMove - 1, commentList[currentMove]);
15451 ClearMap(); // [HGM] exclude: invalidate map
15458 if (gameMode == IcsExamining && !pausing) {
15459 SendToICS(ics_prefix);
15460 SendToICS("forward\n");
15462 ForwardInner(currentMove + 1);
15469 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15470 /* to optimze, we temporarily turn off analysis mode while we feed
15471 * the remaining moves to the engine. Otherwise we get analysis output
15474 if (first.analysisSupport) {
15475 SendToProgram("exit\nforce\n", &first);
15476 first.analyzing = FALSE;
15480 if (gameMode == IcsExamining && !pausing) {
15481 SendToICS(ics_prefix);
15482 SendToICS("forward 999999\n");
15484 ForwardInner(forwardMostMove);
15487 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15488 /* we have fed all the moves, so reactivate analysis mode */
15489 SendToProgram("analyze\n", &first);
15490 first.analyzing = TRUE;
15491 /*first.maybeThinking = TRUE;*/
15492 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15497 BackwardInner (int target)
15499 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15501 if (appData.debugMode)
15502 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15503 target, currentMove, forwardMostMove);
15505 if (gameMode == EditPosition) return;
15506 seekGraphUp = FALSE;
15507 MarkTargetSquares(1);
15508 if (currentMove <= backwardMostMove) {
15510 DrawPosition(full_redraw, boards[currentMove]);
15513 if (gameMode == PlayFromGameFile && !pausing)
15516 if (moveList[target][0]) {
15517 int fromX, fromY, toX, toY;
15518 toX = moveList[target][2] - AAA;
15519 toY = moveList[target][3] - ONE;
15520 if (moveList[target][1] == '@') {
15521 if (appData.highlightLastMove) {
15522 SetHighlights(-1, -1, toX, toY);
15525 fromX = moveList[target][0] - AAA;
15526 fromY = moveList[target][1] - ONE;
15527 if (target == currentMove - 1) {
15528 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15530 if (appData.highlightLastMove) {
15531 SetHighlights(fromX, fromY, toX, toY);
15535 if (gameMode == EditGame || gameMode==AnalyzeMode ||
15536 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15537 while (currentMove > target) {
15538 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15539 // null move cannot be undone. Reload program with move history before it.
15541 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15542 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15544 SendBoard(&first, i);
15545 if(second.analyzing) SendBoard(&second, i);
15546 for(currentMove=i; currentMove<target; currentMove++) {
15547 SendMoveToProgram(currentMove, &first);
15548 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15552 SendToBoth("undo\n");
15556 currentMove = target;
15559 if (gameMode == EditGame || gameMode == EndOfGame) {
15560 whiteTimeRemaining = timeRemaining[0][currentMove];
15561 blackTimeRemaining = timeRemaining[1][currentMove];
15563 DisplayBothClocks();
15564 DisplayMove(currentMove - 1);
15565 DrawPosition(full_redraw, boards[currentMove]);
15566 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15567 // [HGM] PV info: routine tests if comment empty
15568 DisplayComment(currentMove - 1, commentList[currentMove]);
15569 ClearMap(); // [HGM] exclude: invalidate map
15575 if (gameMode == IcsExamining && !pausing) {
15576 SendToICS(ics_prefix);
15577 SendToICS("backward\n");
15579 BackwardInner(currentMove - 1);
15586 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15587 /* to optimize, we temporarily turn off analysis mode while we undo
15588 * all the moves. Otherwise we get analysis output after each undo.
15590 if (first.analysisSupport) {
15591 SendToProgram("exit\nforce\n", &first);
15592 first.analyzing = FALSE;
15596 if (gameMode == IcsExamining && !pausing) {
15597 SendToICS(ics_prefix);
15598 SendToICS("backward 999999\n");
15600 BackwardInner(backwardMostMove);
15603 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15604 /* we have fed all the moves, so reactivate analysis mode */
15605 SendToProgram("analyze\n", &first);
15606 first.analyzing = TRUE;
15607 /*first.maybeThinking = TRUE;*/
15608 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15615 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15616 if (to >= forwardMostMove) to = forwardMostMove;
15617 if (to <= backwardMostMove) to = backwardMostMove;
15618 if (to < currentMove) {
15626 RevertEvent (Boolean annotate)
15628 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15631 if (gameMode != IcsExamining) {
15632 DisplayError(_("You are not examining a game"), 0);
15636 DisplayError(_("You can't revert while pausing"), 0);
15639 SendToICS(ics_prefix);
15640 SendToICS("revert\n");
15644 RetractMoveEvent ()
15646 switch (gameMode) {
15647 case MachinePlaysWhite:
15648 case MachinePlaysBlack:
15649 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15650 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15653 if (forwardMostMove < 2) return;
15654 currentMove = forwardMostMove = forwardMostMove - 2;
15655 whiteTimeRemaining = timeRemaining[0][currentMove];
15656 blackTimeRemaining = timeRemaining[1][currentMove];
15657 DisplayBothClocks();
15658 DisplayMove(currentMove - 1);
15659 ClearHighlights();/*!! could figure this out*/
15660 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15661 SendToProgram("remove\n", &first);
15662 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15665 case BeginningOfGame:
15669 case IcsPlayingWhite:
15670 case IcsPlayingBlack:
15671 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15672 SendToICS(ics_prefix);
15673 SendToICS("takeback 2\n");
15675 SendToICS(ics_prefix);
15676 SendToICS("takeback 1\n");
15685 ChessProgramState *cps;
15687 switch (gameMode) {
15688 case MachinePlaysWhite:
15689 if (!WhiteOnMove(forwardMostMove)) {
15690 DisplayError(_("It is your turn"), 0);
15695 case MachinePlaysBlack:
15696 if (WhiteOnMove(forwardMostMove)) {
15697 DisplayError(_("It is your turn"), 0);
15702 case TwoMachinesPlay:
15703 if (WhiteOnMove(forwardMostMove) ==
15704 (first.twoMachinesColor[0] == 'w')) {
15710 case BeginningOfGame:
15714 SendToProgram("?\n", cps);
15718 TruncateGameEvent ()
15721 if (gameMode != EditGame) return;
15728 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15729 if (forwardMostMove > currentMove) {
15730 if (gameInfo.resultDetails != NULL) {
15731 free(gameInfo.resultDetails);
15732 gameInfo.resultDetails = NULL;
15733 gameInfo.result = GameUnfinished;
15735 forwardMostMove = currentMove;
15736 HistorySet(parseList, backwardMostMove, forwardMostMove,
15744 if (appData.noChessProgram) return;
15745 switch (gameMode) {
15746 case MachinePlaysWhite:
15747 if (WhiteOnMove(forwardMostMove)) {
15748 DisplayError(_("Wait until your turn."), 0);
15752 case BeginningOfGame:
15753 case MachinePlaysBlack:
15754 if (!WhiteOnMove(forwardMostMove)) {
15755 DisplayError(_("Wait until your turn."), 0);
15760 DisplayError(_("No hint available"), 0);
15763 SendToProgram("hint\n", &first);
15764 hintRequested = TRUE;
15770 ListGame * lg = (ListGame *) gameList.head;
15773 static int secondTime = FALSE;
15775 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15776 DisplayError(_("Game list not loaded or empty"), 0);
15780 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15783 DisplayNote(_("Book file exists! Try again for overwrite."));
15787 creatingBook = TRUE;
15788 secondTime = FALSE;
15790 /* Get list size */
15791 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15792 LoadGame(f, nItem, "", TRUE);
15793 AddGameToBook(TRUE);
15794 lg = (ListGame *) lg->node.succ;
15797 creatingBook = FALSE;
15804 if (appData.noChessProgram) return;
15805 switch (gameMode) {
15806 case MachinePlaysWhite:
15807 if (WhiteOnMove(forwardMostMove)) {
15808 DisplayError(_("Wait until your turn."), 0);
15812 case BeginningOfGame:
15813 case MachinePlaysBlack:
15814 if (!WhiteOnMove(forwardMostMove)) {
15815 DisplayError(_("Wait until your turn."), 0);
15820 EditPositionDone(TRUE);
15822 case TwoMachinesPlay:
15827 SendToProgram("bk\n", &first);
15828 bookOutput[0] = NULLCHAR;
15829 bookRequested = TRUE;
15835 char *tags = PGNTags(&gameInfo);
15836 TagsPopUp(tags, CmailMsg());
15840 /* end button procedures */
15843 PrintPosition (FILE *fp, int move)
15847 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15848 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15849 char c = PieceToChar(boards[move][i][j]);
15850 fputc(c == 'x' ? '.' : c, fp);
15851 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15854 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15855 fprintf(fp, "white to play\n");
15857 fprintf(fp, "black to play\n");
15861 PrintOpponents (FILE *fp)
15863 if (gameInfo.white != NULL) {
15864 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15870 /* Find last component of program's own name, using some heuristics */
15872 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15875 int local = (strcmp(host, "localhost") == 0);
15876 while (!local && (p = strchr(prog, ';')) != NULL) {
15878 while (*p == ' ') p++;
15881 if (*prog == '"' || *prog == '\'') {
15882 q = strchr(prog + 1, *prog);
15884 q = strchr(prog, ' ');
15886 if (q == NULL) q = prog + strlen(prog);
15888 while (p >= prog && *p != '/' && *p != '\\') p--;
15890 if(p == prog && *p == '"') p++;
15892 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15893 memcpy(buf, p, q - p);
15894 buf[q - p] = NULLCHAR;
15902 TimeControlTagValue ()
15905 if (!appData.clockMode) {
15906 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15907 } else if (movesPerSession > 0) {
15908 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15909 } else if (timeIncrement == 0) {
15910 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15912 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15914 return StrSave(buf);
15920 /* This routine is used only for certain modes */
15921 VariantClass v = gameInfo.variant;
15922 ChessMove r = GameUnfinished;
15925 if(keepInfo) return;
15927 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15928 r = gameInfo.result;
15929 p = gameInfo.resultDetails;
15930 gameInfo.resultDetails = NULL;
15932 ClearGameInfo(&gameInfo);
15933 gameInfo.variant = v;
15935 switch (gameMode) {
15936 case MachinePlaysWhite:
15937 gameInfo.event = StrSave( appData.pgnEventHeader );
15938 gameInfo.site = StrSave(HostName());
15939 gameInfo.date = PGNDate();
15940 gameInfo.round = StrSave("-");
15941 gameInfo.white = StrSave(first.tidy);
15942 gameInfo.black = StrSave(UserName());
15943 gameInfo.timeControl = TimeControlTagValue();
15946 case MachinePlaysBlack:
15947 gameInfo.event = StrSave( appData.pgnEventHeader );
15948 gameInfo.site = StrSave(HostName());
15949 gameInfo.date = PGNDate();
15950 gameInfo.round = StrSave("-");
15951 gameInfo.white = StrSave(UserName());
15952 gameInfo.black = StrSave(first.tidy);
15953 gameInfo.timeControl = TimeControlTagValue();
15956 case TwoMachinesPlay:
15957 gameInfo.event = StrSave( appData.pgnEventHeader );
15958 gameInfo.site = StrSave(HostName());
15959 gameInfo.date = PGNDate();
15962 snprintf(buf, MSG_SIZ, "%d", roundNr);
15963 gameInfo.round = StrSave(buf);
15965 gameInfo.round = StrSave("-");
15967 if (first.twoMachinesColor[0] == 'w') {
15968 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15969 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15971 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15972 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15974 gameInfo.timeControl = TimeControlTagValue();
15978 gameInfo.event = StrSave("Edited game");
15979 gameInfo.site = StrSave(HostName());
15980 gameInfo.date = PGNDate();
15981 gameInfo.round = StrSave("-");
15982 gameInfo.white = StrSave("-");
15983 gameInfo.black = StrSave("-");
15984 gameInfo.result = r;
15985 gameInfo.resultDetails = p;
15989 gameInfo.event = StrSave("Edited position");
15990 gameInfo.site = StrSave(HostName());
15991 gameInfo.date = PGNDate();
15992 gameInfo.round = StrSave("-");
15993 gameInfo.white = StrSave("-");
15994 gameInfo.black = StrSave("-");
15997 case IcsPlayingWhite:
15998 case IcsPlayingBlack:
16003 case PlayFromGameFile:
16004 gameInfo.event = StrSave("Game from non-PGN file");
16005 gameInfo.site = StrSave(HostName());
16006 gameInfo.date = PGNDate();
16007 gameInfo.round = StrSave("-");
16008 gameInfo.white = StrSave("?");
16009 gameInfo.black = StrSave("?");
16018 ReplaceComment (int index, char *text)
16024 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16025 pvInfoList[index-1].depth == len &&
16026 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16027 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16028 while (*text == '\n') text++;
16029 len = strlen(text);
16030 while (len > 0 && text[len - 1] == '\n') len--;
16032 if (commentList[index] != NULL)
16033 free(commentList[index]);
16036 commentList[index] = NULL;
16039 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16040 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16041 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16042 commentList[index] = (char *) malloc(len + 2);
16043 strncpy(commentList[index], text, len);
16044 commentList[index][len] = '\n';
16045 commentList[index][len + 1] = NULLCHAR;
16047 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16049 commentList[index] = (char *) malloc(len + 7);
16050 safeStrCpy(commentList[index], "{\n", 3);
16051 safeStrCpy(commentList[index]+2, text, len+1);
16052 commentList[index][len+2] = NULLCHAR;
16053 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16054 strcat(commentList[index], "\n}\n");
16059 CrushCRs (char *text)
16067 if (ch == '\r') continue;
16069 } while (ch != '\0');
16073 AppendComment (int index, char *text, Boolean addBraces)
16074 /* addBraces tells if we should add {} */
16079 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16080 if(addBraces == 3) addBraces = 0; else // force appending literally
16081 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16084 while (*text == '\n') text++;
16085 len = strlen(text);
16086 while (len > 0 && text[len - 1] == '\n') len--;
16087 text[len] = NULLCHAR;
16089 if (len == 0) return;
16091 if (commentList[index] != NULL) {
16092 Boolean addClosingBrace = addBraces;
16093 old = commentList[index];
16094 oldlen = strlen(old);
16095 while(commentList[index][oldlen-1] == '\n')
16096 commentList[index][--oldlen] = NULLCHAR;
16097 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16098 safeStrCpy(commentList[index], old, oldlen + len + 6);
16100 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16101 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16102 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16103 while (*text == '\n') { text++; len--; }
16104 commentList[index][--oldlen] = NULLCHAR;
16106 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16107 else strcat(commentList[index], "\n");
16108 strcat(commentList[index], text);
16109 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16110 else strcat(commentList[index], "\n");
16112 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16114 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16115 else commentList[index][0] = NULLCHAR;
16116 strcat(commentList[index], text);
16117 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16118 if(addBraces == TRUE) strcat(commentList[index], "}\n");
16123 FindStr (char * text, char * sub_text)
16125 char * result = strstr( text, sub_text );
16127 if( result != NULL ) {
16128 result += strlen( sub_text );
16134 /* [AS] Try to extract PV info from PGN comment */
16135 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16137 GetInfoFromComment (int index, char * text)
16139 char * sep = text, *p;
16141 if( text != NULL && index > 0 ) {
16144 int time = -1, sec = 0, deci;
16145 char * s_eval = FindStr( text, "[%eval " );
16146 char * s_emt = FindStr( text, "[%emt " );
16148 if( s_eval != NULL || s_emt != NULL ) {
16150 if(0) { // [HGM] this code is not finished, and could actually be detrimental
16155 if( s_eval != NULL ) {
16156 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16160 if( delim != ']' ) {
16165 if( s_emt != NULL ) {
16170 /* We expect something like: [+|-]nnn.nn/dd */
16173 if(*text != '{') return text; // [HGM] braces: must be normal comment
16175 sep = strchr( text, '/' );
16176 if( sep == NULL || sep < (text+4) ) {
16181 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16182 if(p[1] == '(') { // comment starts with PV
16183 p = strchr(p, ')'); // locate end of PV
16184 if(p == NULL || sep < p+5) return text;
16185 // at this point we have something like "{(.*) +0.23/6 ..."
16186 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16187 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16188 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16190 time = -1; sec = -1; deci = -1;
16191 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16192 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16193 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16194 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
16198 if( score_lo < 0 || score_lo >= 100 ) {
16202 if(sec >= 0) time = 600*time + 10*sec; else
16203 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16205 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16207 /* [HGM] PV time: now locate end of PV info */
16208 while( *++sep >= '0' && *sep <= '9'); // strip depth
16210 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16212 while( *++sep >= '0' && *sep <= '9'); // strip seconds
16214 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16215 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16226 pvInfoList[index-1].depth = depth;
16227 pvInfoList[index-1].score = score;
16228 pvInfoList[index-1].time = 10*time; // centi-sec
16229 if(*sep == '}') *sep = 0; else *--sep = '{';
16230 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16236 SendToProgram (char *message, ChessProgramState *cps)
16238 int count, outCount, error;
16241 if (cps->pr == NoProc) return;
16244 if (appData.debugMode) {
16247 fprintf(debugFP, "%ld >%-6s: %s",
16248 SubtractTimeMarks(&now, &programStartTime),
16249 cps->which, message);
16251 fprintf(serverFP, "%ld >%-6s: %s",
16252 SubtractTimeMarks(&now, &programStartTime),
16253 cps->which, message), fflush(serverFP);
16256 count = strlen(message);
16257 outCount = OutputToProcess(cps->pr, message, count, &error);
16258 if (outCount < count && !exiting
16259 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16260 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16261 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16262 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16263 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16264 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16265 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16266 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16268 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16269 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16270 gameInfo.result = res;
16272 gameInfo.resultDetails = StrSave(buf);
16274 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16275 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16280 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16284 ChessProgramState *cps = (ChessProgramState *)closure;
16286 if (isr != cps->isr) return; /* Killed intentionally */
16289 RemoveInputSource(cps->isr);
16290 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16291 _(cps->which), cps->program);
16292 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16293 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16294 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16295 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16296 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16297 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16299 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16300 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16301 gameInfo.result = res;
16303 gameInfo.resultDetails = StrSave(buf);
16305 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16306 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16308 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16309 _(cps->which), cps->program);
16310 RemoveInputSource(cps->isr);
16312 /* [AS] Program is misbehaving badly... kill it */
16313 if( count == -2 ) {
16314 DestroyChildProcess( cps->pr, 9 );
16318 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16323 if ((end_str = strchr(message, '\r')) != NULL)
16324 *end_str = NULLCHAR;
16325 if ((end_str = strchr(message, '\n')) != NULL)
16326 *end_str = NULLCHAR;
16328 if (appData.debugMode) {
16329 TimeMark now; int print = 1;
16330 char *quote = ""; char c; int i;
16332 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16333 char start = message[0];
16334 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16335 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16336 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
16337 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16338 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16339 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
16340 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16341 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
16342 sscanf(message, "hint: %c", &c)!=1 &&
16343 sscanf(message, "pong %c", &c)!=1 && start != '#') {
16344 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16345 print = (appData.engineComments >= 2);
16347 message[0] = start; // restore original message
16351 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16352 SubtractTimeMarks(&now, &programStartTime), cps->which,
16356 fprintf(serverFP, "%ld <%-6s: %s%s\n",
16357 SubtractTimeMarks(&now, &programStartTime), cps->which,
16359 message), fflush(serverFP);
16363 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16364 if (appData.icsEngineAnalyze) {
16365 if (strstr(message, "whisper") != NULL ||
16366 strstr(message, "kibitz") != NULL ||
16367 strstr(message, "tellics") != NULL) return;
16370 HandleMachineMove(message, cps);
16375 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16380 if( timeControl_2 > 0 ) {
16381 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16382 tc = timeControl_2;
16385 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16386 inc /= cps->timeOdds;
16387 st /= cps->timeOdds;
16389 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16392 /* Set exact time per move, normally using st command */
16393 if (cps->stKludge) {
16394 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16396 if (seconds == 0) {
16397 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16399 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16402 snprintf(buf, MSG_SIZ, "st %d\n", st);
16405 /* Set conventional or incremental time control, using level command */
16406 if (seconds == 0) {
16407 /* Note old gnuchess bug -- minutes:seconds used to not work.
16408 Fixed in later versions, but still avoid :seconds
16409 when seconds is 0. */
16410 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16412 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16413 seconds, inc/1000.);
16416 SendToProgram(buf, cps);
16418 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16419 /* Orthogonally, limit search to given depth */
16421 if (cps->sdKludge) {
16422 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16424 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16426 SendToProgram(buf, cps);
16429 if(cps->nps >= 0) { /* [HGM] nps */
16430 if(cps->supportsNPS == FALSE)
16431 cps->nps = -1; // don't use if engine explicitly says not supported!
16433 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16434 SendToProgram(buf, cps);
16439 ChessProgramState *
16441 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16443 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16444 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16450 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16452 char message[MSG_SIZ];
16455 /* Note: this routine must be called when the clocks are stopped
16456 or when they have *just* been set or switched; otherwise
16457 it will be off by the time since the current tick started.
16459 if (machineWhite) {
16460 time = whiteTimeRemaining / 10;
16461 otime = blackTimeRemaining / 10;
16463 time = blackTimeRemaining / 10;
16464 otime = whiteTimeRemaining / 10;
16466 /* [HGM] translate opponent's time by time-odds factor */
16467 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16469 if (time <= 0) time = 1;
16470 if (otime <= 0) otime = 1;
16472 snprintf(message, MSG_SIZ, "time %ld\n", time);
16473 SendToProgram(message, cps);
16475 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16476 SendToProgram(message, cps);
16480 EngineDefinedVariant (ChessProgramState *cps, int n)
16481 { // return name of n-th unknown variant that engine supports
16482 static char buf[MSG_SIZ];
16483 char *p, *s = cps->variants;
16484 if(!s) return NULL;
16485 do { // parse string from variants feature
16487 p = strchr(s, ',');
16488 if(p) *p = NULLCHAR;
16489 v = StringToVariant(s);
16490 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16491 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16492 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16495 if(n < 0) return buf;
16501 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16504 int len = strlen(name);
16507 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16509 sscanf(*p, "%d", &val);
16511 while (**p && **p != ' ')
16513 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16514 SendToProgram(buf, cps);
16521 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16524 int len = strlen(name);
16525 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16527 sscanf(*p, "%d", loc);
16528 while (**p && **p != ' ') (*p)++;
16529 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16530 SendToProgram(buf, cps);
16537 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16540 int len = strlen(name);
16541 if (strncmp((*p), name, len) == 0
16542 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16544 ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16545 sscanf(*p, "%[^\"]", *loc);
16546 while (**p && **p != '\"') (*p)++;
16547 if (**p == '\"') (*p)++;
16548 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16549 SendToProgram(buf, cps);
16556 ParseOption (Option *opt, ChessProgramState *cps)
16557 // [HGM] options: process the string that defines an engine option, and determine
16558 // name, type, default value, and allowed value range
16560 char *p, *q, buf[MSG_SIZ];
16561 int n, min = (-1)<<31, max = 1<<31, def;
16563 if(p = strstr(opt->name, " -spin ")) {
16564 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16565 if(max < min) max = min; // enforce consistency
16566 if(def < min) def = min;
16567 if(def > max) def = max;
16572 } else if((p = strstr(opt->name, " -slider "))) {
16573 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16574 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16575 if(max < min) max = min; // enforce consistency
16576 if(def < min) def = min;
16577 if(def > max) def = max;
16581 opt->type = Spin; // Slider;
16582 } else if((p = strstr(opt->name, " -string "))) {
16583 opt->textValue = p+9;
16584 opt->type = TextBox;
16585 } else if((p = strstr(opt->name, " -file "))) {
16586 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16587 opt->textValue = p+7;
16588 opt->type = FileName; // FileName;
16589 } else if((p = strstr(opt->name, " -path "))) {
16590 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16591 opt->textValue = p+7;
16592 opt->type = PathName; // PathName;
16593 } else if(p = strstr(opt->name, " -check ")) {
16594 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16595 opt->value = (def != 0);
16596 opt->type = CheckBox;
16597 } else if(p = strstr(opt->name, " -combo ")) {
16598 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16599 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16600 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16601 opt->value = n = 0;
16602 while(q = StrStr(q, " /// ")) {
16603 n++; *q = 0; // count choices, and null-terminate each of them
16605 if(*q == '*') { // remember default, which is marked with * prefix
16609 cps->comboList[cps->comboCnt++] = q;
16611 cps->comboList[cps->comboCnt++] = NULL;
16613 opt->type = ComboBox;
16614 } else if(p = strstr(opt->name, " -button")) {
16615 opt->type = Button;
16616 } else if(p = strstr(opt->name, " -save")) {
16617 opt->type = SaveButton;
16618 } else return FALSE;
16619 *p = 0; // terminate option name
16620 // now look if the command-line options define a setting for this engine option.
16621 if(cps->optionSettings && cps->optionSettings[0])
16622 p = strstr(cps->optionSettings, opt->name); else p = NULL;
16623 if(p && (p == cps->optionSettings || p[-1] == ',')) {
16624 snprintf(buf, MSG_SIZ, "option %s", p);
16625 if(p = strstr(buf, ",")) *p = 0;
16626 if(q = strchr(buf, '=')) switch(opt->type) {
16628 for(n=0; n<opt->max; n++)
16629 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16632 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16636 opt->value = atoi(q+1);
16641 SendToProgram(buf, cps);
16647 FeatureDone (ChessProgramState *cps, int val)
16649 DelayedEventCallback cb = GetDelayedEvent();
16650 if ((cb == InitBackEnd3 && cps == &first) ||
16651 (cb == SettingsMenuIfReady && cps == &second) ||
16652 (cb == LoadEngine) ||
16653 (cb == TwoMachinesEventIfReady)) {
16654 CancelDelayedEvent();
16655 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16657 cps->initDone = val;
16658 if(val) cps->reload = FALSE;
16661 /* Parse feature command from engine */
16663 ParseFeatures (char *args, ChessProgramState *cps)
16671 while (*p == ' ') p++;
16672 if (*p == NULLCHAR) return;
16674 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16675 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16676 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16677 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16678 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16679 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16680 if (BoolFeature(&p, "reuse", &val, cps)) {
16681 /* Engine can disable reuse, but can't enable it if user said no */
16682 if (!val) cps->reuse = FALSE;
16685 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16686 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16687 if (gameMode == TwoMachinesPlay) {
16688 DisplayTwoMachinesTitle();
16694 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16695 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16696 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16697 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16698 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16699 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16700 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16701 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16702 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16703 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16704 if (IntFeature(&p, "done", &val, cps)) {
16705 FeatureDone(cps, val);
16708 /* Added by Tord: */
16709 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16710 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16711 /* End of additions by Tord */
16713 /* [HGM] added features: */
16714 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16715 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16716 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16717 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16718 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16719 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16720 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16721 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16722 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16723 FREE(cps->option[cps->nrOptions].name);
16724 cps->option[cps->nrOptions].name = q; q = NULL;
16725 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16726 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16727 SendToProgram(buf, cps);
16730 if(cps->nrOptions >= MAX_OPTIONS) {
16732 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16733 DisplayError(buf, 0);
16737 /* End of additions by HGM */
16739 /* unknown feature: complain and skip */
16741 while (*q && *q != '=') q++;
16742 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16743 SendToProgram(buf, cps);
16749 while (*p && *p != '\"') p++;
16750 if (*p == '\"') p++;
16752 while (*p && *p != ' ') p++;
16760 PeriodicUpdatesEvent (int newState)
16762 if (newState == appData.periodicUpdates)
16765 appData.periodicUpdates=newState;
16767 /* Display type changes, so update it now */
16768 // DisplayAnalysis();
16770 /* Get the ball rolling again... */
16772 AnalysisPeriodicEvent(1);
16773 StartAnalysisClock();
16778 PonderNextMoveEvent (int newState)
16780 if (newState == appData.ponderNextMove) return;
16781 if (gameMode == EditPosition) EditPositionDone(TRUE);
16783 SendToProgram("hard\n", &first);
16784 if (gameMode == TwoMachinesPlay) {
16785 SendToProgram("hard\n", &second);
16788 SendToProgram("easy\n", &first);
16789 thinkOutput[0] = NULLCHAR;
16790 if (gameMode == TwoMachinesPlay) {
16791 SendToProgram("easy\n", &second);
16794 appData.ponderNextMove = newState;
16798 NewSettingEvent (int option, int *feature, char *command, int value)
16802 if (gameMode == EditPosition) EditPositionDone(TRUE);
16803 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16804 if(feature == NULL || *feature) SendToProgram(buf, &first);
16805 if (gameMode == TwoMachinesPlay) {
16806 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16811 ShowThinkingEvent ()
16812 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16814 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16815 int newState = appData.showThinking
16816 // [HGM] thinking: other features now need thinking output as well
16817 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16819 if (oldState == newState) return;
16820 oldState = newState;
16821 if (gameMode == EditPosition) EditPositionDone(TRUE);
16823 SendToProgram("post\n", &first);
16824 if (gameMode == TwoMachinesPlay) {
16825 SendToProgram("post\n", &second);
16828 SendToProgram("nopost\n", &first);
16829 thinkOutput[0] = NULLCHAR;
16830 if (gameMode == TwoMachinesPlay) {
16831 SendToProgram("nopost\n", &second);
16834 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16838 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16840 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16841 if (pr == NoProc) return;
16842 AskQuestion(title, question, replyPrefix, pr);
16846 TypeInEvent (char firstChar)
16848 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16849 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16850 gameMode == AnalyzeMode || gameMode == EditGame ||
16851 gameMode == EditPosition || gameMode == IcsExamining ||
16852 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16853 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16854 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16855 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
16856 gameMode == Training) PopUpMoveDialog(firstChar);
16860 TypeInDoneEvent (char *move)
16863 int n, fromX, fromY, toX, toY;
16865 ChessMove moveType;
16868 if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16869 EditPositionPasteFEN(move);
16872 // [HGM] movenum: allow move number to be typed in any mode
16873 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16877 // undocumented kludge: allow command-line option to be typed in!
16878 // (potentially fatal, and does not implement the effect of the option.)
16879 // should only be used for options that are values on which future decisions will be made,
16880 // and definitely not on options that would be used during initialization.
16881 if(strstr(move, "!!! -") == move) {
16882 ParseArgsFromString(move+4);
16886 if (gameMode != EditGame && currentMove != forwardMostMove &&
16887 gameMode != Training) {
16888 DisplayMoveError(_("Displayed move is not current"));
16890 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16891 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16892 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16893 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16894 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16895 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16897 DisplayMoveError(_("Could not parse move"));
16903 DisplayMove (int moveNumber)
16905 char message[MSG_SIZ];
16907 char cpThinkOutput[MSG_SIZ];
16909 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16911 if (moveNumber == forwardMostMove - 1 ||
16912 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16914 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16916 if (strchr(cpThinkOutput, '\n')) {
16917 *strchr(cpThinkOutput, '\n') = NULLCHAR;
16920 *cpThinkOutput = NULLCHAR;
16923 /* [AS] Hide thinking from human user */
16924 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16925 *cpThinkOutput = NULLCHAR;
16926 if( thinkOutput[0] != NULLCHAR ) {
16929 for( i=0; i<=hiddenThinkOutputState; i++ ) {
16930 cpThinkOutput[i] = '.';
16932 cpThinkOutput[i] = NULLCHAR;
16933 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16937 if (moveNumber == forwardMostMove - 1 &&
16938 gameInfo.resultDetails != NULL) {
16939 if (gameInfo.resultDetails[0] == NULLCHAR) {
16940 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16942 snprintf(res, MSG_SIZ, " {%s} %s",
16943 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16949 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16950 DisplayMessage(res, cpThinkOutput);
16952 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16953 WhiteOnMove(moveNumber) ? " " : ".. ",
16954 parseList[moveNumber], res);
16955 DisplayMessage(message, cpThinkOutput);
16960 DisplayComment (int moveNumber, char *text)
16962 char title[MSG_SIZ];
16964 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16965 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16967 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16968 WhiteOnMove(moveNumber) ? " " : ".. ",
16969 parseList[moveNumber]);
16971 if (text != NULL && (appData.autoDisplayComment || commentUp))
16972 CommentPopUp(title, text);
16975 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16976 * might be busy thinking or pondering. It can be omitted if your
16977 * gnuchess is configured to stop thinking immediately on any user
16978 * input. However, that gnuchess feature depends on the FIONREAD
16979 * ioctl, which does not work properly on some flavors of Unix.
16982 Attention (ChessProgramState *cps)
16985 if (!cps->useSigint) return;
16986 if (appData.noChessProgram || (cps->pr == NoProc)) return;
16987 switch (gameMode) {
16988 case MachinePlaysWhite:
16989 case MachinePlaysBlack:
16990 case TwoMachinesPlay:
16991 case IcsPlayingWhite:
16992 case IcsPlayingBlack:
16995 /* Skip if we know it isn't thinking */
16996 if (!cps->maybeThinking) return;
16997 if (appData.debugMode)
16998 fprintf(debugFP, "Interrupting %s\n", cps->which);
16999 InterruptChildProcess(cps->pr);
17000 cps->maybeThinking = FALSE;
17005 #endif /*ATTENTION*/
17011 if (whiteTimeRemaining <= 0) {
17014 if (appData.icsActive) {
17015 if (appData.autoCallFlag &&
17016 gameMode == IcsPlayingBlack && !blackFlag) {
17017 SendToICS(ics_prefix);
17018 SendToICS("flag\n");
17022 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17024 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17025 if (appData.autoCallFlag) {
17026 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17033 if (blackTimeRemaining <= 0) {
17036 if (appData.icsActive) {
17037 if (appData.autoCallFlag &&
17038 gameMode == IcsPlayingWhite && !whiteFlag) {
17039 SendToICS(ics_prefix);
17040 SendToICS("flag\n");
17044 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17046 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17047 if (appData.autoCallFlag) {
17048 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17059 CheckTimeControl ()
17061 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17062 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17065 * add time to clocks when time control is achieved ([HGM] now also used for increment)
17067 if ( !WhiteOnMove(forwardMostMove) ) {
17068 /* White made time control */
17069 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17070 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17071 /* [HGM] time odds: correct new time quota for time odds! */
17072 / WhitePlayer()->timeOdds;
17073 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17075 lastBlack -= blackTimeRemaining;
17076 /* Black made time control */
17077 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17078 / WhitePlayer()->other->timeOdds;
17079 lastWhite = whiteTimeRemaining;
17084 DisplayBothClocks ()
17086 int wom = gameMode == EditPosition ?
17087 !blackPlaysFirst : WhiteOnMove(currentMove);
17088 DisplayWhiteClock(whiteTimeRemaining, wom);
17089 DisplayBlackClock(blackTimeRemaining, !wom);
17093 /* Timekeeping seems to be a portability nightmare. I think everyone
17094 has ftime(), but I'm really not sure, so I'm including some ifdefs
17095 to use other calls if you don't. Clocks will be less accurate if
17096 you have neither ftime nor gettimeofday.
17099 /* VS 2008 requires the #include outside of the function */
17100 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17101 #include <sys/timeb.h>
17104 /* Get the current time as a TimeMark */
17106 GetTimeMark (TimeMark *tm)
17108 #if HAVE_GETTIMEOFDAY
17110 struct timeval timeVal;
17111 struct timezone timeZone;
17113 gettimeofday(&timeVal, &timeZone);
17114 tm->sec = (long) timeVal.tv_sec;
17115 tm->ms = (int) (timeVal.tv_usec / 1000L);
17117 #else /*!HAVE_GETTIMEOFDAY*/
17120 // include <sys/timeb.h> / moved to just above start of function
17121 struct timeb timeB;
17124 tm->sec = (long) timeB.time;
17125 tm->ms = (int) timeB.millitm;
17127 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17128 tm->sec = (long) time(NULL);
17134 /* Return the difference in milliseconds between two
17135 time marks. We assume the difference will fit in a long!
17138 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17140 return 1000L*(tm2->sec - tm1->sec) +
17141 (long) (tm2->ms - tm1->ms);
17146 * Code to manage the game clocks.
17148 * In tournament play, black starts the clock and then white makes a move.
17149 * We give the human user a slight advantage if he is playing white---the
17150 * clocks don't run until he makes his first move, so it takes zero time.
17151 * Also, we don't account for network lag, so we could get out of sync
17152 * with GNU Chess's clock -- but then, referees are always right.
17155 static TimeMark tickStartTM;
17156 static long intendedTickLength;
17159 NextTickLength (long timeRemaining)
17161 long nominalTickLength, nextTickLength;
17163 if (timeRemaining > 0L && timeRemaining <= 10000L)
17164 nominalTickLength = 100L;
17166 nominalTickLength = 1000L;
17167 nextTickLength = timeRemaining % nominalTickLength;
17168 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17170 return nextTickLength;
17173 /* Adjust clock one minute up or down */
17175 AdjustClock (Boolean which, int dir)
17177 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17178 if(which) blackTimeRemaining += 60000*dir;
17179 else whiteTimeRemaining += 60000*dir;
17180 DisplayBothClocks();
17181 adjustedClock = TRUE;
17184 /* Stop clocks and reset to a fresh time control */
17188 (void) StopClockTimer();
17189 if (appData.icsActive) {
17190 whiteTimeRemaining = blackTimeRemaining = 0;
17191 } else if (searchTime) {
17192 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17193 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17194 } else { /* [HGM] correct new time quote for time odds */
17195 whiteTC = blackTC = fullTimeControlString;
17196 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17197 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17199 if (whiteFlag || blackFlag) {
17201 whiteFlag = blackFlag = FALSE;
17203 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17204 DisplayBothClocks();
17205 adjustedClock = FALSE;
17208 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17210 /* Decrement running clock by amount of time that has passed */
17214 long timeRemaining;
17215 long lastTickLength, fudge;
17218 if (!appData.clockMode) return;
17219 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17223 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17225 /* Fudge if we woke up a little too soon */
17226 fudge = intendedTickLength - lastTickLength;
17227 if (fudge < 0 || fudge > FUDGE) fudge = 0;
17229 if (WhiteOnMove(forwardMostMove)) {
17230 if(whiteNPS >= 0) lastTickLength = 0;
17231 timeRemaining = whiteTimeRemaining -= lastTickLength;
17232 if(timeRemaining < 0 && !appData.icsActive) {
17233 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17234 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17235 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17236 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17239 DisplayWhiteClock(whiteTimeRemaining - fudge,
17240 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17242 if(blackNPS >= 0) lastTickLength = 0;
17243 timeRemaining = blackTimeRemaining -= lastTickLength;
17244 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17245 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17247 blackStartMove = forwardMostMove;
17248 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17251 DisplayBlackClock(blackTimeRemaining - fudge,
17252 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17254 if (CheckFlags()) return;
17256 if(twoBoards) { // count down secondary board's clocks as well
17257 activePartnerTime -= lastTickLength;
17259 if(activePartner == 'W')
17260 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17262 DisplayBlackClock(activePartnerTime, TRUE);
17267 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17268 StartClockTimer(intendedTickLength);
17270 /* if the time remaining has fallen below the alarm threshold, sound the
17271 * alarm. if the alarm has sounded and (due to a takeback or time control
17272 * with increment) the time remaining has increased to a level above the
17273 * threshold, reset the alarm so it can sound again.
17276 if (appData.icsActive && appData.icsAlarm) {
17278 /* make sure we are dealing with the user's clock */
17279 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17280 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17283 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17284 alarmSounded = FALSE;
17285 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17287 alarmSounded = TRUE;
17293 /* A player has just moved, so stop the previously running
17294 clock and (if in clock mode) start the other one.
17295 We redisplay both clocks in case we're in ICS mode, because
17296 ICS gives us an update to both clocks after every move.
17297 Note that this routine is called *after* forwardMostMove
17298 is updated, so the last fractional tick must be subtracted
17299 from the color that is *not* on move now.
17302 SwitchClocks (int newMoveNr)
17304 long lastTickLength;
17306 int flagged = FALSE;
17310 if (StopClockTimer() && appData.clockMode) {
17311 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17312 if (!WhiteOnMove(forwardMostMove)) {
17313 if(blackNPS >= 0) lastTickLength = 0;
17314 blackTimeRemaining -= lastTickLength;
17315 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17316 // if(pvInfoList[forwardMostMove].time == -1)
17317 pvInfoList[forwardMostMove].time = // use GUI time
17318 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17320 if(whiteNPS >= 0) lastTickLength = 0;
17321 whiteTimeRemaining -= lastTickLength;
17322 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17323 // if(pvInfoList[forwardMostMove].time == -1)
17324 pvInfoList[forwardMostMove].time =
17325 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17327 flagged = CheckFlags();
17329 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17330 CheckTimeControl();
17332 if (flagged || !appData.clockMode) return;
17334 switch (gameMode) {
17335 case MachinePlaysBlack:
17336 case MachinePlaysWhite:
17337 case BeginningOfGame:
17338 if (pausing) return;
17342 case PlayFromGameFile:
17350 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17351 if(WhiteOnMove(forwardMostMove))
17352 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17353 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17357 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17358 whiteTimeRemaining : blackTimeRemaining);
17359 StartClockTimer(intendedTickLength);
17363 /* Stop both clocks */
17367 long lastTickLength;
17370 if (!StopClockTimer()) return;
17371 if (!appData.clockMode) return;
17375 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17376 if (WhiteOnMove(forwardMostMove)) {
17377 if(whiteNPS >= 0) lastTickLength = 0;
17378 whiteTimeRemaining -= lastTickLength;
17379 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17381 if(blackNPS >= 0) lastTickLength = 0;
17382 blackTimeRemaining -= lastTickLength;
17383 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17388 /* Start clock of player on move. Time may have been reset, so
17389 if clock is already running, stop and restart it. */
17393 (void) StopClockTimer(); /* in case it was running already */
17394 DisplayBothClocks();
17395 if (CheckFlags()) return;
17397 if (!appData.clockMode) return;
17398 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17400 GetTimeMark(&tickStartTM);
17401 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17402 whiteTimeRemaining : blackTimeRemaining);
17404 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17405 whiteNPS = blackNPS = -1;
17406 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17407 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17408 whiteNPS = first.nps;
17409 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17410 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17411 blackNPS = first.nps;
17412 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17413 whiteNPS = second.nps;
17414 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17415 blackNPS = second.nps;
17416 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17418 StartClockTimer(intendedTickLength);
17422 TimeString (long ms)
17424 long second, minute, hour, day;
17426 static char buf[32];
17428 if (ms > 0 && ms <= 9900) {
17429 /* convert milliseconds to tenths, rounding up */
17430 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17432 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17436 /* convert milliseconds to seconds, rounding up */
17437 /* use floating point to avoid strangeness of integer division
17438 with negative dividends on many machines */
17439 second = (long) floor(((double) (ms + 999L)) / 1000.0);
17446 day = second / (60 * 60 * 24);
17447 second = second % (60 * 60 * 24);
17448 hour = second / (60 * 60);
17449 second = second % (60 * 60);
17450 minute = second / 60;
17451 second = second % 60;
17454 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17455 sign, day, hour, minute, second);
17457 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17459 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17466 * This is necessary because some C libraries aren't ANSI C compliant yet.
17469 StrStr (char *string, char *match)
17473 length = strlen(match);
17475 for (i = strlen(string) - length; i >= 0; i--, string++)
17476 if (!strncmp(match, string, length))
17483 StrCaseStr (char *string, char *match)
17487 length = strlen(match);
17489 for (i = strlen(string) - length; i >= 0; i--, string++) {
17490 for (j = 0; j < length; j++) {
17491 if (ToLower(match[j]) != ToLower(string[j]))
17494 if (j == length) return string;
17502 StrCaseCmp (char *s1, char *s2)
17507 c1 = ToLower(*s1++);
17508 c2 = ToLower(*s2++);
17509 if (c1 > c2) return 1;
17510 if (c1 < c2) return -1;
17511 if (c1 == NULLCHAR) return 0;
17519 return isupper(c) ? tolower(c) : c;
17526 return islower(c) ? toupper(c) : c;
17528 #endif /* !_amigados */
17535 if ((ret = (char *) malloc(strlen(s) + 1)))
17537 safeStrCpy(ret, s, strlen(s)+1);
17543 StrSavePtr (char *s, char **savePtr)
17548 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17549 safeStrCpy(*savePtr, s, strlen(s)+1);
17561 clock = time((time_t *)NULL);
17562 tm = localtime(&clock);
17563 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17564 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17565 return StrSave(buf);
17570 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17572 int i, j, fromX, fromY, toX, toY;
17579 whiteToPlay = (gameMode == EditPosition) ?
17580 !blackPlaysFirst : (move % 2 == 0);
17583 /* Piece placement data */
17584 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17585 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17587 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17588 if (boards[move][i][j] == EmptySquare) {
17590 } else { ChessSquare piece = boards[move][i][j];
17591 if (emptycount > 0) {
17592 if(emptycount<10) /* [HGM] can be >= 10 */
17593 *p++ = '0' + emptycount;
17594 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17597 if(PieceToChar(piece) == '+') {
17598 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17600 piece = (ChessSquare)(DEMOTED piece);
17602 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17604 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17605 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17610 if (emptycount > 0) {
17611 if(emptycount<10) /* [HGM] can be >= 10 */
17612 *p++ = '0' + emptycount;
17613 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17620 /* [HGM] print Crazyhouse or Shogi holdings */
17621 if( gameInfo.holdingsWidth ) {
17622 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17624 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17625 piece = boards[move][i][BOARD_WIDTH-1];
17626 if( piece != EmptySquare )
17627 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17628 *p++ = PieceToChar(piece);
17630 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17631 piece = boards[move][BOARD_HEIGHT-i-1][0];
17632 if( piece != EmptySquare )
17633 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17634 *p++ = PieceToChar(piece);
17637 if( q == p ) *p++ = '-';
17643 *p++ = whiteToPlay ? 'w' : 'b';
17646 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17647 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17649 if(nrCastlingRights) {
17651 if(appData.fischerCastling) {
17652 /* [HGM] write directly from rights */
17653 if(boards[move][CASTLING][2] != NoRights &&
17654 boards[move][CASTLING][0] != NoRights )
17655 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17656 if(boards[move][CASTLING][2] != NoRights &&
17657 boards[move][CASTLING][1] != NoRights )
17658 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17659 if(boards[move][CASTLING][5] != NoRights &&
17660 boards[move][CASTLING][3] != NoRights )
17661 *p++ = boards[move][CASTLING][3] + AAA;
17662 if(boards[move][CASTLING][5] != NoRights &&
17663 boards[move][CASTLING][4] != NoRights )
17664 *p++ = boards[move][CASTLING][4] + AAA;
17667 /* [HGM] write true castling rights */
17668 if( nrCastlingRights == 6 ) {
17670 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17671 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
17672 q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17673 boards[move][CASTLING][2] != NoRights );
17674 if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17675 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17676 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17677 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17678 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17682 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17683 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
17684 q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17685 boards[move][CASTLING][5] != NoRights );
17686 if(gameInfo.variant == VariantSChess) {
17687 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17688 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17689 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17690 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17695 if (q == p) *p++ = '-'; /* No castling rights */
17699 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17700 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17701 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17702 /* En passant target square */
17703 if (move > backwardMostMove) {
17704 fromX = moveList[move - 1][0] - AAA;
17705 fromY = moveList[move - 1][1] - ONE;
17706 toX = moveList[move - 1][2] - AAA;
17707 toY = moveList[move - 1][3] - ONE;
17708 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17709 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17710 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17712 /* 2-square pawn move just happened */
17714 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17718 } else if(move == backwardMostMove) {
17719 // [HGM] perhaps we should always do it like this, and forget the above?
17720 if((signed char)boards[move][EP_STATUS] >= 0) {
17721 *p++ = boards[move][EP_STATUS] + AAA;
17722 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17734 { int i = 0, j=move;
17736 /* [HGM] find reversible plies */
17737 if (appData.debugMode) { int k;
17738 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17739 for(k=backwardMostMove; k<=forwardMostMove; k++)
17740 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17744 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17745 if( j == backwardMostMove ) i += initialRulePlies;
17746 sprintf(p, "%d ", i);
17747 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17749 /* Fullmove number */
17750 sprintf(p, "%d", (move / 2) + 1);
17751 } else *--p = NULLCHAR;
17753 return StrSave(buf);
17757 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17759 int i, j, k, w=0, subst=0, shuffle=0;
17761 int emptycount, virgin[BOARD_FILES];
17766 /* Piece placement data */
17767 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17770 if (*p == '/' || *p == ' ' || *p == '[' ) {
17772 emptycount = gameInfo.boardWidth - j;
17773 while (emptycount--)
17774 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17775 if (*p == '/') p++;
17776 else if(autoSize) { // we stumbled unexpectedly into end of board
17777 for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17778 for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17780 appData.NrRanks = gameInfo.boardHeight - i; i=0;
17783 #if(BOARD_FILES >= 10)
17784 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17785 p++; emptycount=10;
17786 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17787 while (emptycount--)
17788 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17790 } else if (*p == '*') {
17791 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17792 } else if (isdigit(*p)) {
17793 emptycount = *p++ - '0';
17794 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17795 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17796 while (emptycount--)
17797 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17798 } else if (*p == '<') {
17799 if(i == BOARD_HEIGHT-1) shuffle = 1;
17800 else if (i != 0 || !shuffle) return FALSE;
17802 } else if (shuffle && *p == '>') {
17803 p++; // for now ignore closing shuffle range, and assume rank-end
17804 } else if (*p == '?') {
17805 if (j >= gameInfo.boardWidth) return FALSE;
17806 if (i != 0 && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
17807 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
17808 } else if (*p == '+' || isalpha(*p)) {
17809 if (j >= gameInfo.boardWidth) return FALSE;
17811 piece = CharToPiece(*++p);
17812 if(piece == EmptySquare) return FALSE; /* unknown piece */
17813 piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17814 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17815 } else piece = CharToPiece(*p++);
17817 if(piece==EmptySquare) return FALSE; /* unknown piece */
17818 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17819 piece = (ChessSquare) (PROMOTED piece);
17820 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17823 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17829 while (*p == '/' || *p == ' ') p++;
17831 if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17833 /* [HGM] by default clear Crazyhouse holdings, if present */
17834 if(gameInfo.holdingsWidth) {
17835 for(i=0; i<BOARD_HEIGHT; i++) {
17836 board[i][0] = EmptySquare; /* black holdings */
17837 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17838 board[i][1] = (ChessSquare) 0; /* black counts */
17839 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17843 /* [HGM] look for Crazyhouse holdings here */
17844 while(*p==' ') p++;
17845 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17846 int swap=0, wcnt=0, bcnt=0;
17848 if(*p == '<') swap++, p++;
17849 if(*p == '-' ) p++; /* empty holdings */ else {
17850 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17851 /* if we would allow FEN reading to set board size, we would */
17852 /* have to add holdings and shift the board read so far here */
17853 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17855 if((int) piece >= (int) BlackPawn ) {
17856 i = (int)piece - (int)BlackPawn;
17857 i = PieceToNumber((ChessSquare)i);
17858 if( i >= gameInfo.holdingsSize ) return FALSE;
17859 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17860 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
17863 i = (int)piece - (int)WhitePawn;
17864 i = PieceToNumber((ChessSquare)i);
17865 if( i >= gameInfo.holdingsSize ) return FALSE;
17866 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
17867 board[i][BOARD_WIDTH-2]++; /* black holdings */
17871 if(subst) { // substitute back-rank question marks by holdings pieces
17872 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
17873 int k, m, n = bcnt + 1;
17874 if(board[0][j] == ClearBoard) {
17875 if(!wcnt) return FALSE;
17877 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
17878 board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
17879 if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
17883 if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
17884 if(!bcnt) return FALSE;
17885 if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
17886 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
17887 board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
17888 if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
17899 if(subst) return FALSE; // substitution requested, but no holdings
17901 while(*p == ' ') p++;
17905 if(appData.colorNickNames) {
17906 if( c == appData.colorNickNames[0] ) c = 'w'; else
17907 if( c == appData.colorNickNames[1] ) c = 'b';
17911 *blackPlaysFirst = FALSE;
17914 *blackPlaysFirst = TRUE;
17920 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17921 /* return the extra info in global variiables */
17923 /* set defaults in case FEN is incomplete */
17924 board[EP_STATUS] = EP_UNKNOWN;
17925 for(i=0; i<nrCastlingRights; i++ ) {
17926 board[CASTLING][i] =
17927 appData.fischerCastling ? NoRights : initialRights[i];
17928 } /* assume possible unless obviously impossible */
17929 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17930 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17931 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17932 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17933 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17934 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17935 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17936 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17939 while(*p==' ') p++;
17940 if(nrCastlingRights) {
17942 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17943 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17944 /* castling indicator present, so default becomes no castlings */
17945 for(i=0; i<nrCastlingRights; i++ ) {
17946 board[CASTLING][i] = NoRights;
17949 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17950 (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
17951 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17952 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
17953 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17955 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17956 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17957 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
17959 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17960 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17961 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17962 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17963 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17964 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17967 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17968 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17969 board[CASTLING][2] = whiteKingFile;
17970 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17971 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17972 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
17975 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17976 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17977 board[CASTLING][2] = whiteKingFile;
17978 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17979 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17980 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
17983 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17984 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17985 board[CASTLING][5] = blackKingFile;
17986 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17987 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17988 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
17991 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17992 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17993 board[CASTLING][5] = blackKingFile;
17994 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17995 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17996 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
17999 default: /* FRC castlings */
18000 if(c >= 'a') { /* black rights */
18001 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18002 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18003 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18004 if(i == BOARD_RGHT) break;
18005 board[CASTLING][5] = i;
18007 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
18008 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
18010 board[CASTLING][3] = c;
18012 board[CASTLING][4] = c;
18013 } else { /* white rights */
18014 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18015 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18016 if(board[0][i] == WhiteKing) break;
18017 if(i == BOARD_RGHT) break;
18018 board[CASTLING][2] = i;
18019 c -= AAA - 'a' + 'A';
18020 if(board[0][c] >= WhiteKing) break;
18022 board[CASTLING][0] = c;
18024 board[CASTLING][1] = c;
18028 for(i=0; i<nrCastlingRights; i++)
18029 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18030 if(gameInfo.variant == VariantSChess)
18031 for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18032 if(fischer && shuffle) appData.fischerCastling = TRUE;
18033 if (appData.debugMode) {
18034 fprintf(debugFP, "FEN castling rights:");
18035 for(i=0; i<nrCastlingRights; i++)
18036 fprintf(debugFP, " %d", board[CASTLING][i]);
18037 fprintf(debugFP, "\n");
18040 while(*p==' ') p++;
18043 if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18045 /* read e.p. field in games that know e.p. capture */
18046 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
18047 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18048 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18050 p++; board[EP_STATUS] = EP_NONE;
18052 char c = *p++ - AAA;
18054 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18055 if(*p >= '0' && *p <='9') p++;
18056 board[EP_STATUS] = c;
18061 if(sscanf(p, "%d", &i) == 1) {
18062 FENrulePlies = i; /* 50-move ply counter */
18063 /* (The move number is still ignored) */
18070 EditPositionPasteFEN (char *fen)
18073 Board initial_position;
18075 if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18076 DisplayError(_("Bad FEN position in clipboard"), 0);
18079 int savedBlackPlaysFirst = blackPlaysFirst;
18080 EditPositionEvent();
18081 blackPlaysFirst = savedBlackPlaysFirst;
18082 CopyBoard(boards[0], initial_position);
18083 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18084 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18085 DisplayBothClocks();
18086 DrawPosition(FALSE, boards[currentMove]);
18091 static char cseq[12] = "\\ ";
18094 set_cont_sequence (char *new_seq)
18099 // handle bad attempts to set the sequence
18101 return 0; // acceptable error - no debug
18103 len = strlen(new_seq);
18104 ret = (len > 0) && (len < sizeof(cseq));
18106 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18107 else if (appData.debugMode)
18108 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18113 reformat a source message so words don't cross the width boundary. internal
18114 newlines are not removed. returns the wrapped size (no null character unless
18115 included in source message). If dest is NULL, only calculate the size required
18116 for the dest buffer. lp argument indicats line position upon entry, and it's
18117 passed back upon exit.
18120 wrap (char *dest, char *src, int count, int width, int *lp)
18122 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18124 cseq_len = strlen(cseq);
18125 old_line = line = *lp;
18126 ansi = len = clen = 0;
18128 for (i=0; i < count; i++)
18130 if (src[i] == '\033')
18133 // if we hit the width, back up
18134 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18136 // store i & len in case the word is too long
18137 old_i = i, old_len = len;
18139 // find the end of the last word
18140 while (i && src[i] != ' ' && src[i] != '\n')
18146 // word too long? restore i & len before splitting it
18147 if ((old_i-i+clen) >= width)
18154 if (i && src[i-1] == ' ')
18157 if (src[i] != ' ' && src[i] != '\n')
18164 // now append the newline and continuation sequence
18169 strncpy(dest+len, cseq, cseq_len);
18177 dest[len] = src[i];
18181 if (src[i] == '\n')
18186 if (dest && appData.debugMode)
18188 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18189 count, width, line, len, *lp);
18190 show_bytes(debugFP, src, count);
18191 fprintf(debugFP, "\ndest: ");
18192 show_bytes(debugFP, dest, len);
18193 fprintf(debugFP, "\n");
18195 *lp = dest ? line : old_line;
18200 // [HGM] vari: routines for shelving variations
18201 Boolean modeRestore = FALSE;
18204 PushInner (int firstMove, int lastMove)
18206 int i, j, nrMoves = lastMove - firstMove;
18208 // push current tail of game on stack
18209 savedResult[storedGames] = gameInfo.result;
18210 savedDetails[storedGames] = gameInfo.resultDetails;
18211 gameInfo.resultDetails = NULL;
18212 savedFirst[storedGames] = firstMove;
18213 savedLast [storedGames] = lastMove;
18214 savedFramePtr[storedGames] = framePtr;
18215 framePtr -= nrMoves; // reserve space for the boards
18216 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18217 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18218 for(j=0; j<MOVE_LEN; j++)
18219 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18220 for(j=0; j<2*MOVE_LEN; j++)
18221 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18222 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18223 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18224 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18225 pvInfoList[firstMove+i-1].depth = 0;
18226 commentList[framePtr+i] = commentList[firstMove+i];
18227 commentList[firstMove+i] = NULL;
18231 forwardMostMove = firstMove; // truncate game so we can start variation
18235 PushTail (int firstMove, int lastMove)
18237 if(appData.icsActive) { // only in local mode
18238 forwardMostMove = currentMove; // mimic old ICS behavior
18241 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18243 PushInner(firstMove, lastMove);
18244 if(storedGames == 1) GreyRevert(FALSE);
18245 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18249 PopInner (Boolean annotate)
18252 char buf[8000], moveBuf[20];
18254 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18255 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18256 nrMoves = savedLast[storedGames] - currentMove;
18259 if(!WhiteOnMove(currentMove))
18260 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18261 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18262 for(i=currentMove; i<forwardMostMove; i++) {
18264 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18265 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18266 strcat(buf, moveBuf);
18267 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18268 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18272 for(i=1; i<=nrMoves; i++) { // copy last variation back
18273 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18274 for(j=0; j<MOVE_LEN; j++)
18275 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18276 for(j=0; j<2*MOVE_LEN; j++)
18277 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18278 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18279 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18280 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18281 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18282 commentList[currentMove+i] = commentList[framePtr+i];
18283 commentList[framePtr+i] = NULL;
18285 if(annotate) AppendComment(currentMove+1, buf, FALSE);
18286 framePtr = savedFramePtr[storedGames];
18287 gameInfo.result = savedResult[storedGames];
18288 if(gameInfo.resultDetails != NULL) {
18289 free(gameInfo.resultDetails);
18291 gameInfo.resultDetails = savedDetails[storedGames];
18292 forwardMostMove = currentMove + nrMoves;
18296 PopTail (Boolean annotate)
18298 if(appData.icsActive) return FALSE; // only in local mode
18299 if(!storedGames) return FALSE; // sanity
18300 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18302 PopInner(annotate);
18303 if(currentMove < forwardMostMove) ForwardEvent(); else
18304 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18306 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18312 { // remove all shelved variations
18314 for(i=0; i<storedGames; i++) {
18315 if(savedDetails[i])
18316 free(savedDetails[i]);
18317 savedDetails[i] = NULL;
18319 for(i=framePtr; i<MAX_MOVES; i++) {
18320 if(commentList[i]) free(commentList[i]);
18321 commentList[i] = NULL;
18323 framePtr = MAX_MOVES-1;
18328 LoadVariation (int index, char *text)
18329 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18330 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18331 int level = 0, move;
18333 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18334 // first find outermost bracketing variation
18335 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18336 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18337 if(*p == '{') wait = '}'; else
18338 if(*p == '[') wait = ']'; else
18339 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18340 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18342 if(*p == wait) wait = NULLCHAR; // closing ]} found
18345 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18346 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18347 end[1] = NULLCHAR; // clip off comment beyond variation
18348 ToNrEvent(currentMove-1);
18349 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18350 // kludge: use ParsePV() to append variation to game
18351 move = currentMove;
18352 ParsePV(start, TRUE, TRUE);
18353 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18354 ClearPremoveHighlights();
18356 ToNrEvent(currentMove+1);
18362 char *p, *q, buf[MSG_SIZ];
18363 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18364 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18365 ParseArgsFromString(buf);
18366 ActivateTheme(TRUE); // also redo colors
18370 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18373 q = appData.themeNames;
18374 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18375 if(appData.useBitmaps) {
18376 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18377 appData.liteBackTextureFile, appData.darkBackTextureFile,
18378 appData.liteBackTextureMode,
18379 appData.darkBackTextureMode );
18381 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18382 Col2Text(2), // lightSquareColor
18383 Col2Text(3) ); // darkSquareColor
18385 if(appData.useBorder) {
18386 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18389 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18391 if(appData.useFont) {
18392 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18393 appData.renderPiecesWithFont,
18394 appData.fontToPieceTable,
18395 Col2Text(9), // appData.fontBackColorWhite
18396 Col2Text(10) ); // appData.fontForeColorBlack
18398 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18399 appData.pieceDirectory);
18400 if(!appData.pieceDirectory[0])
18401 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18402 Col2Text(0), // whitePieceColor
18403 Col2Text(1) ); // blackPieceColor
18405 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18406 Col2Text(4), // highlightSquareColor
18407 Col2Text(5) ); // premoveHighlightColor
18408 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18409 if(insert != q) insert[-1] = NULLCHAR;
18410 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18413 ActivateTheme(FALSE);