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 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
299 /* States for ics_getting_history */
301 #define H_REQUESTED 1
302 #define H_GOT_REQ_HEADER 2
303 #define H_GOT_UNREQ_HEADER 3
304 #define H_GETTING_MOVES 4
305 #define H_GOT_UNWANTED_HEADER 5
307 /* whosays values for GameEnds */
316 /* Maximum number of games in a cmail message */
317 #define CMAIL_MAX_GAMES 20
319 /* Different types of move when calling RegisterMove */
321 #define CMAIL_RESIGN 1
323 #define CMAIL_ACCEPT 3
325 /* Different types of result to remember for each game */
326 #define CMAIL_NOT_RESULT 0
327 #define CMAIL_OLD_RESULT 1
328 #define CMAIL_NEW_RESULT 2
330 /* Telnet protocol constants */
341 safeStrCpy (char *dst, const char *src, size_t count)
344 assert( dst != NULL );
345 assert( src != NULL );
348 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
349 if( i == count && dst[count-1] != NULLCHAR)
351 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
352 if(appData.debugMode)
353 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
359 /* Some compiler can't cast u64 to double
360 * This function do the job for us:
362 * We use the highest bit for cast, this only
363 * works if the highest bit is not
364 * in use (This should not happen)
366 * We used this for all compiler
369 u64ToDouble (u64 value)
372 u64 tmp = value & u64Const(0x7fffffffffffffff);
373 r = (double)(s64)tmp;
374 if (value & u64Const(0x8000000000000000))
375 r += 9.2233720368547758080e18; /* 2^63 */
379 /* Fake up flags for now, as we aren't keeping track of castling
380 availability yet. [HGM] Change of logic: the flag now only
381 indicates the type of castlings allowed by the rule of the game.
382 The actual rights themselves are maintained in the array
383 castlingRights, as part of the game history, and are not probed
389 int flags = F_ALL_CASTLE_OK;
390 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
391 switch (gameInfo.variant) {
393 flags &= ~F_ALL_CASTLE_OK;
394 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
395 flags |= F_IGNORE_CHECK;
397 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
400 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
402 case VariantKriegspiel:
403 flags |= F_KRIEGSPIEL_CAPTURE;
405 case VariantCapaRandom:
406 case VariantFischeRandom:
407 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
408 case VariantNoCastle:
409 case VariantShatranj:
414 flags &= ~F_ALL_CASTLE_OK;
419 if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
423 FILE *gameFileFP, *debugFP, *serverFP;
424 char *currentDebugFile; // [HGM] debug split: to remember name
427 [AS] Note: sometimes, the sscanf() function is used to parse the input
428 into a fixed-size buffer. Because of this, we must be prepared to
429 receive strings as long as the size of the input buffer, which is currently
430 set to 4K for Windows and 8K for the rest.
431 So, we must either allocate sufficiently large buffers here, or
432 reduce the size of the input buffer in the input reading part.
435 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
436 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
437 char thinkOutput1[MSG_SIZ*10];
439 ChessProgramState first, second, pairing;
441 /* premove variables */
444 int premoveFromX = 0;
445 int premoveFromY = 0;
446 int premovePromoChar = 0;
448 Boolean alarmSounded;
449 /* end premove variables */
451 char *ics_prefix = "$";
452 enum ICS_TYPE ics_type = ICS_GENERIC;
454 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
455 int pauseExamForwardMostMove = 0;
456 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
457 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
458 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
459 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
460 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
461 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
462 int whiteFlag = FALSE, blackFlag = FALSE;
463 int userOfferedDraw = FALSE;
464 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
465 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
466 int cmailMoveType[CMAIL_MAX_GAMES];
467 long ics_clock_paused = 0;
468 ProcRef icsPR = NoProc, cmailPR = NoProc;
469 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
470 GameMode gameMode = BeginningOfGame;
471 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
472 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
473 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
474 int hiddenThinkOutputState = 0; /* [AS] */
475 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
476 int adjudicateLossPlies = 6;
477 char white_holding[64], black_holding[64];
478 TimeMark lastNodeCountTime;
479 long lastNodeCount=0;
480 int shiftKey, controlKey; // [HGM] set by mouse handler
482 int have_sent_ICS_logon = 0;
484 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
485 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
486 Boolean adjustedClock;
487 long timeControl_2; /* [AS] Allow separate time controls */
488 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
489 long timeRemaining[2][MAX_MOVES];
490 int matchGame = 0, nextGame = 0, roundNr = 0;
491 Boolean waitingForGame = FALSE, startingEngine = FALSE;
492 TimeMark programStartTime, pauseStart;
493 char ics_handle[MSG_SIZ];
494 int have_set_title = 0;
496 /* animateTraining preserves the state of appData.animate
497 * when Training mode is activated. This allows the
498 * response to be animated when appData.animate == TRUE and
499 * appData.animateDragging == TRUE.
501 Boolean animateTraining;
507 Board boards[MAX_MOVES];
508 /* [HGM] Following 7 needed for accurate legality tests: */
509 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
510 signed char initialRights[BOARD_FILES];
511 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
512 int initialRulePlies, FENrulePlies;
513 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
515 Boolean shuffleOpenings;
516 int mute; // mute all sounds
518 // [HGM] vari: next 12 to save and restore variations
519 #define MAX_VARIATIONS 10
520 int framePtr = MAX_MOVES-1; // points to free stack entry
522 int savedFirst[MAX_VARIATIONS];
523 int savedLast[MAX_VARIATIONS];
524 int savedFramePtr[MAX_VARIATIONS];
525 char *savedDetails[MAX_VARIATIONS];
526 ChessMove savedResult[MAX_VARIATIONS];
528 void PushTail P((int firstMove, int lastMove));
529 Boolean PopTail P((Boolean annotate));
530 void PushInner P((int firstMove, int lastMove));
531 void PopInner P((Boolean annotate));
532 void CleanupTail P((void));
534 ChessSquare FIDEArray[2][BOARD_FILES] = {
535 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
538 BlackKing, BlackBishop, BlackKnight, BlackRook }
541 ChessSquare twoKingsArray[2][BOARD_FILES] = {
542 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
543 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
544 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
545 BlackKing, BlackKing, BlackKnight, BlackRook }
548 ChessSquare KnightmateArray[2][BOARD_FILES] = {
549 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
550 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
551 { BlackRook, BlackMan, BlackBishop, BlackQueen,
552 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
555 ChessSquare SpartanArray[2][BOARD_FILES] = {
556 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
557 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
558 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
559 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
562 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
563 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
564 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
565 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
566 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
569 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
570 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
571 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
572 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
573 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
576 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
577 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
578 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
579 { BlackRook, BlackKnight, BlackMan, BlackFerz,
580 BlackKing, BlackMan, BlackKnight, BlackRook }
583 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
584 { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
585 WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
586 { BlackRook, BlackKnight, BlackMan, BlackFerz,
587 BlackKing, BlackMan, BlackKnight, BlackRook }
590 ChessSquare lionArray[2][BOARD_FILES] = {
591 { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
592 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
593 { BlackRook, BlackLion, BlackBishop, BlackQueen,
594 BlackKing, BlackBishop, BlackKnight, BlackRook }
598 #if (BOARD_FILES>=10)
599 ChessSquare ShogiArray[2][BOARD_FILES] = {
600 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
601 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
602 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
603 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
606 ChessSquare XiangqiArray[2][BOARD_FILES] = {
607 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
608 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
609 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
610 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
613 ChessSquare CapablancaArray[2][BOARD_FILES] = {
614 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
615 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
616 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
617 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
620 ChessSquare GreatArray[2][BOARD_FILES] = {
621 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
622 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
623 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
624 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
627 ChessSquare JanusArray[2][BOARD_FILES] = {
628 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
629 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
630 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
631 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
634 ChessSquare GrandArray[2][BOARD_FILES] = {
635 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
636 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
637 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
638 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
641 ChessSquare ChuChessArray[2][BOARD_FILES] = {
642 { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
643 WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
644 { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
645 BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
649 ChessSquare GothicArray[2][BOARD_FILES] = {
650 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
651 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
652 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
653 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
656 #define GothicArray CapablancaArray
660 ChessSquare FalconArray[2][BOARD_FILES] = {
661 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
662 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
663 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
664 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
667 #define FalconArray CapablancaArray
670 #else // !(BOARD_FILES>=10)
671 #define XiangqiPosition FIDEArray
672 #define CapablancaArray FIDEArray
673 #define GothicArray FIDEArray
674 #define GreatArray FIDEArray
675 #endif // !(BOARD_FILES>=10)
677 #if (BOARD_FILES>=12)
678 ChessSquare CourierArray[2][BOARD_FILES] = {
679 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
680 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
681 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
682 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
684 ChessSquare ChuArray[6][BOARD_FILES] = {
685 { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
686 WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
687 { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
688 BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
689 { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
690 WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
691 { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
692 BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
693 { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
694 WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
695 { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
696 BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
698 #else // !(BOARD_FILES>=12)
699 #define CourierArray CapablancaArray
700 #define ChuArray CapablancaArray
701 #endif // !(BOARD_FILES>=12)
704 Board initialPosition;
707 /* Convert str to a rating. Checks for special cases of "----",
709 "++++", etc. Also strips ()'s */
711 string_to_rating (char *str)
713 while(*str && !isdigit(*str)) ++str;
715 return 0; /* One of the special "no rating" cases */
723 /* Init programStats */
724 programStats.movelist[0] = 0;
725 programStats.depth = 0;
726 programStats.nr_moves = 0;
727 programStats.moves_left = 0;
728 programStats.nodes = 0;
729 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
730 programStats.score = 0;
731 programStats.got_only_move = 0;
732 programStats.got_fail = 0;
733 programStats.line_is_book = 0;
738 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
739 if (appData.firstPlaysBlack) {
740 first.twoMachinesColor = "black\n";
741 second.twoMachinesColor = "white\n";
743 first.twoMachinesColor = "white\n";
744 second.twoMachinesColor = "black\n";
747 first.other = &second;
748 second.other = &first;
751 if(appData.timeOddsMode) {
752 norm = appData.timeOdds[0];
753 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
755 first.timeOdds = appData.timeOdds[0]/norm;
756 second.timeOdds = appData.timeOdds[1]/norm;
759 if(programVersion) free(programVersion);
760 if (appData.noChessProgram) {
761 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
762 sprintf(programVersion, "%s", PACKAGE_STRING);
764 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
765 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
766 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
771 UnloadEngine (ChessProgramState *cps)
773 /* Kill off first chess program */
774 if (cps->isr != NULL)
775 RemoveInputSource(cps->isr);
778 if (cps->pr != NoProc) {
780 DoSleep( appData.delayBeforeQuit );
781 SendToProgram("quit\n", cps);
782 DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
785 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
789 ClearOptions (ChessProgramState *cps)
792 cps->nrOptions = cps->comboCnt = 0;
793 for(i=0; i<MAX_OPTIONS; i++) {
794 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
795 cps->option[i].textValue = 0;
799 char *engineNames[] = {
800 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
801 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
803 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
804 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
809 InitEngine (ChessProgramState *cps, int n)
810 { // [HGM] all engine initialiation put in a function that does one engine
814 cps->which = engineNames[n];
815 cps->maybeThinking = FALSE;
819 cps->sendDrawOffers = 1;
821 cps->program = appData.chessProgram[n];
822 cps->host = appData.host[n];
823 cps->dir = appData.directory[n];
824 cps->initString = appData.engInitString[n];
825 cps->computerString = appData.computerString[n];
826 cps->useSigint = TRUE;
827 cps->useSigterm = TRUE;
828 cps->reuse = appData.reuse[n];
829 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
830 cps->useSetboard = FALSE;
832 cps->usePing = FALSE;
835 cps->usePlayother = FALSE;
836 cps->useColors = TRUE;
837 cps->useUsermove = FALSE;
838 cps->sendICS = FALSE;
839 cps->sendName = appData.icsActive;
840 cps->sdKludge = FALSE;
841 cps->stKludge = FALSE;
842 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
843 TidyProgramName(cps->program, cps->host, cps->tidy);
845 ASSIGN(cps->variants, appData.variant);
846 cps->analysisSupport = 2; /* detect */
847 cps->analyzing = FALSE;
848 cps->initDone = FALSE;
850 cps->pseudo = appData.pseudo[n];
852 /* New features added by Tord: */
853 cps->useFEN960 = FALSE;
854 cps->useOOCastle = TRUE;
855 /* End of new features added by Tord. */
856 cps->fenOverride = appData.fenOverride[n];
858 /* [HGM] time odds: set factor for each machine */
859 cps->timeOdds = appData.timeOdds[n];
861 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
862 cps->accumulateTC = appData.accumulateTC[n];
863 cps->maxNrOfSessions = 1;
868 cps->drawDepth = appData.drawDepth[n];
869 cps->supportsNPS = UNKNOWN;
870 cps->memSize = FALSE;
871 cps->maxCores = FALSE;
872 ASSIGN(cps->egtFormats, "");
875 cps->optionSettings = appData.engOptions[n];
877 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
878 cps->isUCI = appData.isUCI[n]; /* [AS] */
879 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
882 if (appData.protocolVersion[n] > PROTOVER
883 || appData.protocolVersion[n] < 1)
888 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
889 appData.protocolVersion[n]);
890 if( (len >= MSG_SIZ) && appData.debugMode )
891 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
893 DisplayFatalError(buf, 0, 2);
897 cps->protocolVersion = appData.protocolVersion[n];
900 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
901 ParseFeatures(appData.featureDefaults, cps);
904 ChessProgramState *savCps;
912 if(WaitForEngine(savCps, LoadEngine)) return;
913 CommonEngineInit(); // recalculate time odds
914 if(gameInfo.variant != StringToVariant(appData.variant)) {
915 // we changed variant when loading the engine; this forces us to reset
916 Reset(TRUE, savCps != &first);
917 oldMode = BeginningOfGame; // to prevent restoring old mode
919 InitChessProgram(savCps, FALSE);
920 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
921 DisplayMessage("", "");
922 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
923 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
926 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
930 ReplaceEngine (ChessProgramState *cps, int n)
932 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
934 if(oldMode != BeginningOfGame) EditGameEvent();
937 appData.noChessProgram = FALSE;
938 appData.clockMode = TRUE;
941 if(n) return; // only startup first engine immediately; second can wait
942 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
946 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
947 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
949 static char resetOptions[] =
950 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
951 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
952 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
953 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
956 FloatToFront(char **list, char *engineLine)
958 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
960 if(appData.recentEngines <= 0) return;
961 TidyProgramName(engineLine, "localhost", tidy+1);
962 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
963 strncpy(buf+1, *list, MSG_SIZ-50);
964 if(p = strstr(buf, tidy)) { // tidy name appears in list
965 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
966 while(*p++ = *++q); // squeeze out
968 strcat(tidy, buf+1); // put list behind tidy name
969 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
970 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
971 ASSIGN(*list, tidy+1);
974 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
977 Load (ChessProgramState *cps, int i)
979 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
980 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
981 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
982 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
983 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
984 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
985 appData.firstProtocolVersion = PROTOVER;
986 ParseArgsFromString(buf);
988 ReplaceEngine(cps, i);
989 FloatToFront(&appData.recentEngineList, engineLine);
993 while(q = strchr(p, SLASH)) p = q+1;
994 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
995 if(engineDir[0] != NULLCHAR) {
996 ASSIGN(appData.directory[i], engineDir); p = engineName;
997 } else if(p != engineName) { // derive directory from engine path, when not given
999 ASSIGN(appData.directory[i], engineName);
1001 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1002 } else { ASSIGN(appData.directory[i], "."); }
1003 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1005 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1006 snprintf(command, MSG_SIZ, "%s %s", p, params);
1009 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1010 ASSIGN(appData.chessProgram[i], p);
1011 appData.isUCI[i] = isUCI;
1012 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1013 appData.hasOwnBookUCI[i] = hasBook;
1014 if(!nickName[0]) useNick = FALSE;
1015 if(useNick) ASSIGN(appData.pgnName[i], nickName);
1019 q = firstChessProgramNames;
1020 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1021 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1022 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1023 quote, p, quote, appData.directory[i],
1024 useNick ? " -fn \"" : "",
1025 useNick ? nickName : "",
1026 useNick ? "\"" : "",
1027 v1 ? " -firstProtocolVersion 1" : "",
1028 hasBook ? "" : " -fNoOwnBookUCI",
1029 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1030 storeVariant ? " -variant " : "",
1031 storeVariant ? VariantName(gameInfo.variant) : "");
1032 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1033 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1034 if(insert != q) insert[-1] = NULLCHAR;
1035 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1037 FloatToFront(&appData.recentEngineList, buf);
1039 ReplaceEngine(cps, i);
1045 int matched, min, sec;
1047 * Parse timeControl resource
1049 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1050 appData.movesPerSession)) {
1052 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1053 DisplayFatalError(buf, 0, 2);
1057 * Parse searchTime resource
1059 if (*appData.searchTime != NULLCHAR) {
1060 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1062 searchTime = min * 60;
1063 } else if (matched == 2) {
1064 searchTime = min * 60 + sec;
1067 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1068 DisplayFatalError(buf, 0, 2);
1077 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1078 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1080 GetTimeMark(&programStartTime);
1081 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1082 appData.seedBase = random() + (random()<<15);
1083 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1085 ClearProgramStats();
1086 programStats.ok_to_send = 1;
1087 programStats.seen_stat = 0;
1090 * Initialize game list
1096 * Internet chess server status
1098 if (appData.icsActive) {
1099 appData.matchMode = FALSE;
1100 appData.matchGames = 0;
1102 appData.noChessProgram = !appData.zippyPlay;
1104 appData.zippyPlay = FALSE;
1105 appData.zippyTalk = FALSE;
1106 appData.noChessProgram = TRUE;
1108 if (*appData.icsHelper != NULLCHAR) {
1109 appData.useTelnet = TRUE;
1110 appData.telnetProgram = appData.icsHelper;
1113 appData.zippyTalk = appData.zippyPlay = FALSE;
1116 /* [AS] Initialize pv info list [HGM] and game state */
1120 for( i=0; i<=framePtr; i++ ) {
1121 pvInfoList[i].depth = -1;
1122 boards[i][EP_STATUS] = EP_NONE;
1123 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1129 /* [AS] Adjudication threshold */
1130 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1132 InitEngine(&first, 0);
1133 InitEngine(&second, 1);
1136 pairing.which = "pairing"; // pairing engine
1137 pairing.pr = NoProc;
1139 pairing.program = appData.pairingEngine;
1140 pairing.host = "localhost";
1143 if (appData.icsActive) {
1144 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1145 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1146 appData.clockMode = FALSE;
1147 first.sendTime = second.sendTime = 0;
1151 /* Override some settings from environment variables, for backward
1152 compatibility. Unfortunately it's not feasible to have the env
1153 vars just set defaults, at least in xboard. Ugh.
1155 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1160 if (!appData.icsActive) {
1164 /* Check for variants that are supported only in ICS mode,
1165 or not at all. Some that are accepted here nevertheless
1166 have bugs; see comments below.
1168 VariantClass variant = StringToVariant(appData.variant);
1170 case VariantBughouse: /* need four players and two boards */
1171 case VariantKriegspiel: /* need to hide pieces and move details */
1172 /* case VariantFischeRandom: (Fabien: moved below) */
1173 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1174 if( (len >= MSG_SIZ) && appData.debugMode )
1175 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1177 DisplayFatalError(buf, 0, 2);
1180 case VariantUnknown:
1181 case VariantLoadable:
1191 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1192 if( (len >= MSG_SIZ) && appData.debugMode )
1193 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1195 DisplayFatalError(buf, 0, 2);
1198 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1199 case VariantFairy: /* [HGM] TestLegality definitely off! */
1200 case VariantGothic: /* [HGM] should work */
1201 case VariantCapablanca: /* [HGM] should work */
1202 case VariantCourier: /* [HGM] initial forced moves not implemented */
1203 case VariantShogi: /* [HGM] could still mate with pawn drop */
1204 case VariantChu: /* [HGM] experimental */
1205 case VariantKnightmate: /* [HGM] should work */
1206 case VariantCylinder: /* [HGM] untested */
1207 case VariantFalcon: /* [HGM] untested */
1208 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1209 offboard interposition not understood */
1210 case VariantNormal: /* definitely works! */
1211 case VariantWildCastle: /* pieces not automatically shuffled */
1212 case VariantNoCastle: /* pieces not automatically shuffled */
1213 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1214 case VariantLosers: /* should work except for win condition,
1215 and doesn't know captures are mandatory */
1216 case VariantSuicide: /* should work except for win condition,
1217 and doesn't know captures are mandatory */
1218 case VariantGiveaway: /* should work except for win condition,
1219 and doesn't know captures are mandatory */
1220 case VariantTwoKings: /* should work */
1221 case VariantAtomic: /* should work except for win condition */
1222 case Variant3Check: /* should work except for win condition */
1223 case VariantShatranj: /* should work except for all win conditions */
1224 case VariantMakruk: /* should work except for draw countdown */
1225 case VariantASEAN : /* should work except for draw countdown */
1226 case VariantBerolina: /* might work if TestLegality is off */
1227 case VariantCapaRandom: /* should work */
1228 case VariantJanus: /* should work */
1229 case VariantSuper: /* experimental */
1230 case VariantGreat: /* experimental, requires legality testing to be off */
1231 case VariantSChess: /* S-Chess, should work */
1232 case VariantGrand: /* should work */
1233 case VariantSpartan: /* should work */
1234 case VariantLion: /* should work */
1235 case VariantChuChess: /* should work */
1243 NextIntegerFromString (char ** str, long * value)
1248 while( *s == ' ' || *s == '\t' ) {
1254 if( *s >= '0' && *s <= '9' ) {
1255 while( *s >= '0' && *s <= '9' ) {
1256 *value = *value * 10 + (*s - '0');
1269 NextTimeControlFromString (char ** str, long * value)
1272 int result = NextIntegerFromString( str, &temp );
1275 *value = temp * 60; /* Minutes */
1276 if( **str == ':' ) {
1278 result = NextIntegerFromString( str, &temp );
1279 *value += temp; /* Seconds */
1287 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1288 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1289 int result = -1, type = 0; long temp, temp2;
1291 if(**str != ':') return -1; // old params remain in force!
1293 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1294 if( NextIntegerFromString( str, &temp ) ) return -1;
1295 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1298 /* time only: incremental or sudden-death time control */
1299 if(**str == '+') { /* increment follows; read it */
1301 if(**str == '!') type = *(*str)++; // Bronstein TC
1302 if(result = NextIntegerFromString( str, &temp2)) return -1;
1303 *inc = temp2 * 1000;
1304 if(**str == '.') { // read fraction of increment
1305 char *start = ++(*str);
1306 if(result = NextIntegerFromString( str, &temp2)) return -1;
1308 while(start++ < *str) temp2 /= 10;
1312 *moves = 0; *tc = temp * 1000; *incType = type;
1316 (*str)++; /* classical time control */
1317 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1329 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1330 { /* [HGM] get time to add from the multi-session time-control string */
1331 int incType, moves=1; /* kludge to force reading of first session */
1332 long time, increment;
1335 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1337 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1338 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1339 if(movenr == -1) return time; /* last move before new session */
1340 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1341 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1342 if(!moves) return increment; /* current session is incremental */
1343 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1344 } while(movenr >= -1); /* try again for next session */
1346 return 0; // no new time quota on this move
1350 ParseTimeControl (char *tc, float ti, int mps)
1354 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1357 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1358 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1359 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1363 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1365 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1368 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1370 snprintf(buf, MSG_SIZ, ":%s", mytc);
1372 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1374 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1379 /* Parse second time control */
1382 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1390 timeControl_2 = tc2 * 1000;
1400 timeControl = tc1 * 1000;
1403 timeIncrement = ti * 1000; /* convert to ms */
1404 movesPerSession = 0;
1407 movesPerSession = mps;
1415 if (appData.debugMode) {
1416 # ifdef __GIT_VERSION
1417 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1419 fprintf(debugFP, "Version: %s\n", programVersion);
1422 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1424 set_cont_sequence(appData.wrapContSeq);
1425 if (appData.matchGames > 0) {
1426 appData.matchMode = TRUE;
1427 } else if (appData.matchMode) {
1428 appData.matchGames = 1;
1430 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1431 appData.matchGames = appData.sameColorGames;
1432 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1433 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1434 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1437 if (appData.noChessProgram || first.protocolVersion == 1) {
1440 /* kludge: allow timeout for initial "feature" commands */
1442 DisplayMessage("", _("Starting chess program"));
1443 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1448 CalculateIndex (int index, int gameNr)
1449 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1451 if(index > 0) return index; // fixed nmber
1452 if(index == 0) return 1;
1453 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1454 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1459 LoadGameOrPosition (int gameNr)
1460 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1461 if (*appData.loadGameFile != NULLCHAR) {
1462 if (!LoadGameFromFile(appData.loadGameFile,
1463 CalculateIndex(appData.loadGameIndex, gameNr),
1464 appData.loadGameFile, FALSE)) {
1465 DisplayFatalError(_("Bad game file"), 0, 1);
1468 } else if (*appData.loadPositionFile != NULLCHAR) {
1469 if (!LoadPositionFromFile(appData.loadPositionFile,
1470 CalculateIndex(appData.loadPositionIndex, gameNr),
1471 appData.loadPositionFile)) {
1472 DisplayFatalError(_("Bad position file"), 0, 1);
1480 ReserveGame (int gameNr, char resChar)
1482 FILE *tf = fopen(appData.tourneyFile, "r+");
1483 char *p, *q, c, buf[MSG_SIZ];
1484 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1485 safeStrCpy(buf, lastMsg, MSG_SIZ);
1486 DisplayMessage(_("Pick new game"), "");
1487 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1488 ParseArgsFromFile(tf);
1489 p = q = appData.results;
1490 if(appData.debugMode) {
1491 char *r = appData.participants;
1492 fprintf(debugFP, "results = '%s'\n", p);
1493 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1494 fprintf(debugFP, "\n");
1496 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1498 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1499 safeStrCpy(q, p, strlen(p) + 2);
1500 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1501 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1502 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1503 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1506 fseek(tf, -(strlen(p)+4), SEEK_END);
1508 if(c != '"') // depending on DOS or Unix line endings we can be one off
1509 fseek(tf, -(strlen(p)+2), SEEK_END);
1510 else fseek(tf, -(strlen(p)+3), SEEK_END);
1511 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1512 DisplayMessage(buf, "");
1513 free(p); appData.results = q;
1514 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1515 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1516 int round = appData.defaultMatchGames * appData.tourneyType;
1517 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1518 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1519 UnloadEngine(&first); // next game belongs to other pairing;
1520 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1522 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1526 MatchEvent (int mode)
1527 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1529 if(matchMode) { // already in match mode: switch it off
1531 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1534 // if(gameMode != BeginningOfGame) {
1535 // DisplayError(_("You can only start a match from the initial position."), 0);
1539 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1540 /* Set up machine vs. machine match */
1542 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1543 if(appData.tourneyFile[0]) {
1545 if(nextGame > appData.matchGames) {
1547 if(strchr(appData.results, '*') == NULL) {
1549 appData.tourneyCycles++;
1550 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1552 NextTourneyGame(-1, &dummy);
1554 if(nextGame <= appData.matchGames) {
1555 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1557 ScheduleDelayedEvent(NextMatchGame, 10000);
1562 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1563 DisplayError(buf, 0);
1564 appData.tourneyFile[0] = 0;
1568 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1569 DisplayFatalError(_("Can't have a match with no chess programs"),
1574 matchGame = roundNr = 1;
1575 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1579 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1582 InitBackEnd3 P((void))
1584 GameMode initialMode;
1588 if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode && // mode involves only first engine
1589 !strcmp(appData.variant, "normal") && // no explicit variant request
1590 appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 && // no size overrides requested
1591 !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") && // but 'normal' won't work with engine
1592 !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1593 char c, *q = first.variants, *p = strchr(q, ',');
1594 if(p) *p = NULLCHAR;
1595 if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1597 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1598 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1599 ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1600 Reset(TRUE, FALSE); // and re-initialize
1605 InitChessProgram(&first, startedFromSetupPosition);
1607 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1608 free(programVersion);
1609 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1610 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1611 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1614 if (appData.icsActive) {
1616 /* [DM] Make a console window if needed [HGM] merged ifs */
1622 if (*appData.icsCommPort != NULLCHAR)
1623 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1624 appData.icsCommPort);
1626 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1627 appData.icsHost, appData.icsPort);
1629 if( (len >= MSG_SIZ) && appData.debugMode )
1630 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1632 DisplayFatalError(buf, err, 1);
1637 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1639 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1640 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1641 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1642 } else if (appData.noChessProgram) {
1648 if (*appData.cmailGameName != NULLCHAR) {
1650 OpenLoopback(&cmailPR);
1652 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1656 DisplayMessage("", "");
1657 if (StrCaseCmp(appData.initialMode, "") == 0) {
1658 initialMode = BeginningOfGame;
1659 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1660 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1661 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1662 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1665 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1666 initialMode = TwoMachinesPlay;
1667 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1668 initialMode = AnalyzeFile;
1669 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1670 initialMode = AnalyzeMode;
1671 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1672 initialMode = MachinePlaysWhite;
1673 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1674 initialMode = MachinePlaysBlack;
1675 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1676 initialMode = EditGame;
1677 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1678 initialMode = EditPosition;
1679 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1680 initialMode = Training;
1682 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1683 if( (len >= MSG_SIZ) && appData.debugMode )
1684 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1686 DisplayFatalError(buf, 0, 2);
1690 if (appData.matchMode) {
1691 if(appData.tourneyFile[0]) { // start tourney from command line
1693 if(f = fopen(appData.tourneyFile, "r")) {
1694 ParseArgsFromFile(f); // make sure tourney parmeters re known
1696 appData.clockMode = TRUE;
1698 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1701 } else if (*appData.cmailGameName != NULLCHAR) {
1702 /* Set up cmail mode */
1703 ReloadCmailMsgEvent(TRUE);
1705 /* Set up other modes */
1706 if (initialMode == AnalyzeFile) {
1707 if (*appData.loadGameFile == NULLCHAR) {
1708 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1712 if (*appData.loadGameFile != NULLCHAR) {
1713 (void) LoadGameFromFile(appData.loadGameFile,
1714 appData.loadGameIndex,
1715 appData.loadGameFile, TRUE);
1716 } else if (*appData.loadPositionFile != NULLCHAR) {
1717 (void) LoadPositionFromFile(appData.loadPositionFile,
1718 appData.loadPositionIndex,
1719 appData.loadPositionFile);
1720 /* [HGM] try to make self-starting even after FEN load */
1721 /* to allow automatic setup of fairy variants with wtm */
1722 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1723 gameMode = BeginningOfGame;
1724 setboardSpoiledMachineBlack = 1;
1726 /* [HGM] loadPos: make that every new game uses the setup */
1727 /* from file as long as we do not switch variant */
1728 if(!blackPlaysFirst) {
1729 startedFromPositionFile = TRUE;
1730 CopyBoard(filePosition, boards[0]);
1733 if (initialMode == AnalyzeMode) {
1734 if (appData.noChessProgram) {
1735 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1738 if (appData.icsActive) {
1739 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1743 } else if (initialMode == AnalyzeFile) {
1744 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1745 ShowThinkingEvent();
1747 AnalysisPeriodicEvent(1);
1748 } else if (initialMode == MachinePlaysWhite) {
1749 if (appData.noChessProgram) {
1750 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1754 if (appData.icsActive) {
1755 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1759 MachineWhiteEvent();
1760 } else if (initialMode == MachinePlaysBlack) {
1761 if (appData.noChessProgram) {
1762 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1766 if (appData.icsActive) {
1767 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1771 MachineBlackEvent();
1772 } else if (initialMode == TwoMachinesPlay) {
1773 if (appData.noChessProgram) {
1774 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1778 if (appData.icsActive) {
1779 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1784 } else if (initialMode == EditGame) {
1786 } else if (initialMode == EditPosition) {
1787 EditPositionEvent();
1788 } else if (initialMode == Training) {
1789 if (*appData.loadGameFile == NULLCHAR) {
1790 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1799 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1801 DisplayBook(current+1);
1803 MoveHistorySet( movelist, first, last, current, pvInfoList );
1805 EvalGraphSet( first, last, current, pvInfoList );
1807 MakeEngineOutputTitle();
1811 * Establish will establish a contact to a remote host.port.
1812 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1813 * used to talk to the host.
1814 * Returns 0 if okay, error code if not.
1821 if (*appData.icsCommPort != NULLCHAR) {
1822 /* Talk to the host through a serial comm port */
1823 return OpenCommPort(appData.icsCommPort, &icsPR);
1825 } else if (*appData.gateway != NULLCHAR) {
1826 if (*appData.remoteShell == NULLCHAR) {
1827 /* Use the rcmd protocol to run telnet program on a gateway host */
1828 snprintf(buf, sizeof(buf), "%s %s %s",
1829 appData.telnetProgram, appData.icsHost, appData.icsPort);
1830 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1833 /* Use the rsh program to run telnet program on a gateway host */
1834 if (*appData.remoteUser == NULLCHAR) {
1835 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1836 appData.gateway, appData.telnetProgram,
1837 appData.icsHost, appData.icsPort);
1839 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1840 appData.remoteShell, appData.gateway,
1841 appData.remoteUser, appData.telnetProgram,
1842 appData.icsHost, appData.icsPort);
1844 return StartChildProcess(buf, "", &icsPR);
1847 } else if (appData.useTelnet) {
1848 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1851 /* TCP socket interface differs somewhat between
1852 Unix and NT; handle details in the front end.
1854 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1859 EscapeExpand (char *p, char *q)
1860 { // [HGM] initstring: routine to shape up string arguments
1861 while(*p++ = *q++) if(p[-1] == '\\')
1863 case 'n': p[-1] = '\n'; break;
1864 case 'r': p[-1] = '\r'; break;
1865 case 't': p[-1] = '\t'; break;
1866 case '\\': p[-1] = '\\'; break;
1867 case 0: *p = 0; return;
1868 default: p[-1] = q[-1]; break;
1873 show_bytes (FILE *fp, char *buf, int count)
1876 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1877 fprintf(fp, "\\%03o", *buf & 0xff);
1886 /* Returns an errno value */
1888 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1890 char buf[8192], *p, *q, *buflim;
1891 int left, newcount, outcount;
1893 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1894 *appData.gateway != NULLCHAR) {
1895 if (appData.debugMode) {
1896 fprintf(debugFP, ">ICS: ");
1897 show_bytes(debugFP, message, count);
1898 fprintf(debugFP, "\n");
1900 return OutputToProcess(pr, message, count, outError);
1903 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1910 if (appData.debugMode) {
1911 fprintf(debugFP, ">ICS: ");
1912 show_bytes(debugFP, buf, newcount);
1913 fprintf(debugFP, "\n");
1915 outcount = OutputToProcess(pr, buf, newcount, outError);
1916 if (outcount < newcount) return -1; /* to be sure */
1923 } else if (((unsigned char) *p) == TN_IAC) {
1924 *q++ = (char) TN_IAC;
1931 if (appData.debugMode) {
1932 fprintf(debugFP, ">ICS: ");
1933 show_bytes(debugFP, buf, newcount);
1934 fprintf(debugFP, "\n");
1936 outcount = OutputToProcess(pr, buf, newcount, outError);
1937 if (outcount < newcount) return -1; /* to be sure */
1942 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1944 int outError, outCount;
1945 static int gotEof = 0;
1948 /* Pass data read from player on to ICS */
1951 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1952 if (outCount < count) {
1953 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1955 if(have_sent_ICS_logon == 2) {
1956 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1957 fprintf(ini, "%s", message);
1958 have_sent_ICS_logon = 3;
1960 have_sent_ICS_logon = 1;
1961 } else if(have_sent_ICS_logon == 3) {
1962 fprintf(ini, "%s", message);
1964 have_sent_ICS_logon = 1;
1966 } else if (count < 0) {
1967 RemoveInputSource(isr);
1968 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1969 } else if (gotEof++ > 0) {
1970 RemoveInputSource(isr);
1971 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1977 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1978 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1979 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1980 SendToICS("date\n");
1981 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1984 /* added routine for printf style output to ics */
1986 ics_printf (char *format, ...)
1988 char buffer[MSG_SIZ];
1991 va_start(args, format);
1992 vsnprintf(buffer, sizeof(buffer), format, args);
1993 buffer[sizeof(buffer)-1] = '\0';
2001 int count, outCount, outError;
2003 if (icsPR == NoProc) return;
2006 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2007 if (outCount < count) {
2008 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2012 /* This is used for sending logon scripts to the ICS. Sending
2013 without a delay causes problems when using timestamp on ICC
2014 (at least on my machine). */
2016 SendToICSDelayed (char *s, long msdelay)
2018 int count, outCount, outError;
2020 if (icsPR == NoProc) return;
2023 if (appData.debugMode) {
2024 fprintf(debugFP, ">ICS: ");
2025 show_bytes(debugFP, s, count);
2026 fprintf(debugFP, "\n");
2028 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2030 if (outCount < count) {
2031 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2036 /* Remove all highlighting escape sequences in s
2037 Also deletes any suffix starting with '('
2040 StripHighlightAndTitle (char *s)
2042 static char retbuf[MSG_SIZ];
2045 while (*s != NULLCHAR) {
2046 while (*s == '\033') {
2047 while (*s != NULLCHAR && !isalpha(*s)) s++;
2048 if (*s != NULLCHAR) s++;
2050 while (*s != NULLCHAR && *s != '\033') {
2051 if (*s == '(' || *s == '[') {
2062 /* Remove all highlighting escape sequences in s */
2064 StripHighlight (char *s)
2066 static char retbuf[MSG_SIZ];
2069 while (*s != NULLCHAR) {
2070 while (*s == '\033') {
2071 while (*s != NULLCHAR && !isalpha(*s)) s++;
2072 if (*s != NULLCHAR) s++;
2074 while (*s != NULLCHAR && *s != '\033') {
2082 char engineVariant[MSG_SIZ];
2083 char *variantNames[] = VARIANT_NAMES;
2085 VariantName (VariantClass v)
2087 if(v == VariantUnknown || *engineVariant) return engineVariant;
2088 return variantNames[v];
2092 /* Identify a variant from the strings the chess servers use or the
2093 PGN Variant tag names we use. */
2095 StringToVariant (char *e)
2099 VariantClass v = VariantNormal;
2100 int i, found = FALSE;
2106 /* [HGM] skip over optional board-size prefixes */
2107 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2108 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2109 while( *e++ != '_');
2112 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2116 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2117 if (p = StrCaseStr(e, variantNames[i])) {
2118 if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
2119 v = (VariantClass) i;
2126 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2127 || StrCaseStr(e, "wild/fr")
2128 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2129 v = VariantFischeRandom;
2130 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2131 (i = 1, p = StrCaseStr(e, "w"))) {
2133 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2140 case 0: /* FICS only, actually */
2142 /* Castling legal even if K starts on d-file */
2143 v = VariantWildCastle;
2148 /* Castling illegal even if K & R happen to start in
2149 normal positions. */
2150 v = VariantNoCastle;
2163 /* Castling legal iff K & R start in normal positions */
2169 /* Special wilds for position setup; unclear what to do here */
2170 v = VariantLoadable;
2173 /* Bizarre ICC game */
2174 v = VariantTwoKings;
2177 v = VariantKriegspiel;
2183 v = VariantFischeRandom;
2186 v = VariantCrazyhouse;
2189 v = VariantBughouse;
2195 /* Not quite the same as FICS suicide! */
2196 v = VariantGiveaway;
2202 v = VariantShatranj;
2205 /* Temporary names for future ICC types. The name *will* change in
2206 the next xboard/WinBoard release after ICC defines it. */
2244 v = VariantCapablanca;
2247 v = VariantKnightmate;
2253 v = VariantCylinder;
2259 v = VariantCapaRandom;
2262 v = VariantBerolina;
2274 /* Found "wild" or "w" in the string but no number;
2275 must assume it's normal chess. */
2279 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2280 if( (len >= MSG_SIZ) && appData.debugMode )
2281 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2283 DisplayError(buf, 0);
2289 if (appData.debugMode) {
2290 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2291 e, wnum, VariantName(v));
2296 static int leftover_start = 0, leftover_len = 0;
2297 char star_match[STAR_MATCH_N][MSG_SIZ];
2299 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2300 advance *index beyond it, and set leftover_start to the new value of
2301 *index; else return FALSE. If pattern contains the character '*', it
2302 matches any sequence of characters not containing '\r', '\n', or the
2303 character following the '*' (if any), and the matched sequence(s) are
2304 copied into star_match.
2307 looking_at ( char *buf, int *index, char *pattern)
2309 char *bufp = &buf[*index], *patternp = pattern;
2311 char *matchp = star_match[0];
2314 if (*patternp == NULLCHAR) {
2315 *index = leftover_start = bufp - buf;
2319 if (*bufp == NULLCHAR) return FALSE;
2320 if (*patternp == '*') {
2321 if (*bufp == *(patternp + 1)) {
2323 matchp = star_match[++star_count];
2327 } else if (*bufp == '\n' || *bufp == '\r') {
2329 if (*patternp == NULLCHAR)
2334 *matchp++ = *bufp++;
2338 if (*patternp != *bufp) return FALSE;
2345 SendToPlayer (char *data, int length)
2347 int error, outCount;
2348 outCount = OutputToProcess(NoProc, data, length, &error);
2349 if (outCount < length) {
2350 DisplayFatalError(_("Error writing to display"), error, 1);
2355 PackHolding (char packed[], char *holding)
2365 switch (runlength) {
2376 sprintf(q, "%d", runlength);
2388 /* Telnet protocol requests from the front end */
2390 TelnetRequest (unsigned char ddww, unsigned char option)
2392 unsigned char msg[3];
2393 int outCount, outError;
2395 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2397 if (appData.debugMode) {
2398 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2414 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2423 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2426 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2431 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2433 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2440 if (!appData.icsActive) return;
2441 TelnetRequest(TN_DO, TN_ECHO);
2447 if (!appData.icsActive) return;
2448 TelnetRequest(TN_DONT, TN_ECHO);
2452 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2454 /* put the holdings sent to us by the server on the board holdings area */
2455 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2459 if(gameInfo.holdingsWidth < 2) return;
2460 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2461 return; // prevent overwriting by pre-board holdings
2463 if( (int)lowestPiece >= BlackPawn ) {
2466 holdingsStartRow = BOARD_HEIGHT-1;
2469 holdingsColumn = BOARD_WIDTH-1;
2470 countsColumn = BOARD_WIDTH-2;
2471 holdingsStartRow = 0;
2475 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2476 board[i][holdingsColumn] = EmptySquare;
2477 board[i][countsColumn] = (ChessSquare) 0;
2479 while( (p=*holdings++) != NULLCHAR ) {
2480 piece = CharToPiece( ToUpper(p) );
2481 if(piece == EmptySquare) continue;
2482 /*j = (int) piece - (int) WhitePawn;*/
2483 j = PieceToNumber(piece);
2484 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2485 if(j < 0) continue; /* should not happen */
2486 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2487 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2488 board[holdingsStartRow+j*direction][countsColumn]++;
2494 VariantSwitch (Board board, VariantClass newVariant)
2496 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2497 static Board oldBoard;
2499 startedFromPositionFile = FALSE;
2500 if(gameInfo.variant == newVariant) return;
2502 /* [HGM] This routine is called each time an assignment is made to
2503 * gameInfo.variant during a game, to make sure the board sizes
2504 * are set to match the new variant. If that means adding or deleting
2505 * holdings, we shift the playing board accordingly
2506 * This kludge is needed because in ICS observe mode, we get boards
2507 * of an ongoing game without knowing the variant, and learn about the
2508 * latter only later. This can be because of the move list we requested,
2509 * in which case the game history is refilled from the beginning anyway,
2510 * but also when receiving holdings of a crazyhouse game. In the latter
2511 * case we want to add those holdings to the already received position.
2515 if (appData.debugMode) {
2516 fprintf(debugFP, "Switch board from %s to %s\n",
2517 VariantName(gameInfo.variant), VariantName(newVariant));
2518 setbuf(debugFP, NULL);
2520 shuffleOpenings = 0; /* [HGM] shuffle */
2521 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2525 newWidth = 9; newHeight = 9;
2526 gameInfo.holdingsSize = 7;
2527 case VariantBughouse:
2528 case VariantCrazyhouse:
2529 newHoldingsWidth = 2; break;
2533 newHoldingsWidth = 2;
2534 gameInfo.holdingsSize = 8;
2537 case VariantCapablanca:
2538 case VariantCapaRandom:
2541 newHoldingsWidth = gameInfo.holdingsSize = 0;
2544 if(newWidth != gameInfo.boardWidth ||
2545 newHeight != gameInfo.boardHeight ||
2546 newHoldingsWidth != gameInfo.holdingsWidth ) {
2548 /* shift position to new playing area, if needed */
2549 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2550 for(i=0; i<BOARD_HEIGHT; i++)
2551 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2552 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2554 for(i=0; i<newHeight; i++) {
2555 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2556 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2558 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2559 for(i=0; i<BOARD_HEIGHT; i++)
2560 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2561 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2564 board[HOLDINGS_SET] = 0;
2565 gameInfo.boardWidth = newWidth;
2566 gameInfo.boardHeight = newHeight;
2567 gameInfo.holdingsWidth = newHoldingsWidth;
2568 gameInfo.variant = newVariant;
2569 InitDrawingSizes(-2, 0);
2570 } else gameInfo.variant = newVariant;
2571 CopyBoard(oldBoard, board); // remember correctly formatted board
2572 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2573 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2576 static int loggedOn = FALSE;
2578 /*-- Game start info cache: --*/
2580 char gs_kind[MSG_SIZ];
2581 static char player1Name[128] = "";
2582 static char player2Name[128] = "";
2583 static char cont_seq[] = "\n\\ ";
2584 static int player1Rating = -1;
2585 static int player2Rating = -1;
2586 /*----------------------------*/
2588 ColorClass curColor = ColorNormal;
2589 int suppressKibitz = 0;
2592 Boolean soughtPending = FALSE;
2593 Boolean seekGraphUp;
2594 #define MAX_SEEK_ADS 200
2596 char *seekAdList[MAX_SEEK_ADS];
2597 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2598 float tcList[MAX_SEEK_ADS];
2599 char colorList[MAX_SEEK_ADS];
2600 int nrOfSeekAds = 0;
2601 int minRating = 1010, maxRating = 2800;
2602 int hMargin = 10, vMargin = 20, h, w;
2603 extern int squareSize, lineGap;
2608 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2609 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2610 if(r < minRating+100 && r >=0 ) r = minRating+100;
2611 if(r > maxRating) r = maxRating;
2612 if(tc < 1.f) tc = 1.f;
2613 if(tc > 95.f) tc = 95.f;
2614 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2615 y = ((double)r - minRating)/(maxRating - minRating)
2616 * (h-vMargin-squareSize/8-1) + vMargin;
2617 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2618 if(strstr(seekAdList[i], " u ")) color = 1;
2619 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2620 !strstr(seekAdList[i], "bullet") &&
2621 !strstr(seekAdList[i], "blitz") &&
2622 !strstr(seekAdList[i], "standard") ) color = 2;
2623 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2624 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2628 PlotSingleSeekAd (int i)
2634 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2636 char buf[MSG_SIZ], *ext = "";
2637 VariantClass v = StringToVariant(type);
2638 if(strstr(type, "wild")) {
2639 ext = type + 4; // append wild number
2640 if(v == VariantFischeRandom) type = "chess960"; else
2641 if(v == VariantLoadable) type = "setup"; else
2642 type = VariantName(v);
2644 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2645 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2646 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2647 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2648 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2649 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2650 seekNrList[nrOfSeekAds] = nr;
2651 zList[nrOfSeekAds] = 0;
2652 seekAdList[nrOfSeekAds++] = StrSave(buf);
2653 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2658 EraseSeekDot (int i)
2660 int x = xList[i], y = yList[i], d=squareSize/4, k;
2661 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2662 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2663 // now replot every dot that overlapped
2664 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2665 int xx = xList[k], yy = yList[k];
2666 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2667 DrawSeekDot(xx, yy, colorList[k]);
2672 RemoveSeekAd (int nr)
2675 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2677 if(seekAdList[i]) free(seekAdList[i]);
2678 seekAdList[i] = seekAdList[--nrOfSeekAds];
2679 seekNrList[i] = seekNrList[nrOfSeekAds];
2680 ratingList[i] = ratingList[nrOfSeekAds];
2681 colorList[i] = colorList[nrOfSeekAds];
2682 tcList[i] = tcList[nrOfSeekAds];
2683 xList[i] = xList[nrOfSeekAds];
2684 yList[i] = yList[nrOfSeekAds];
2685 zList[i] = zList[nrOfSeekAds];
2686 seekAdList[nrOfSeekAds] = NULL;
2692 MatchSoughtLine (char *line)
2694 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2695 int nr, base, inc, u=0; char dummy;
2697 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2698 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2700 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2701 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2702 // match: compact and save the line
2703 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2713 if(!seekGraphUp) return FALSE;
2714 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2715 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2717 DrawSeekBackground(0, 0, w, h);
2718 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2719 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2720 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2721 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2723 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2726 snprintf(buf, MSG_SIZ, "%d", i);
2727 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2730 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2731 for(i=1; i<100; i+=(i<10?1:5)) {
2732 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2733 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2734 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2736 snprintf(buf, MSG_SIZ, "%d", i);
2737 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2740 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2745 SeekGraphClick (ClickType click, int x, int y, int moving)
2747 static int lastDown = 0, displayed = 0, lastSecond;
2748 if(y < 0) return FALSE;
2749 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2750 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2751 if(!seekGraphUp) return FALSE;
2752 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2753 DrawPosition(TRUE, NULL);
2756 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2757 if(click == Release || moving) return FALSE;
2759 soughtPending = TRUE;
2760 SendToICS(ics_prefix);
2761 SendToICS("sought\n"); // should this be "sought all"?
2762 } else { // issue challenge based on clicked ad
2763 int dist = 10000; int i, closest = 0, second = 0;
2764 for(i=0; i<nrOfSeekAds; i++) {
2765 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2766 if(d < dist) { dist = d; closest = i; }
2767 second += (d - zList[i] < 120); // count in-range ads
2768 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2772 second = (second > 1);
2773 if(displayed != closest || second != lastSecond) {
2774 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2775 lastSecond = second; displayed = closest;
2777 if(click == Press) {
2778 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2781 } // on press 'hit', only show info
2782 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2783 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2784 SendToICS(ics_prefix);
2786 return TRUE; // let incoming board of started game pop down the graph
2787 } else if(click == Release) { // release 'miss' is ignored
2788 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2789 if(moving == 2) { // right up-click
2790 nrOfSeekAds = 0; // refresh graph
2791 soughtPending = TRUE;
2792 SendToICS(ics_prefix);
2793 SendToICS("sought\n"); // should this be "sought all"?
2796 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2797 // press miss or release hit 'pop down' seek graph
2798 seekGraphUp = FALSE;
2799 DrawPosition(TRUE, NULL);
2805 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2807 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2808 #define STARTED_NONE 0
2809 #define STARTED_MOVES 1
2810 #define STARTED_BOARD 2
2811 #define STARTED_OBSERVE 3
2812 #define STARTED_HOLDINGS 4
2813 #define STARTED_CHATTER 5
2814 #define STARTED_COMMENT 6
2815 #define STARTED_MOVES_NOHIDE 7
2817 static int started = STARTED_NONE;
2818 static char parse[20000];
2819 static int parse_pos = 0;
2820 static char buf[BUF_SIZE + 1];
2821 static int firstTime = TRUE, intfSet = FALSE;
2822 static ColorClass prevColor = ColorNormal;
2823 static int savingComment = FALSE;
2824 static int cmatch = 0; // continuation sequence match
2831 int backup; /* [DM] For zippy color lines */
2833 char talker[MSG_SIZ]; // [HGM] chat
2834 int channel, collective=0;
2836 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2838 if (appData.debugMode) {
2840 fprintf(debugFP, "<ICS: ");
2841 show_bytes(debugFP, data, count);
2842 fprintf(debugFP, "\n");
2846 if (appData.debugMode) { int f = forwardMostMove;
2847 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2848 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2849 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2852 /* If last read ended with a partial line that we couldn't parse,
2853 prepend it to the new read and try again. */
2854 if (leftover_len > 0) {
2855 for (i=0; i<leftover_len; i++)
2856 buf[i] = buf[leftover_start + i];
2859 /* copy new characters into the buffer */
2860 bp = buf + leftover_len;
2861 buf_len=leftover_len;
2862 for (i=0; i<count; i++)
2865 if (data[i] == '\r')
2868 // join lines split by ICS?
2869 if (!appData.noJoin)
2872 Joining just consists of finding matches against the
2873 continuation sequence, and discarding that sequence
2874 if found instead of copying it. So, until a match
2875 fails, there's nothing to do since it might be the
2876 complete sequence, and thus, something we don't want
2879 if (data[i] == cont_seq[cmatch])
2882 if (cmatch == strlen(cont_seq))
2884 cmatch = 0; // complete match. just reset the counter
2887 it's possible for the ICS to not include the space
2888 at the end of the last word, making our [correct]
2889 join operation fuse two separate words. the server
2890 does this when the space occurs at the width setting.
2892 if (!buf_len || buf[buf_len-1] != ' ')
2903 match failed, so we have to copy what matched before
2904 falling through and copying this character. In reality,
2905 this will only ever be just the newline character, but
2906 it doesn't hurt to be precise.
2908 strncpy(bp, cont_seq, cmatch);
2920 buf[buf_len] = NULLCHAR;
2921 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2926 while (i < buf_len) {
2927 /* Deal with part of the TELNET option negotiation
2928 protocol. We refuse to do anything beyond the
2929 defaults, except that we allow the WILL ECHO option,
2930 which ICS uses to turn off password echoing when we are
2931 directly connected to it. We reject this option
2932 if localLineEditing mode is on (always on in xboard)
2933 and we are talking to port 23, which might be a real
2934 telnet server that will try to keep WILL ECHO on permanently.
2936 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2937 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2938 unsigned char option;
2940 switch ((unsigned char) buf[++i]) {
2942 if (appData.debugMode)
2943 fprintf(debugFP, "\n<WILL ");
2944 switch (option = (unsigned char) buf[++i]) {
2946 if (appData.debugMode)
2947 fprintf(debugFP, "ECHO ");
2948 /* Reply only if this is a change, according
2949 to the protocol rules. */
2950 if (remoteEchoOption) break;
2951 if (appData.localLineEditing &&
2952 atoi(appData.icsPort) == TN_PORT) {
2953 TelnetRequest(TN_DONT, TN_ECHO);
2956 TelnetRequest(TN_DO, TN_ECHO);
2957 remoteEchoOption = TRUE;
2961 if (appData.debugMode)
2962 fprintf(debugFP, "%d ", option);
2963 /* Whatever this is, we don't want it. */
2964 TelnetRequest(TN_DONT, option);
2969 if (appData.debugMode)
2970 fprintf(debugFP, "\n<WONT ");
2971 switch (option = (unsigned char) buf[++i]) {
2973 if (appData.debugMode)
2974 fprintf(debugFP, "ECHO ");
2975 /* Reply only if this is a change, according
2976 to the protocol rules. */
2977 if (!remoteEchoOption) break;
2979 TelnetRequest(TN_DONT, TN_ECHO);
2980 remoteEchoOption = FALSE;
2983 if (appData.debugMode)
2984 fprintf(debugFP, "%d ", (unsigned char) option);
2985 /* Whatever this is, it must already be turned
2986 off, because we never agree to turn on
2987 anything non-default, so according to the
2988 protocol rules, we don't reply. */
2993 if (appData.debugMode)
2994 fprintf(debugFP, "\n<DO ");
2995 switch (option = (unsigned char) buf[++i]) {
2997 /* Whatever this is, we refuse to do it. */
2998 if (appData.debugMode)
2999 fprintf(debugFP, "%d ", option);
3000 TelnetRequest(TN_WONT, option);
3005 if (appData.debugMode)
3006 fprintf(debugFP, "\n<DONT ");
3007 switch (option = (unsigned char) buf[++i]) {
3009 if (appData.debugMode)
3010 fprintf(debugFP, "%d ", option);
3011 /* Whatever this is, we are already not doing
3012 it, because we never agree to do anything
3013 non-default, so according to the protocol
3014 rules, we don't reply. */
3019 if (appData.debugMode)
3020 fprintf(debugFP, "\n<IAC ");
3021 /* Doubled IAC; pass it through */
3025 if (appData.debugMode)
3026 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3027 /* Drop all other telnet commands on the floor */
3030 if (oldi > next_out)
3031 SendToPlayer(&buf[next_out], oldi - next_out);
3037 /* OK, this at least will *usually* work */
3038 if (!loggedOn && looking_at(buf, &i, "ics%")) {
3042 if (loggedOn && !intfSet) {
3043 if (ics_type == ICS_ICC) {
3044 snprintf(str, MSG_SIZ,
3045 "/set-quietly interface %s\n/set-quietly style 12\n",
3047 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3048 strcat(str, "/set-2 51 1\n/set seek 1\n");
3049 } else if (ics_type == ICS_CHESSNET) {
3050 snprintf(str, MSG_SIZ, "/style 12\n");
3052 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3053 strcat(str, programVersion);
3054 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3055 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3056 strcat(str, "$iset seekremove 1\n$set seek 1\n");
3058 strcat(str, "$iset nohighlight 1\n");
3060 strcat(str, "$iset lock 1\n$style 12\n");
3063 NotifyFrontendLogin();
3067 if (started == STARTED_COMMENT) {
3068 /* Accumulate characters in comment */
3069 parse[parse_pos++] = buf[i];
3070 if (buf[i] == '\n') {
3071 parse[parse_pos] = NULLCHAR;
3072 if(chattingPartner>=0) {
3074 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3075 OutputChatMessage(chattingPartner, mess);
3076 if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3078 talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3079 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3080 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3081 OutputChatMessage(p, mess);
3085 chattingPartner = -1;
3086 if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3089 if(!suppressKibitz) // [HGM] kibitz
3090 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3091 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3092 int nrDigit = 0, nrAlph = 0, j;
3093 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3094 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3095 parse[parse_pos] = NULLCHAR;
3096 // try to be smart: if it does not look like search info, it should go to
3097 // ICS interaction window after all, not to engine-output window.
3098 for(j=0; j<parse_pos; j++) { // count letters and digits
3099 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3100 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3101 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3103 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3104 int depth=0; float score;
3105 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3106 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3107 pvInfoList[forwardMostMove-1].depth = depth;
3108 pvInfoList[forwardMostMove-1].score = 100*score;
3110 OutputKibitz(suppressKibitz, parse);
3113 if(gameMode == IcsObserving) // restore original ICS messages
3114 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3115 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3117 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3118 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3119 SendToPlayer(tmp, strlen(tmp));
3121 next_out = i+1; // [HGM] suppress printing in ICS window
3123 started = STARTED_NONE;
3125 /* Don't match patterns against characters in comment */
3130 if (started == STARTED_CHATTER) {
3131 if (buf[i] != '\n') {
3132 /* Don't match patterns against characters in chatter */
3136 started = STARTED_NONE;
3137 if(suppressKibitz) next_out = i+1;
3140 /* Kludge to deal with rcmd protocol */
3141 if (firstTime && looking_at(buf, &i, "\001*")) {
3142 DisplayFatalError(&buf[1], 0, 1);
3148 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3151 if (appData.debugMode)
3152 fprintf(debugFP, "ics_type %d\n", ics_type);
3155 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3156 ics_type = ICS_FICS;
3158 if (appData.debugMode)
3159 fprintf(debugFP, "ics_type %d\n", ics_type);
3162 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3163 ics_type = ICS_CHESSNET;
3165 if (appData.debugMode)
3166 fprintf(debugFP, "ics_type %d\n", ics_type);
3171 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3172 looking_at(buf, &i, "Logging you in as \"*\"") ||
3173 looking_at(buf, &i, "will be \"*\""))) {
3174 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3178 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3180 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3181 DisplayIcsInteractionTitle(buf);
3182 have_set_title = TRUE;
3185 /* skip finger notes */
3186 if (started == STARTED_NONE &&
3187 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3188 (buf[i] == '1' && buf[i+1] == '0')) &&
3189 buf[i+2] == ':' && buf[i+3] == ' ') {
3190 started = STARTED_CHATTER;
3196 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3197 if(appData.seekGraph) {
3198 if(soughtPending && MatchSoughtLine(buf+i)) {
3199 i = strstr(buf+i, "rated") - buf;
3200 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3201 next_out = leftover_start = i;
3202 started = STARTED_CHATTER;
3203 suppressKibitz = TRUE;
3206 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3207 && looking_at(buf, &i, "* ads displayed")) {
3208 soughtPending = FALSE;
3213 if(appData.autoRefresh) {
3214 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3215 int s = (ics_type == ICS_ICC); // ICC format differs
3217 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3218 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3219 looking_at(buf, &i, "*% "); // eat prompt
3220 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3221 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3222 next_out = i; // suppress
3225 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3226 char *p = star_match[0];
3228 if(seekGraphUp) RemoveSeekAd(atoi(p));
3229 while(*p && *p++ != ' '); // next
3231 looking_at(buf, &i, "*% "); // eat prompt
3232 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3239 /* skip formula vars */
3240 if (started == STARTED_NONE &&
3241 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3242 started = STARTED_CHATTER;
3247 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3248 if (appData.autoKibitz && started == STARTED_NONE &&
3249 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3250 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3251 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3252 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3253 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3254 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3255 suppressKibitz = TRUE;
3256 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3258 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3259 && (gameMode == IcsPlayingWhite)) ||
3260 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3261 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3262 started = STARTED_CHATTER; // own kibitz we simply discard
3264 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3265 parse_pos = 0; parse[0] = NULLCHAR;
3266 savingComment = TRUE;
3267 suppressKibitz = gameMode != IcsObserving ? 2 :
3268 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3272 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3273 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3274 && atoi(star_match[0])) {
3275 // suppress the acknowledgements of our own autoKibitz
3277 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3278 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3279 SendToPlayer(star_match[0], strlen(star_match[0]));
3280 if(looking_at(buf, &i, "*% ")) // eat prompt
3281 suppressKibitz = FALSE;
3285 } // [HGM] kibitz: end of patch
3287 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3289 // [HGM] chat: intercept tells by users for which we have an open chat window
3291 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3292 looking_at(buf, &i, "* whispers:") ||
3293 looking_at(buf, &i, "* kibitzes:") ||
3294 looking_at(buf, &i, "* shouts:") ||
3295 looking_at(buf, &i, "* c-shouts:") ||
3296 looking_at(buf, &i, "--> * ") ||
3297 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3298 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3299 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3300 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3302 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3303 chattingPartner = -1; collective = 0;
3305 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3306 for(p=0; p<MAX_CHAT; p++) {
3308 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3309 talker[0] = '['; strcat(talker, "] ");
3310 Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3311 chattingPartner = p; break;
3314 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3315 for(p=0; p<MAX_CHAT; p++) {
3317 if(!strcmp("kibitzes", chatPartner[p])) {
3318 talker[0] = '['; strcat(talker, "] ");
3319 chattingPartner = p; break;
3322 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3323 for(p=0; p<MAX_CHAT; p++) {
3325 if(!strcmp("whispers", chatPartner[p])) {
3326 talker[0] = '['; strcat(talker, "] ");
3327 chattingPartner = p; break;
3330 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3331 if(buf[i-8] == '-' && buf[i-3] == 't')
3332 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3334 if(!strcmp("c-shouts", chatPartner[p])) {
3335 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3336 chattingPartner = p; break;
3339 if(chattingPartner < 0)
3340 for(p=0; p<MAX_CHAT; p++) {
3342 if(!strcmp("shouts", chatPartner[p])) {
3343 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3344 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3345 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3346 chattingPartner = p; break;
3350 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3351 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3353 Colorize(ColorTell, FALSE);
3354 if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3356 chattingPartner = p; break;
3358 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3359 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3360 started = STARTED_COMMENT;
3361 parse_pos = 0; parse[0] = NULLCHAR;
3362 savingComment = 3 + chattingPartner; // counts as TRUE
3363 if(collective == 3) i = oldi; else {
3364 suppressKibitz = TRUE;
3365 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3366 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3370 } // [HGM] chat: end of patch
3373 if (appData.zippyTalk || appData.zippyPlay) {
3374 /* [DM] Backup address for color zippy lines */
3376 if (loggedOn == TRUE)
3377 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3378 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3380 } // [DM] 'else { ' deleted
3382 /* Regular tells and says */
3383 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3384 looking_at(buf, &i, "* (your partner) tells you: ") ||
3385 looking_at(buf, &i, "* says: ") ||
3386 /* Don't color "message" or "messages" output */
3387 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3388 looking_at(buf, &i, "*. * at *:*: ") ||
3389 looking_at(buf, &i, "--* (*:*): ") ||
3390 /* Message notifications (same color as tells) */
3391 looking_at(buf, &i, "* has left a message ") ||
3392 looking_at(buf, &i, "* just sent you a message:\n") ||
3393 /* Whispers and kibitzes */
3394 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3395 looking_at(buf, &i, "* kibitzes: ") ||
3397 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3399 if (tkind == 1 && strchr(star_match[0], ':')) {
3400 /* Avoid "tells you:" spoofs in channels */
3403 if (star_match[0][0] == NULLCHAR ||
3404 strchr(star_match[0], ' ') ||
3405 (tkind == 3 && strchr(star_match[1], ' '))) {
3406 /* Reject bogus matches */
3409 if (appData.colorize) {
3410 if (oldi > next_out) {
3411 SendToPlayer(&buf[next_out], oldi - next_out);
3416 Colorize(ColorTell, FALSE);
3417 curColor = ColorTell;
3420 Colorize(ColorKibitz, FALSE);
3421 curColor = ColorKibitz;
3424 p = strrchr(star_match[1], '(');
3431 Colorize(ColorChannel1, FALSE);
3432 curColor = ColorChannel1;
3434 Colorize(ColorChannel, FALSE);
3435 curColor = ColorChannel;
3439 curColor = ColorNormal;
3443 if (started == STARTED_NONE && appData.autoComment &&
3444 (gameMode == IcsObserving ||
3445 gameMode == IcsPlayingWhite ||
3446 gameMode == IcsPlayingBlack)) {
3447 parse_pos = i - oldi;
3448 memcpy(parse, &buf[oldi], parse_pos);
3449 parse[parse_pos] = NULLCHAR;
3450 started = STARTED_COMMENT;
3451 savingComment = TRUE;
3452 } else if(collective != 3) {
3453 started = STARTED_CHATTER;
3454 savingComment = FALSE;
3461 if (looking_at(buf, &i, "* s-shouts: ") ||
3462 looking_at(buf, &i, "* c-shouts: ")) {
3463 if (appData.colorize) {
3464 if (oldi > next_out) {
3465 SendToPlayer(&buf[next_out], oldi - next_out);
3468 Colorize(ColorSShout, FALSE);
3469 curColor = ColorSShout;
3472 started = STARTED_CHATTER;
3476 if (looking_at(buf, &i, "--->")) {
3481 if (looking_at(buf, &i, "* shouts: ") ||
3482 looking_at(buf, &i, "--> ")) {
3483 if (appData.colorize) {
3484 if (oldi > next_out) {
3485 SendToPlayer(&buf[next_out], oldi - next_out);
3488 Colorize(ColorShout, FALSE);
3489 curColor = ColorShout;
3492 started = STARTED_CHATTER;
3496 if (looking_at( buf, &i, "Challenge:")) {
3497 if (appData.colorize) {
3498 if (oldi > next_out) {
3499 SendToPlayer(&buf[next_out], oldi - next_out);
3502 Colorize(ColorChallenge, FALSE);
3503 curColor = ColorChallenge;
3509 if (looking_at(buf, &i, "* offers you") ||
3510 looking_at(buf, &i, "* offers to be") ||
3511 looking_at(buf, &i, "* would like to") ||
3512 looking_at(buf, &i, "* requests to") ||
3513 looking_at(buf, &i, "Your opponent offers") ||
3514 looking_at(buf, &i, "Your opponent requests")) {
3516 if (appData.colorize) {
3517 if (oldi > next_out) {
3518 SendToPlayer(&buf[next_out], oldi - next_out);
3521 Colorize(ColorRequest, FALSE);
3522 curColor = ColorRequest;
3527 if (looking_at(buf, &i, "* (*) seeking")) {
3528 if (appData.colorize) {
3529 if (oldi > next_out) {
3530 SendToPlayer(&buf[next_out], oldi - next_out);
3533 Colorize(ColorSeek, FALSE);
3534 curColor = ColorSeek;
3539 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3541 if (looking_at(buf, &i, "\\ ")) {
3542 if (prevColor != ColorNormal) {
3543 if (oldi > next_out) {
3544 SendToPlayer(&buf[next_out], oldi - next_out);
3547 Colorize(prevColor, TRUE);
3548 curColor = prevColor;
3550 if (savingComment) {
3551 parse_pos = i - oldi;
3552 memcpy(parse, &buf[oldi], parse_pos);
3553 parse[parse_pos] = NULLCHAR;
3554 started = STARTED_COMMENT;
3555 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3556 chattingPartner = savingComment - 3; // kludge to remember the box
3558 started = STARTED_CHATTER;
3563 if (looking_at(buf, &i, "Black Strength :") ||
3564 looking_at(buf, &i, "<<< style 10 board >>>") ||
3565 looking_at(buf, &i, "<10>") ||
3566 looking_at(buf, &i, "#@#")) {
3567 /* Wrong board style */
3569 SendToICS(ics_prefix);
3570 SendToICS("set style 12\n");
3571 SendToICS(ics_prefix);
3572 SendToICS("refresh\n");
3576 if (looking_at(buf, &i, "login:")) {
3577 if (!have_sent_ICS_logon) {
3579 have_sent_ICS_logon = 1;
3580 else // no init script was found
3581 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3582 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3583 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3588 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3589 (looking_at(buf, &i, "\n<12> ") ||
3590 looking_at(buf, &i, "<12> "))) {
3592 if (oldi > next_out) {
3593 SendToPlayer(&buf[next_out], oldi - next_out);
3596 started = STARTED_BOARD;
3601 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3602 looking_at(buf, &i, "<b1> ")) {
3603 if (oldi > next_out) {
3604 SendToPlayer(&buf[next_out], oldi - next_out);
3607 started = STARTED_HOLDINGS;
3612 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3614 /* Header for a move list -- first line */
3616 switch (ics_getting_history) {
3620 case BeginningOfGame:
3621 /* User typed "moves" or "oldmoves" while we
3622 were idle. Pretend we asked for these
3623 moves and soak them up so user can step
3624 through them and/or save them.
3627 gameMode = IcsObserving;
3630 ics_getting_history = H_GOT_UNREQ_HEADER;
3632 case EditGame: /*?*/
3633 case EditPosition: /*?*/
3634 /* Should above feature work in these modes too? */
3635 /* For now it doesn't */
3636 ics_getting_history = H_GOT_UNWANTED_HEADER;
3639 ics_getting_history = H_GOT_UNWANTED_HEADER;
3644 /* Is this the right one? */
3645 if (gameInfo.white && gameInfo.black &&
3646 strcmp(gameInfo.white, star_match[0]) == 0 &&
3647 strcmp(gameInfo.black, star_match[2]) == 0) {
3649 ics_getting_history = H_GOT_REQ_HEADER;
3652 case H_GOT_REQ_HEADER:
3653 case H_GOT_UNREQ_HEADER:
3654 case H_GOT_UNWANTED_HEADER:
3655 case H_GETTING_MOVES:
3656 /* Should not happen */
3657 DisplayError(_("Error gathering move list: two headers"), 0);
3658 ics_getting_history = H_FALSE;
3662 /* Save player ratings into gameInfo if needed */
3663 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3664 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3665 (gameInfo.whiteRating == -1 ||
3666 gameInfo.blackRating == -1)) {
3668 gameInfo.whiteRating = string_to_rating(star_match[1]);
3669 gameInfo.blackRating = string_to_rating(star_match[3]);
3670 if (appData.debugMode)
3671 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3672 gameInfo.whiteRating, gameInfo.blackRating);
3677 if (looking_at(buf, &i,
3678 "* * match, initial time: * minute*, increment: * second")) {
3679 /* Header for a move list -- second line */
3680 /* Initial board will follow if this is a wild game */
3681 if (gameInfo.event != NULL) free(gameInfo.event);
3682 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3683 gameInfo.event = StrSave(str);
3684 /* [HGM] we switched variant. Translate boards if needed. */
3685 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3689 if (looking_at(buf, &i, "Move ")) {
3690 /* Beginning of a move list */
3691 switch (ics_getting_history) {
3693 /* Normally should not happen */
3694 /* Maybe user hit reset while we were parsing */
3697 /* Happens if we are ignoring a move list that is not
3698 * the one we just requested. Common if the user
3699 * tries to observe two games without turning off
3702 case H_GETTING_MOVES:
3703 /* Should not happen */
3704 DisplayError(_("Error gathering move list: nested"), 0);
3705 ics_getting_history = H_FALSE;
3707 case H_GOT_REQ_HEADER:
3708 ics_getting_history = H_GETTING_MOVES;
3709 started = STARTED_MOVES;
3711 if (oldi > next_out) {
3712 SendToPlayer(&buf[next_out], oldi - next_out);
3715 case H_GOT_UNREQ_HEADER:
3716 ics_getting_history = H_GETTING_MOVES;
3717 started = STARTED_MOVES_NOHIDE;
3720 case H_GOT_UNWANTED_HEADER:
3721 ics_getting_history = H_FALSE;
3727 if (looking_at(buf, &i, "% ") ||
3728 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3729 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3730 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3731 soughtPending = FALSE;
3735 if(suppressKibitz) next_out = i;
3736 savingComment = FALSE;
3740 case STARTED_MOVES_NOHIDE:
3741 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3742 parse[parse_pos + i - oldi] = NULLCHAR;
3743 ParseGameHistory(parse);
3745 if (appData.zippyPlay && first.initDone) {
3746 FeedMovesToProgram(&first, forwardMostMove);
3747 if (gameMode == IcsPlayingWhite) {
3748 if (WhiteOnMove(forwardMostMove)) {
3749 if (first.sendTime) {
3750 if (first.useColors) {
3751 SendToProgram("black\n", &first);
3753 SendTimeRemaining(&first, TRUE);
3755 if (first.useColors) {
3756 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3758 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3759 first.maybeThinking = TRUE;
3761 if (first.usePlayother) {
3762 if (first.sendTime) {
3763 SendTimeRemaining(&first, TRUE);
3765 SendToProgram("playother\n", &first);
3771 } else if (gameMode == IcsPlayingBlack) {
3772 if (!WhiteOnMove(forwardMostMove)) {
3773 if (first.sendTime) {
3774 if (first.useColors) {
3775 SendToProgram("white\n", &first);
3777 SendTimeRemaining(&first, FALSE);
3779 if (first.useColors) {
3780 SendToProgram("black\n", &first);
3782 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3783 first.maybeThinking = TRUE;
3785 if (first.usePlayother) {
3786 if (first.sendTime) {
3787 SendTimeRemaining(&first, FALSE);
3789 SendToProgram("playother\n", &first);
3798 if (gameMode == IcsObserving && ics_gamenum == -1) {
3799 /* Moves came from oldmoves or moves command
3800 while we weren't doing anything else.
3802 currentMove = forwardMostMove;
3803 ClearHighlights();/*!!could figure this out*/
3804 flipView = appData.flipView;
3805 DrawPosition(TRUE, boards[currentMove]);
3806 DisplayBothClocks();
3807 snprintf(str, MSG_SIZ, "%s %s %s",
3808 gameInfo.white, _("vs."), gameInfo.black);
3812 /* Moves were history of an active game */
3813 if (gameInfo.resultDetails != NULL) {
3814 free(gameInfo.resultDetails);
3815 gameInfo.resultDetails = NULL;
3818 HistorySet(parseList, backwardMostMove,
3819 forwardMostMove, currentMove-1);
3820 DisplayMove(currentMove - 1);
3821 if (started == STARTED_MOVES) next_out = i;
3822 started = STARTED_NONE;
3823 ics_getting_history = H_FALSE;
3826 case STARTED_OBSERVE:
3827 started = STARTED_NONE;
3828 SendToICS(ics_prefix);
3829 SendToICS("refresh\n");
3835 if(bookHit) { // [HGM] book: simulate book reply
3836 static char bookMove[MSG_SIZ]; // a bit generous?
3838 programStats.nodes = programStats.depth = programStats.time =
3839 programStats.score = programStats.got_only_move = 0;
3840 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3842 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3843 strcat(bookMove, bookHit);
3844 HandleMachineMove(bookMove, &first);
3849 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3850 started == STARTED_HOLDINGS ||
3851 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3852 /* Accumulate characters in move list or board */
3853 parse[parse_pos++] = buf[i];
3856 /* Start of game messages. Mostly we detect start of game
3857 when the first board image arrives. On some versions
3858 of the ICS, though, we need to do a "refresh" after starting
3859 to observe in order to get the current board right away. */
3860 if (looking_at(buf, &i, "Adding game * to observation list")) {
3861 started = STARTED_OBSERVE;
3865 /* Handle auto-observe */
3866 if (appData.autoObserve &&
3867 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3868 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3870 /* Choose the player that was highlighted, if any. */
3871 if (star_match[0][0] == '\033' ||
3872 star_match[1][0] != '\033') {
3873 player = star_match[0];
3875 player = star_match[2];
3877 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3878 ics_prefix, StripHighlightAndTitle(player));
3881 /* Save ratings from notify string */
3882 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3883 player1Rating = string_to_rating(star_match[1]);
3884 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3885 player2Rating = string_to_rating(star_match[3]);
3887 if (appData.debugMode)
3889 "Ratings from 'Game notification:' %s %d, %s %d\n",
3890 player1Name, player1Rating,
3891 player2Name, player2Rating);
3896 /* Deal with automatic examine mode after a game,
3897 and with IcsObserving -> IcsExamining transition */
3898 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3899 looking_at(buf, &i, "has made you an examiner of game *")) {
3901 int gamenum = atoi(star_match[0]);
3902 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3903 gamenum == ics_gamenum) {
3904 /* We were already playing or observing this game;
3905 no need to refetch history */
3906 gameMode = IcsExamining;
3908 pauseExamForwardMostMove = forwardMostMove;
3909 } else if (currentMove < forwardMostMove) {
3910 ForwardInner(forwardMostMove);
3913 /* I don't think this case really can happen */
3914 SendToICS(ics_prefix);
3915 SendToICS("refresh\n");
3920 /* Error messages */
3921 // if (ics_user_moved) {
3922 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3923 if (looking_at(buf, &i, "Illegal move") ||
3924 looking_at(buf, &i, "Not a legal move") ||
3925 looking_at(buf, &i, "Your king is in check") ||
3926 looking_at(buf, &i, "It isn't your turn") ||
3927 looking_at(buf, &i, "It is not your move")) {
3929 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3930 currentMove = forwardMostMove-1;
3931 DisplayMove(currentMove - 1); /* before DMError */
3932 DrawPosition(FALSE, boards[currentMove]);
3933 SwitchClocks(forwardMostMove-1); // [HGM] race
3934 DisplayBothClocks();
3936 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3942 if (looking_at(buf, &i, "still have time") ||
3943 looking_at(buf, &i, "not out of time") ||
3944 looking_at(buf, &i, "either player is out of time") ||
3945 looking_at(buf, &i, "has timeseal; checking")) {
3946 /* We must have called his flag a little too soon */
3947 whiteFlag = blackFlag = FALSE;
3951 if (looking_at(buf, &i, "added * seconds to") ||
3952 looking_at(buf, &i, "seconds were added to")) {
3953 /* Update the clocks */
3954 SendToICS(ics_prefix);
3955 SendToICS("refresh\n");
3959 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3960 ics_clock_paused = TRUE;
3965 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3966 ics_clock_paused = FALSE;
3971 /* Grab player ratings from the Creating: message.
3972 Note we have to check for the special case when
3973 the ICS inserts things like [white] or [black]. */
3974 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3975 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3977 0 player 1 name (not necessarily white)
3979 2 empty, white, or black (IGNORED)
3980 3 player 2 name (not necessarily black)
3983 The names/ratings are sorted out when the game
3984 actually starts (below).
3986 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3987 player1Rating = string_to_rating(star_match[1]);
3988 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3989 player2Rating = string_to_rating(star_match[4]);
3991 if (appData.debugMode)
3993 "Ratings from 'Creating:' %s %d, %s %d\n",
3994 player1Name, player1Rating,
3995 player2Name, player2Rating);
4000 /* Improved generic start/end-of-game messages */
4001 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4002 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4003 /* If tkind == 0: */
4004 /* star_match[0] is the game number */
4005 /* [1] is the white player's name */
4006 /* [2] is the black player's name */
4007 /* For end-of-game: */
4008 /* [3] is the reason for the game end */
4009 /* [4] is a PGN end game-token, preceded by " " */
4010 /* For start-of-game: */
4011 /* [3] begins with "Creating" or "Continuing" */
4012 /* [4] is " *" or empty (don't care). */
4013 int gamenum = atoi(star_match[0]);
4014 char *whitename, *blackname, *why, *endtoken;
4015 ChessMove endtype = EndOfFile;
4018 whitename = star_match[1];
4019 blackname = star_match[2];
4020 why = star_match[3];
4021 endtoken = star_match[4];
4023 whitename = star_match[1];
4024 blackname = star_match[3];
4025 why = star_match[5];
4026 endtoken = star_match[6];
4029 /* Game start messages */
4030 if (strncmp(why, "Creating ", 9) == 0 ||
4031 strncmp(why, "Continuing ", 11) == 0) {
4032 gs_gamenum = gamenum;
4033 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4034 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4035 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4037 if (appData.zippyPlay) {
4038 ZippyGameStart(whitename, blackname);
4041 partnerBoardValid = FALSE; // [HGM] bughouse
4045 /* Game end messages */
4046 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4047 ics_gamenum != gamenum) {
4050 while (endtoken[0] == ' ') endtoken++;
4051 switch (endtoken[0]) {
4054 endtype = GameUnfinished;
4057 endtype = BlackWins;
4060 if (endtoken[1] == '/')
4061 endtype = GameIsDrawn;
4063 endtype = WhiteWins;
4066 GameEnds(endtype, why, GE_ICS);
4068 if (appData.zippyPlay && first.initDone) {
4069 ZippyGameEnd(endtype, why);
4070 if (first.pr == NoProc) {
4071 /* Start the next process early so that we'll
4072 be ready for the next challenge */
4073 StartChessProgram(&first);
4075 /* Send "new" early, in case this command takes
4076 a long time to finish, so that we'll be ready
4077 for the next challenge. */
4078 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4082 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4086 if (looking_at(buf, &i, "Removing game * from observation") ||
4087 looking_at(buf, &i, "no longer observing game *") ||
4088 looking_at(buf, &i, "Game * (*) has no examiners")) {
4089 if (gameMode == IcsObserving &&
4090 atoi(star_match[0]) == ics_gamenum)
4092 /* icsEngineAnalyze */
4093 if (appData.icsEngineAnalyze) {
4100 ics_user_moved = FALSE;
4105 if (looking_at(buf, &i, "no longer examining game *")) {
4106 if (gameMode == IcsExamining &&
4107 atoi(star_match[0]) == ics_gamenum)
4111 ics_user_moved = FALSE;
4116 /* Advance leftover_start past any newlines we find,
4117 so only partial lines can get reparsed */
4118 if (looking_at(buf, &i, "\n")) {
4119 prevColor = curColor;
4120 if (curColor != ColorNormal) {
4121 if (oldi > next_out) {
4122 SendToPlayer(&buf[next_out], oldi - next_out);
4125 Colorize(ColorNormal, FALSE);
4126 curColor = ColorNormal;
4128 if (started == STARTED_BOARD) {
4129 started = STARTED_NONE;
4130 parse[parse_pos] = NULLCHAR;
4131 ParseBoard12(parse);
4134 /* Send premove here */
4135 if (appData.premove) {
4137 if (currentMove == 0 &&
4138 gameMode == IcsPlayingWhite &&
4139 appData.premoveWhite) {
4140 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4141 if (appData.debugMode)
4142 fprintf(debugFP, "Sending premove:\n");
4144 } else if (currentMove == 1 &&
4145 gameMode == IcsPlayingBlack &&
4146 appData.premoveBlack) {
4147 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4148 if (appData.debugMode)
4149 fprintf(debugFP, "Sending premove:\n");
4151 } else if (gotPremove) {
4153 ClearPremoveHighlights();
4154 if (appData.debugMode)
4155 fprintf(debugFP, "Sending premove:\n");
4156 UserMoveEvent(premoveFromX, premoveFromY,
4157 premoveToX, premoveToY,
4162 /* Usually suppress following prompt */
4163 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4164 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4165 if (looking_at(buf, &i, "*% ")) {
4166 savingComment = FALSE;
4171 } else if (started == STARTED_HOLDINGS) {
4173 char new_piece[MSG_SIZ];
4174 started = STARTED_NONE;
4175 parse[parse_pos] = NULLCHAR;
4176 if (appData.debugMode)
4177 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4178 parse, currentMove);
4179 if (sscanf(parse, " game %d", &gamenum) == 1) {
4180 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4181 if (gameInfo.variant == VariantNormal) {
4182 /* [HGM] We seem to switch variant during a game!
4183 * Presumably no holdings were displayed, so we have
4184 * to move the position two files to the right to
4185 * create room for them!
4187 VariantClass newVariant;
4188 switch(gameInfo.boardWidth) { // base guess on board width
4189 case 9: newVariant = VariantShogi; break;
4190 case 10: newVariant = VariantGreat; break;
4191 default: newVariant = VariantCrazyhouse; break;
4193 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4194 /* Get a move list just to see the header, which
4195 will tell us whether this is really bug or zh */
4196 if (ics_getting_history == H_FALSE) {
4197 ics_getting_history = H_REQUESTED;
4198 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4202 new_piece[0] = NULLCHAR;
4203 sscanf(parse, "game %d white [%s black [%s <- %s",
4204 &gamenum, white_holding, black_holding,
4206 white_holding[strlen(white_holding)-1] = NULLCHAR;
4207 black_holding[strlen(black_holding)-1] = NULLCHAR;
4208 /* [HGM] copy holdings to board holdings area */
4209 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4210 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4211 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4213 if (appData.zippyPlay && first.initDone) {
4214 ZippyHoldings(white_holding, black_holding,
4218 if (tinyLayout || smallLayout) {
4219 char wh[16], bh[16];
4220 PackHolding(wh, white_holding);
4221 PackHolding(bh, black_holding);
4222 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4223 gameInfo.white, gameInfo.black);
4225 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4226 gameInfo.white, white_holding, _("vs."),
4227 gameInfo.black, black_holding);
4229 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4230 DrawPosition(FALSE, boards[currentMove]);
4232 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4233 sscanf(parse, "game %d white [%s black [%s <- %s",
4234 &gamenum, white_holding, black_holding,
4236 white_holding[strlen(white_holding)-1] = NULLCHAR;
4237 black_holding[strlen(black_holding)-1] = NULLCHAR;
4238 /* [HGM] copy holdings to partner-board holdings area */
4239 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4240 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4241 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4242 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4243 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4246 /* Suppress following prompt */
4247 if (looking_at(buf, &i, "*% ")) {
4248 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4249 savingComment = FALSE;
4257 i++; /* skip unparsed character and loop back */
4260 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4261 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4262 // SendToPlayer(&buf[next_out], i - next_out);
4263 started != STARTED_HOLDINGS && leftover_start > next_out) {
4264 SendToPlayer(&buf[next_out], leftover_start - next_out);
4268 leftover_len = buf_len - leftover_start;
4269 /* if buffer ends with something we couldn't parse,
4270 reparse it after appending the next read */
4272 } else if (count == 0) {
4273 RemoveInputSource(isr);
4274 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4276 DisplayFatalError(_("Error reading from ICS"), error, 1);
4281 /* Board style 12 looks like this:
4283 <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
4285 * The "<12> " is stripped before it gets to this routine. The two
4286 * trailing 0's (flip state and clock ticking) are later addition, and
4287 * some chess servers may not have them, or may have only the first.
4288 * Additional trailing fields may be added in the future.
4291 #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"
4293 #define RELATION_OBSERVING_PLAYED 0
4294 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4295 #define RELATION_PLAYING_MYMOVE 1
4296 #define RELATION_PLAYING_NOTMYMOVE -1
4297 #define RELATION_EXAMINING 2
4298 #define RELATION_ISOLATED_BOARD -3
4299 #define RELATION_STARTING_POSITION -4 /* FICS only */
4302 ParseBoard12 (char *string)
4306 char *bookHit = NULL; // [HGM] book
4308 GameMode newGameMode;
4309 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4310 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4311 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4312 char to_play, board_chars[200];
4313 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4314 char black[32], white[32];
4316 int prevMove = currentMove;
4319 int fromX, fromY, toX, toY;
4321 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4322 Boolean weird = FALSE, reqFlag = FALSE;
4324 fromX = fromY = toX = toY = -1;
4328 if (appData.debugMode)
4329 fprintf(debugFP, "Parsing board: %s\n", string);
4331 move_str[0] = NULLCHAR;
4332 elapsed_time[0] = NULLCHAR;
4333 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4335 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4336 if(string[i] == ' ') { ranks++; files = 0; }
4338 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4341 for(j = 0; j <i; j++) board_chars[j] = string[j];
4342 board_chars[i] = '\0';
4345 n = sscanf(string, PATTERN, &to_play, &double_push,
4346 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4347 &gamenum, white, black, &relation, &basetime, &increment,
4348 &white_stren, &black_stren, &white_time, &black_time,
4349 &moveNum, str, elapsed_time, move_str, &ics_flip,
4353 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4354 DisplayError(str, 0);
4358 /* Convert the move number to internal form */
4359 moveNum = (moveNum - 1) * 2;
4360 if (to_play == 'B') moveNum++;
4361 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4362 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4368 case RELATION_OBSERVING_PLAYED:
4369 case RELATION_OBSERVING_STATIC:
4370 if (gamenum == -1) {
4371 /* Old ICC buglet */
4372 relation = RELATION_OBSERVING_STATIC;
4374 newGameMode = IcsObserving;
4376 case RELATION_PLAYING_MYMOVE:
4377 case RELATION_PLAYING_NOTMYMOVE:
4379 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4380 IcsPlayingWhite : IcsPlayingBlack;
4381 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4383 case RELATION_EXAMINING:
4384 newGameMode = IcsExamining;
4386 case RELATION_ISOLATED_BOARD:
4388 /* Just display this board. If user was doing something else,
4389 we will forget about it until the next board comes. */
4390 newGameMode = IcsIdle;
4392 case RELATION_STARTING_POSITION:
4393 newGameMode = gameMode;
4397 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4398 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4399 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4400 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4401 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4402 static int lastBgGame = -1;
4404 for (k = 0; k < ranks; k++) {
4405 for (j = 0; j < files; j++)
4406 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4407 if(gameInfo.holdingsWidth > 1) {
4408 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4409 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4412 CopyBoard(partnerBoard, board);
4413 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4414 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4415 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4416 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4417 if(toSqr = strchr(str, '-')) {
4418 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4419 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4420 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4421 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4422 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4423 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4425 DisplayWhiteClock(white_time*fac, to_play == 'W');
4426 DisplayBlackClock(black_time*fac, to_play != 'W');
4427 activePartner = to_play;
4428 if(gamenum != lastBgGame) {
4430 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4433 lastBgGame = gamenum;
4434 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4435 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4436 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4437 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4438 if(!twoBoards) DisplayMessage(partnerStatus, "");
4439 partnerBoardValid = TRUE;
4443 if(appData.dualBoard && appData.bgObserve) {
4444 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4445 SendToICS(ics_prefix), SendToICS("pobserve\n");
4446 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4448 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4453 /* Modify behavior for initial board display on move listing
4456 switch (ics_getting_history) {
4460 case H_GOT_REQ_HEADER:
4461 case H_GOT_UNREQ_HEADER:
4462 /* This is the initial position of the current game */
4463 gamenum = ics_gamenum;
4464 moveNum = 0; /* old ICS bug workaround */
4465 if (to_play == 'B') {
4466 startedFromSetupPosition = TRUE;
4467 blackPlaysFirst = TRUE;
4469 if (forwardMostMove == 0) forwardMostMove = 1;
4470 if (backwardMostMove == 0) backwardMostMove = 1;
4471 if (currentMove == 0) currentMove = 1;
4473 newGameMode = gameMode;
4474 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4476 case H_GOT_UNWANTED_HEADER:
4477 /* This is an initial board that we don't want */
4479 case H_GETTING_MOVES:
4480 /* Should not happen */
4481 DisplayError(_("Error gathering move list: extra board"), 0);
4482 ics_getting_history = H_FALSE;
4486 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4487 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4488 weird && (int)gameInfo.variant < (int)VariantShogi) {
4489 /* [HGM] We seem to have switched variant unexpectedly
4490 * Try to guess new variant from board size
4492 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4493 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4494 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4495 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4496 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4497 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4498 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4499 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4500 /* Get a move list just to see the header, which
4501 will tell us whether this is really bug or zh */
4502 if (ics_getting_history == H_FALSE) {
4503 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4504 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4509 /* Take action if this is the first board of a new game, or of a
4510 different game than is currently being displayed. */
4511 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4512 relation == RELATION_ISOLATED_BOARD) {
4514 /* Forget the old game and get the history (if any) of the new one */
4515 if (gameMode != BeginningOfGame) {
4519 if (appData.autoRaiseBoard) BoardToTop();
4521 if (gamenum == -1) {
4522 newGameMode = IcsIdle;
4523 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4524 appData.getMoveList && !reqFlag) {
4525 /* Need to get game history */
4526 ics_getting_history = H_REQUESTED;
4527 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4531 /* Initially flip the board to have black on the bottom if playing
4532 black or if the ICS flip flag is set, but let the user change
4533 it with the Flip View button. */
4534 flipView = appData.autoFlipView ?
4535 (newGameMode == IcsPlayingBlack) || ics_flip :
4538 /* Done with values from previous mode; copy in new ones */
4539 gameMode = newGameMode;
4541 ics_gamenum = gamenum;
4542 if (gamenum == gs_gamenum) {
4543 int klen = strlen(gs_kind);
4544 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4545 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4546 gameInfo.event = StrSave(str);
4548 gameInfo.event = StrSave("ICS game");
4550 gameInfo.site = StrSave(appData.icsHost);
4551 gameInfo.date = PGNDate();
4552 gameInfo.round = StrSave("-");
4553 gameInfo.white = StrSave(white);
4554 gameInfo.black = StrSave(black);
4555 timeControl = basetime * 60 * 1000;
4557 timeIncrement = increment * 1000;
4558 movesPerSession = 0;
4559 gameInfo.timeControl = TimeControlTagValue();
4560 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4561 if (appData.debugMode) {
4562 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4563 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4564 setbuf(debugFP, NULL);
4567 gameInfo.outOfBook = NULL;
4569 /* Do we have the ratings? */
4570 if (strcmp(player1Name, white) == 0 &&
4571 strcmp(player2Name, black) == 0) {
4572 if (appData.debugMode)
4573 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4574 player1Rating, player2Rating);
4575 gameInfo.whiteRating = player1Rating;
4576 gameInfo.blackRating = player2Rating;
4577 } else if (strcmp(player2Name, white) == 0 &&
4578 strcmp(player1Name, black) == 0) {
4579 if (appData.debugMode)
4580 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4581 player2Rating, player1Rating);
4582 gameInfo.whiteRating = player2Rating;
4583 gameInfo.blackRating = player1Rating;
4585 player1Name[0] = player2Name[0] = NULLCHAR;
4587 /* Silence shouts if requested */
4588 if (appData.quietPlay &&
4589 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4590 SendToICS(ics_prefix);
4591 SendToICS("set shout 0\n");
4595 /* Deal with midgame name changes */
4597 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4598 if (gameInfo.white) free(gameInfo.white);
4599 gameInfo.white = StrSave(white);
4601 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4602 if (gameInfo.black) free(gameInfo.black);
4603 gameInfo.black = StrSave(black);
4607 /* Throw away game result if anything actually changes in examine mode */
4608 if (gameMode == IcsExamining && !newGame) {
4609 gameInfo.result = GameUnfinished;
4610 if (gameInfo.resultDetails != NULL) {
4611 free(gameInfo.resultDetails);
4612 gameInfo.resultDetails = NULL;
4616 /* In pausing && IcsExamining mode, we ignore boards coming
4617 in if they are in a different variation than we are. */
4618 if (pauseExamInvalid) return;
4619 if (pausing && gameMode == IcsExamining) {
4620 if (moveNum <= pauseExamForwardMostMove) {
4621 pauseExamInvalid = TRUE;
4622 forwardMostMove = pauseExamForwardMostMove;
4627 if (appData.debugMode) {
4628 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4630 /* Parse the board */
4631 for (k = 0; k < ranks; k++) {
4632 for (j = 0; j < files; j++)
4633 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4634 if(gameInfo.holdingsWidth > 1) {
4635 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4636 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4639 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4640 board[5][BOARD_RGHT+1] = WhiteAngel;
4641 board[6][BOARD_RGHT+1] = WhiteMarshall;
4642 board[1][0] = BlackMarshall;
4643 board[2][0] = BlackAngel;
4644 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4646 CopyBoard(boards[moveNum], board);
4647 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4649 startedFromSetupPosition =
4650 !CompareBoards(board, initialPosition);
4651 if(startedFromSetupPosition)
4652 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4655 /* [HGM] Set castling rights. Take the outermost Rooks,
4656 to make it also work for FRC opening positions. Note that board12
4657 is really defective for later FRC positions, as it has no way to
4658 indicate which Rook can castle if they are on the same side of King.
4659 For the initial position we grant rights to the outermost Rooks,
4660 and remember thos rights, and we then copy them on positions
4661 later in an FRC game. This means WB might not recognize castlings with
4662 Rooks that have moved back to their original position as illegal,
4663 but in ICS mode that is not its job anyway.
4665 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4666 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4668 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4669 if(board[0][i] == WhiteRook) j = i;
4670 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4671 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4672 if(board[0][i] == WhiteRook) j = i;
4673 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4674 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4675 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4676 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4677 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4678 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4679 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4681 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4682 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4683 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4684 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4685 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4686 if(board[BOARD_HEIGHT-1][k] == bKing)
4687 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4688 if(gameInfo.variant == VariantTwoKings) {
4689 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4690 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4691 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4694 r = boards[moveNum][CASTLING][0] = initialRights[0];
4695 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4696 r = boards[moveNum][CASTLING][1] = initialRights[1];
4697 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4698 r = boards[moveNum][CASTLING][3] = initialRights[3];
4699 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4700 r = boards[moveNum][CASTLING][4] = initialRights[4];
4701 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4702 /* wildcastle kludge: always assume King has rights */
4703 r = boards[moveNum][CASTLING][2] = initialRights[2];
4704 r = boards[moveNum][CASTLING][5] = initialRights[5];
4706 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4707 boards[moveNum][EP_STATUS] = EP_NONE;
4708 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4709 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4710 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4713 if (ics_getting_history == H_GOT_REQ_HEADER ||
4714 ics_getting_history == H_GOT_UNREQ_HEADER) {
4715 /* This was an initial position from a move list, not
4716 the current position */
4720 /* Update currentMove and known move number limits */
4721 newMove = newGame || moveNum > forwardMostMove;
4724 forwardMostMove = backwardMostMove = currentMove = moveNum;
4725 if (gameMode == IcsExamining && moveNum == 0) {
4726 /* Workaround for ICS limitation: we are not told the wild
4727 type when starting to examine a game. But if we ask for
4728 the move list, the move list header will tell us */
4729 ics_getting_history = H_REQUESTED;
4730 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4733 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4734 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4736 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4737 /* [HGM] applied this also to an engine that is silently watching */
4738 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4739 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4740 gameInfo.variant == currentlyInitializedVariant) {
4741 takeback = forwardMostMove - moveNum;
4742 for (i = 0; i < takeback; i++) {
4743 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4744 SendToProgram("undo\n", &first);
4749 forwardMostMove = moveNum;
4750 if (!pausing || currentMove > forwardMostMove)
4751 currentMove = forwardMostMove;
4753 /* New part of history that is not contiguous with old part */
4754 if (pausing && gameMode == IcsExamining) {
4755 pauseExamInvalid = TRUE;
4756 forwardMostMove = pauseExamForwardMostMove;
4759 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4761 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4762 // [HGM] when we will receive the move list we now request, it will be
4763 // fed to the engine from the first move on. So if the engine is not
4764 // in the initial position now, bring it there.
4765 InitChessProgram(&first, 0);
4768 ics_getting_history = H_REQUESTED;
4769 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4772 forwardMostMove = backwardMostMove = currentMove = moveNum;
4775 /* Update the clocks */
4776 if (strchr(elapsed_time, '.')) {
4778 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4779 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4781 /* Time is in seconds */
4782 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4783 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4788 if (appData.zippyPlay && newGame &&
4789 gameMode != IcsObserving && gameMode != IcsIdle &&
4790 gameMode != IcsExamining)
4791 ZippyFirstBoard(moveNum, basetime, increment);
4794 /* Put the move on the move list, first converting
4795 to canonical algebraic form. */
4797 if (appData.debugMode) {
4798 int f = forwardMostMove;
4799 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4800 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4801 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4802 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4803 fprintf(debugFP, "moveNum = %d\n", moveNum);
4804 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4805 setbuf(debugFP, NULL);
4807 if (moveNum <= backwardMostMove) {
4808 /* We don't know what the board looked like before
4810 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4811 strcat(parseList[moveNum - 1], " ");
4812 strcat(parseList[moveNum - 1], elapsed_time);
4813 moveList[moveNum - 1][0] = NULLCHAR;
4814 } else if (strcmp(move_str, "none") == 0) {
4815 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4816 /* Again, we don't know what the board looked like;
4817 this is really the start of the game. */
4818 parseList[moveNum - 1][0] = NULLCHAR;
4819 moveList[moveNum - 1][0] = NULLCHAR;
4820 backwardMostMove = moveNum;
4821 startedFromSetupPosition = TRUE;
4822 fromX = fromY = toX = toY = -1;
4824 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4825 // So we parse the long-algebraic move string in stead of the SAN move
4826 int valid; char buf[MSG_SIZ], *prom;
4828 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4829 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4830 // str looks something like "Q/a1-a2"; kill the slash
4832 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4833 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4834 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4835 strcat(buf, prom); // long move lacks promo specification!
4836 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4837 if(appData.debugMode)
4838 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4839 safeStrCpy(move_str, buf, MSG_SIZ);
4841 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4842 &fromX, &fromY, &toX, &toY, &promoChar)
4843 || ParseOneMove(buf, moveNum - 1, &moveType,
4844 &fromX, &fromY, &toX, &toY, &promoChar);
4845 // end of long SAN patch
4847 (void) CoordsToAlgebraic(boards[moveNum - 1],
4848 PosFlags(moveNum - 1),
4849 fromY, fromX, toY, toX, promoChar,
4850 parseList[moveNum-1]);
4851 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4857 if(!IS_SHOGI(gameInfo.variant))
4858 strcat(parseList[moveNum - 1], "+");
4861 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4862 strcat(parseList[moveNum - 1], "#");
4865 strcat(parseList[moveNum - 1], " ");
4866 strcat(parseList[moveNum - 1], elapsed_time);
4867 /* currentMoveString is set as a side-effect of ParseOneMove */
4868 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4869 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4870 strcat(moveList[moveNum - 1], "\n");
4872 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4873 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4874 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4875 ChessSquare old, new = boards[moveNum][k][j];
4876 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4877 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4878 if(old == new) continue;
4879 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4880 else if(new == WhiteWazir || new == BlackWazir) {
4881 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4882 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4883 else boards[moveNum][k][j] = old; // preserve type of Gold
4884 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4885 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4888 /* Move from ICS was illegal!? Punt. */
4889 if (appData.debugMode) {
4890 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4891 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4893 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4894 strcat(parseList[moveNum - 1], " ");
4895 strcat(parseList[moveNum - 1], elapsed_time);
4896 moveList[moveNum - 1][0] = NULLCHAR;
4897 fromX = fromY = toX = toY = -1;
4900 if (appData.debugMode) {
4901 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4902 setbuf(debugFP, NULL);
4906 /* Send move to chess program (BEFORE animating it). */
4907 if (appData.zippyPlay && !newGame && newMove &&
4908 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4910 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4911 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4912 if (moveList[moveNum - 1][0] == NULLCHAR) {
4913 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4915 DisplayError(str, 0);
4917 if (first.sendTime) {
4918 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4920 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4921 if (firstMove && !bookHit) {
4923 if (first.useColors) {
4924 SendToProgram(gameMode == IcsPlayingWhite ?
4926 "black\ngo\n", &first);
4928 SendToProgram("go\n", &first);
4930 first.maybeThinking = TRUE;
4933 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4934 if (moveList[moveNum - 1][0] == NULLCHAR) {
4935 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4936 DisplayError(str, 0);
4938 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4939 SendMoveToProgram(moveNum - 1, &first);
4946 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4947 /* If move comes from a remote source, animate it. If it
4948 isn't remote, it will have already been animated. */
4949 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4950 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4952 if (!pausing && appData.highlightLastMove) {
4953 SetHighlights(fromX, fromY, toX, toY);
4957 /* Start the clocks */
4958 whiteFlag = blackFlag = FALSE;
4959 appData.clockMode = !(basetime == 0 && increment == 0);
4961 ics_clock_paused = TRUE;
4963 } else if (ticking == 1) {
4964 ics_clock_paused = FALSE;
4966 if (gameMode == IcsIdle ||
4967 relation == RELATION_OBSERVING_STATIC ||
4968 relation == RELATION_EXAMINING ||
4970 DisplayBothClocks();
4974 /* Display opponents and material strengths */
4975 if (gameInfo.variant != VariantBughouse &&
4976 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4977 if (tinyLayout || smallLayout) {
4978 if(gameInfo.variant == VariantNormal)
4979 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4980 gameInfo.white, white_stren, gameInfo.black, black_stren,
4981 basetime, increment);
4983 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4984 gameInfo.white, white_stren, gameInfo.black, black_stren,
4985 basetime, increment, (int) gameInfo.variant);
4987 if(gameInfo.variant == VariantNormal)
4988 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4989 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4990 basetime, increment);
4992 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4993 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4994 basetime, increment, VariantName(gameInfo.variant));
4997 if (appData.debugMode) {
4998 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5003 /* Display the board */
5004 if (!pausing && !appData.noGUI) {
5006 if (appData.premove)
5008 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5009 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5010 ClearPremoveHighlights();
5012 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5013 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5014 DrawPosition(j, boards[currentMove]);
5016 DisplayMove(moveNum - 1);
5017 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5018 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5019 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
5020 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5024 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5026 if(bookHit) { // [HGM] book: simulate book reply
5027 static char bookMove[MSG_SIZ]; // a bit generous?
5029 programStats.nodes = programStats.depth = programStats.time =
5030 programStats.score = programStats.got_only_move = 0;
5031 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5033 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5034 strcat(bookMove, bookHit);
5035 HandleMachineMove(bookMove, &first);
5044 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5045 ics_getting_history = H_REQUESTED;
5046 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5052 SendToBoth (char *msg)
5053 { // to make it easy to keep two engines in step in dual analysis
5054 SendToProgram(msg, &first);
5055 if(second.analyzing) SendToProgram(msg, &second);
5059 AnalysisPeriodicEvent (int force)
5061 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5062 && !force) || !appData.periodicUpdates)
5065 /* Send . command to Crafty to collect stats */
5068 /* Don't send another until we get a response (this makes
5069 us stop sending to old Crafty's which don't understand
5070 the "." command (sending illegal cmds resets node count & time,
5071 which looks bad)) */
5072 programStats.ok_to_send = 0;
5076 ics_update_width (int new_width)
5078 ics_printf("set width %d\n", new_width);
5082 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5086 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5087 if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5088 sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5089 SendToProgram(buf, cps);
5092 // null move in variant where engine does not understand it (for analysis purposes)
5093 SendBoard(cps, moveNum + 1); // send position after move in stead.
5096 if (cps->useUsermove) {
5097 SendToProgram("usermove ", cps);
5101 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5102 int len = space - parseList[moveNum];
5103 memcpy(buf, parseList[moveNum], len);
5105 buf[len] = NULLCHAR;
5107 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5109 SendToProgram(buf, cps);
5111 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5112 AlphaRank(moveList[moveNum], 4);
5113 SendToProgram(moveList[moveNum], cps);
5114 AlphaRank(moveList[moveNum], 4); // and back
5116 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5117 * the engine. It would be nice to have a better way to identify castle
5119 if(appData.fischerCastling && cps->useOOCastle) {
5120 int fromX = moveList[moveNum][0] - AAA;
5121 int fromY = moveList[moveNum][1] - ONE;
5122 int toX = moveList[moveNum][2] - AAA;
5123 int toY = moveList[moveNum][3] - ONE;
5124 if((boards[moveNum][fromY][fromX] == WhiteKing
5125 && boards[moveNum][toY][toX] == WhiteRook)
5126 || (boards[moveNum][fromY][fromX] == BlackKing
5127 && boards[moveNum][toY][toX] == BlackRook)) {
5128 if(toX > fromX) SendToProgram("O-O\n", cps);
5129 else SendToProgram("O-O-O\n", cps);
5131 else SendToProgram(moveList[moveNum], cps);
5133 if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5134 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5135 moveList[moveNum][5], moveList[moveNum][6] - '0',
5136 moveList[moveNum][5], moveList[moveNum][6] - '0',
5137 moveList[moveNum][2], moveList[moveNum][3] - '0');
5138 SendToProgram(buf, cps);
5140 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5141 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5142 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5143 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5144 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5146 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5147 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5148 SendToProgram(buf, cps);
5150 else SendToProgram(moveList[moveNum], cps);
5151 /* End of additions by Tord */
5154 /* [HGM] setting up the opening has brought engine in force mode! */
5155 /* Send 'go' if we are in a mode where machine should play. */
5156 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5157 (gameMode == TwoMachinesPlay ||
5159 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5161 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5162 SendToProgram("go\n", cps);
5163 if (appData.debugMode) {
5164 fprintf(debugFP, "(extra)\n");
5167 setboardSpoiledMachineBlack = 0;
5171 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5173 char user_move[MSG_SIZ];
5176 if(gameInfo.variant == VariantSChess && promoChar) {
5177 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5178 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5179 } else suffix[0] = NULLCHAR;
5183 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5184 (int)moveType, fromX, fromY, toX, toY);
5185 DisplayError(user_move + strlen("say "), 0);
5187 case WhiteKingSideCastle:
5188 case BlackKingSideCastle:
5189 case WhiteQueenSideCastleWild:
5190 case BlackQueenSideCastleWild:
5192 case WhiteHSideCastleFR:
5193 case BlackHSideCastleFR:
5195 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5197 case WhiteQueenSideCastle:
5198 case BlackQueenSideCastle:
5199 case WhiteKingSideCastleWild:
5200 case BlackKingSideCastleWild:
5202 case WhiteASideCastleFR:
5203 case BlackASideCastleFR:
5205 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5207 case WhiteNonPromotion:
5208 case BlackNonPromotion:
5209 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5211 case WhitePromotion:
5212 case BlackPromotion:
5213 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5214 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5215 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5216 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5217 PieceToChar(WhiteFerz));
5218 else if(gameInfo.variant == VariantGreat)
5219 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5220 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5221 PieceToChar(WhiteMan));
5223 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5224 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5230 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5231 ToUpper(PieceToChar((ChessSquare) fromX)),
5232 AAA + toX, ONE + toY);
5234 case IllegalMove: /* could be a variant we don't quite understand */
5235 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5237 case WhiteCapturesEnPassant:
5238 case BlackCapturesEnPassant:
5239 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5240 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5243 SendToICS(user_move);
5244 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5245 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5250 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5251 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5252 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5253 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5254 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5257 if(gameMode != IcsExamining) { // is this ever not the case?
5258 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5260 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5261 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5262 } else { // on FICS we must first go to general examine mode
5263 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5265 if(gameInfo.variant != VariantNormal) {
5266 // try figure out wild number, as xboard names are not always valid on ICS
5267 for(i=1; i<=36; i++) {
5268 snprintf(buf, MSG_SIZ, "wild/%d", i);
5269 if(StringToVariant(buf) == gameInfo.variant) break;
5271 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5272 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5273 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5274 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5275 SendToICS(ics_prefix);
5277 if(startedFromSetupPosition || backwardMostMove != 0) {
5278 fen = PositionToFEN(backwardMostMove, NULL, 1);
5279 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5280 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5282 } else { // FICS: everything has to set by separate bsetup commands
5283 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5284 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5286 if(!WhiteOnMove(backwardMostMove)) {
5287 SendToICS("bsetup tomove black\n");
5289 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5290 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5292 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5293 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5295 i = boards[backwardMostMove][EP_STATUS];
5296 if(i >= 0) { // set e.p.
5297 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5303 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5304 SendToICS("bsetup done\n"); // switch to normal examining.
5306 for(i = backwardMostMove; i<last; i++) {
5308 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5309 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5310 int len = strlen(moveList[i]);
5311 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5312 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5316 SendToICS(ics_prefix);
5317 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5320 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5323 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5325 if (rf == DROP_RANK) {
5326 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5327 sprintf(move, "%c@%c%c\n",
5328 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5330 if (promoChar == 'x' || promoChar == NULLCHAR) {
5331 sprintf(move, "%c%c%c%c\n",
5332 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5333 if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5335 sprintf(move, "%c%c%c%c%c\n",
5336 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5342 ProcessICSInitScript (FILE *f)
5346 while (fgets(buf, MSG_SIZ, f)) {
5347 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5354 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5356 static ClickType lastClickType;
5359 Partner (ChessSquare *p)
5360 { // change piece into promotion partner if one shogi-promotes to the other
5361 int stride = gameInfo.variant == VariantChu ? 22 : 11;
5362 ChessSquare partner;
5363 partner = (*p/stride & 1 ? *p - stride : *p + stride);
5364 if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5372 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5373 static int toggleFlag;
5374 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5375 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5376 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5377 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5378 if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5379 if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5381 if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5382 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5383 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5384 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5385 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5386 if(!step) step = -1;
5387 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5388 !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5389 appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5390 (promoSweep == WhiteLion || promoSweep == BlackLion)));
5392 int victim = boards[currentMove][toY][toX];
5393 boards[currentMove][toY][toX] = promoSweep;
5394 DrawPosition(FALSE, boards[currentMove]);
5395 boards[currentMove][toY][toX] = victim;
5397 ChangeDragPiece(promoSweep);
5401 PromoScroll (int x, int y)
5405 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5406 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5407 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5408 if(!step) return FALSE;
5409 lastX = x; lastY = y;
5410 if((promoSweep < BlackPawn) == flipView) step = -step;
5411 if(step > 0) selectFlag = 1;
5412 if(!selectFlag) Sweep(step);
5417 NextPiece (int step)
5419 ChessSquare piece = boards[currentMove][toY][toX];
5422 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5423 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5424 if(!step) step = -1;
5425 } while(PieceToChar(pieceSweep) == '.');
5426 boards[currentMove][toY][toX] = pieceSweep;
5427 DrawPosition(FALSE, boards[currentMove]);
5428 boards[currentMove][toY][toX] = piece;
5430 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5432 AlphaRank (char *move, int n)
5434 // char *p = move, c; int x, y;
5436 if (appData.debugMode) {
5437 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5441 move[2]>='0' && move[2]<='9' &&
5442 move[3]>='a' && move[3]<='x' ) {
5444 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5445 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5447 if(move[0]>='0' && move[0]<='9' &&
5448 move[1]>='a' && move[1]<='x' &&
5449 move[2]>='0' && move[2]<='9' &&
5450 move[3]>='a' && move[3]<='x' ) {
5451 /* input move, Shogi -> normal */
5452 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5453 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5454 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5455 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5458 move[3]>='0' && move[3]<='9' &&
5459 move[2]>='a' && move[2]<='x' ) {
5461 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5462 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5465 move[0]>='a' && move[0]<='x' &&
5466 move[3]>='0' && move[3]<='9' &&
5467 move[2]>='a' && move[2]<='x' ) {
5468 /* output move, normal -> Shogi */
5469 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5470 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5471 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5472 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5473 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5475 if (appData.debugMode) {
5476 fprintf(debugFP, " out = '%s'\n", move);
5480 char yy_textstr[8000];
5482 /* Parser for moves from gnuchess, ICS, or user typein box */
5484 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5486 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5488 switch (*moveType) {
5489 case WhitePromotion:
5490 case BlackPromotion:
5491 case WhiteNonPromotion:
5492 case BlackNonPromotion:
5495 case WhiteCapturesEnPassant:
5496 case BlackCapturesEnPassant:
5497 case WhiteKingSideCastle:
5498 case WhiteQueenSideCastle:
5499 case BlackKingSideCastle:
5500 case BlackQueenSideCastle:
5501 case WhiteKingSideCastleWild:
5502 case WhiteQueenSideCastleWild:
5503 case BlackKingSideCastleWild:
5504 case BlackQueenSideCastleWild:
5505 /* Code added by Tord: */
5506 case WhiteHSideCastleFR:
5507 case WhiteASideCastleFR:
5508 case BlackHSideCastleFR:
5509 case BlackASideCastleFR:
5510 /* End of code added by Tord */
5511 case IllegalMove: /* bug or odd chess variant */
5512 *fromX = currentMoveString[0] - AAA;
5513 *fromY = currentMoveString[1] - ONE;
5514 *toX = currentMoveString[2] - AAA;
5515 *toY = currentMoveString[3] - ONE;
5516 *promoChar = currentMoveString[4];
5517 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5518 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5519 if (appData.debugMode) {
5520 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5522 *fromX = *fromY = *toX = *toY = 0;
5525 if (appData.testLegality) {
5526 return (*moveType != IllegalMove);
5528 return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5529 // [HGM] lion: if this is a double move we are less critical
5530 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5535 *fromX = *moveType == WhiteDrop ?
5536 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5537 (int) CharToPiece(ToLower(currentMoveString[0]));
5539 *toX = currentMoveString[2] - AAA;
5540 *toY = currentMoveString[3] - ONE;
5541 *promoChar = NULLCHAR;
5545 case ImpossibleMove:
5555 if (appData.debugMode) {
5556 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5559 *fromX = *fromY = *toX = *toY = 0;
5560 *promoChar = NULLCHAR;
5565 Boolean pushed = FALSE;
5566 char *lastParseAttempt;
5569 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5570 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5571 int fromX, fromY, toX, toY; char promoChar;
5576 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5577 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5578 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5581 endPV = forwardMostMove;
5583 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5584 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5585 lastParseAttempt = pv;
5586 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5587 if(!valid && nr == 0 &&
5588 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5589 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5590 // Hande case where played move is different from leading PV move
5591 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5592 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5593 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5594 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5595 endPV += 2; // if position different, keep this
5596 moveList[endPV-1][0] = fromX + AAA;
5597 moveList[endPV-1][1] = fromY + ONE;
5598 moveList[endPV-1][2] = toX + AAA;
5599 moveList[endPV-1][3] = toY + ONE;
5600 parseList[endPV-1][0] = NULLCHAR;
5601 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5604 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5605 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5606 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5607 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5608 valid++; // allow comments in PV
5612 if(endPV+1 > framePtr) break; // no space, truncate
5615 CopyBoard(boards[endPV], boards[endPV-1]);
5616 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5617 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5618 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5619 CoordsToAlgebraic(boards[endPV - 1],
5620 PosFlags(endPV - 1),
5621 fromY, fromX, toY, toX, promoChar,
5622 parseList[endPV - 1]);
5624 if(atEnd == 2) return; // used hidden, for PV conversion
5625 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5626 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5627 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5628 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5629 DrawPosition(TRUE, boards[currentMove]);
5633 MultiPV (ChessProgramState *cps)
5634 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5636 for(i=0; i<cps->nrOptions; i++)
5637 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5642 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5645 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5647 int startPV, multi, lineStart, origIndex = index;
5648 char *p, buf2[MSG_SIZ];
5649 ChessProgramState *cps = (pane ? &second : &first);
5651 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5652 lastX = x; lastY = y;
5653 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5654 lineStart = startPV = index;
5655 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5656 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5658 do{ while(buf[index] && buf[index] != '\n') index++;
5659 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5661 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5662 int n = cps->option[multi].value;
5663 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5664 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5665 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5666 cps->option[multi].value = n;
5669 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5670 ExcludeClick(origIndex - lineStart);
5672 } else if(!strncmp(buf+lineStart, "dep\t", 4)) { // column headers clicked
5673 Collapse(origIndex - lineStart);
5676 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5677 *start = startPV; *end = index-1;
5678 extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5685 static char buf[10*MSG_SIZ];
5686 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5688 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5689 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5690 for(i = forwardMostMove; i<endPV; i++){
5691 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5692 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5695 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5696 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5697 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5703 LoadPV (int x, int y)
5704 { // called on right mouse click to load PV
5705 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5706 lastX = x; lastY = y;
5707 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5715 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5716 if(endPV < 0) return;
5717 if(appData.autoCopyPV) CopyFENToClipboard();
5719 if(extendGame && currentMove > forwardMostMove) {
5720 Boolean saveAnimate = appData.animate;
5722 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5723 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5724 } else storedGames--; // abandon shelved tail of original game
5727 forwardMostMove = currentMove;
5728 currentMove = oldFMM;
5729 appData.animate = FALSE;
5730 ToNrEvent(forwardMostMove);
5731 appData.animate = saveAnimate;
5733 currentMove = forwardMostMove;
5734 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5735 ClearPremoveHighlights();
5736 DrawPosition(TRUE, boards[currentMove]);
5740 MovePV (int x, int y, int h)
5741 { // step through PV based on mouse coordinates (called on mouse move)
5742 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5744 // we must somehow check if right button is still down (might be released off board!)
5745 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5746 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5747 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5749 lastX = x; lastY = y;
5751 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5752 if(endPV < 0) return;
5753 if(y < margin) step = 1; else
5754 if(y > h - margin) step = -1;
5755 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5756 currentMove += step;
5757 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5758 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5759 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5760 DrawPosition(FALSE, boards[currentMove]);
5764 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5765 // All positions will have equal probability, but the current method will not provide a unique
5766 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5772 int piecesLeft[(int)BlackPawn];
5773 int seed, nrOfShuffles;
5776 GetPositionNumber ()
5777 { // sets global variable seed
5780 seed = appData.defaultFrcPosition;
5781 if(seed < 0) { // randomize based on time for negative FRC position numbers
5782 for(i=0; i<50; i++) seed += random();
5783 seed = random() ^ random() >> 8 ^ random() << 8;
5784 if(seed<0) seed = -seed;
5789 put (Board board, int pieceType, int rank, int n, int shade)
5790 // put the piece on the (n-1)-th empty squares of the given shade
5794 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5795 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5796 board[rank][i] = (ChessSquare) pieceType;
5797 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5799 piecesLeft[pieceType]--;
5808 AddOnePiece (Board board, int pieceType, int rank, int shade)
5809 // calculate where the next piece goes, (any empty square), and put it there
5813 i = seed % squaresLeft[shade];
5814 nrOfShuffles *= squaresLeft[shade];
5815 seed /= squaresLeft[shade];
5816 put(board, pieceType, rank, i, shade);
5820 AddTwoPieces (Board board, int pieceType, int rank)
5821 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5823 int i, n=squaresLeft[ANY], j=n-1, k;
5825 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5826 i = seed % k; // pick one
5829 while(i >= j) i -= j--;
5830 j = n - 1 - j; i += j;
5831 put(board, pieceType, rank, j, ANY);
5832 put(board, pieceType, rank, i, ANY);
5836 SetUpShuffle (Board board, int number)
5840 GetPositionNumber(); nrOfShuffles = 1;
5842 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5843 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5844 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5846 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5848 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5849 p = (int) board[0][i];
5850 if(p < (int) BlackPawn) piecesLeft[p] ++;
5851 board[0][i] = EmptySquare;
5854 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5855 // shuffles restricted to allow normal castling put KRR first
5856 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5857 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5858 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5859 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5860 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5861 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5862 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5863 put(board, WhiteRook, 0, 0, ANY);
5864 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5867 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5868 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5869 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5870 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5871 while(piecesLeft[p] >= 2) {
5872 AddOnePiece(board, p, 0, LITE);
5873 AddOnePiece(board, p, 0, DARK);
5875 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5878 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5879 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5880 // but we leave King and Rooks for last, to possibly obey FRC restriction
5881 if(p == (int)WhiteRook) continue;
5882 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5883 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5886 // now everything is placed, except perhaps King (Unicorn) and Rooks
5888 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5889 // Last King gets castling rights
5890 while(piecesLeft[(int)WhiteUnicorn]) {
5891 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5892 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5895 while(piecesLeft[(int)WhiteKing]) {
5896 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5897 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5902 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5903 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5906 // Only Rooks can be left; simply place them all
5907 while(piecesLeft[(int)WhiteRook]) {
5908 i = put(board, WhiteRook, 0, 0, ANY);
5909 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5912 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5914 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5917 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5918 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5921 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5925 SetCharTable (char *table, const char * map)
5926 /* [HGM] moved here from winboard.c because of its general usefulness */
5927 /* Basically a safe strcpy that uses the last character as King */
5929 int result = FALSE; int NrPieces;
5931 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5932 && NrPieces >= 12 && !(NrPieces&1)) {
5933 int i; /* [HGM] Accept even length from 12 to 34 */
5935 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5936 for( i=0; i<NrPieces/2-1; i++ ) {
5938 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5940 table[(int) WhiteKing] = map[NrPieces/2-1];
5941 table[(int) BlackKing] = map[NrPieces-1];
5950 Prelude (Board board)
5951 { // [HGM] superchess: random selection of exo-pieces
5952 int i, j, k; ChessSquare p;
5953 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5955 GetPositionNumber(); // use FRC position number
5957 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5958 SetCharTable(pieceToChar, appData.pieceToCharTable);
5959 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5960 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5963 j = seed%4; seed /= 4;
5964 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5965 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5966 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5967 j = seed%3 + (seed%3 >= j); seed /= 3;
5968 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5969 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5970 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5971 j = seed%3; seed /= 3;
5972 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5973 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5974 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5975 j = seed%2 + (seed%2 >= j); seed /= 2;
5976 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5977 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5978 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5979 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5980 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5981 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5982 put(board, exoPieces[0], 0, 0, ANY);
5983 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5987 InitPosition (int redraw)
5989 ChessSquare (* pieces)[BOARD_FILES];
5990 int i, j, pawnRow=1, pieceRows=1, overrule,
5991 oldx = gameInfo.boardWidth,
5992 oldy = gameInfo.boardHeight,
5993 oldh = gameInfo.holdingsWidth;
5996 if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5998 /* [AS] Initialize pv info list [HGM] and game status */
6000 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6001 pvInfoList[i].depth = 0;
6002 boards[i][EP_STATUS] = EP_NONE;
6003 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6006 initialRulePlies = 0; /* 50-move counter start */
6008 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6009 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6013 /* [HGM] logic here is completely changed. In stead of full positions */
6014 /* the initialized data only consist of the two backranks. The switch */
6015 /* selects which one we will use, which is than copied to the Board */
6016 /* initialPosition, which for the rest is initialized by Pawns and */
6017 /* empty squares. This initial position is then copied to boards[0], */
6018 /* possibly after shuffling, so that it remains available. */
6020 gameInfo.holdingsWidth = 0; /* default board sizes */
6021 gameInfo.boardWidth = 8;
6022 gameInfo.boardHeight = 8;
6023 gameInfo.holdingsSize = 0;
6024 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6025 for(i=0; i<BOARD_FILES-2; i++)
6026 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6027 initialPosition[EP_STATUS] = EP_NONE;
6028 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6029 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6030 SetCharTable(pieceNickName, appData.pieceNickNames);
6031 else SetCharTable(pieceNickName, "............");
6034 switch (gameInfo.variant) {
6035 case VariantFischeRandom:
6036 shuffleOpenings = TRUE;
6037 appData.fischerCastling = TRUE;
6040 case VariantShatranj:
6041 pieces = ShatranjArray;
6042 nrCastlingRights = 0;
6043 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6046 pieces = makrukArray;
6047 nrCastlingRights = 0;
6048 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6051 pieces = aseanArray;
6052 nrCastlingRights = 0;
6053 SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6055 case VariantTwoKings:
6056 pieces = twoKingsArray;
6059 pieces = GrandArray;
6060 nrCastlingRights = 0;
6061 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6062 gameInfo.boardWidth = 10;
6063 gameInfo.boardHeight = 10;
6064 gameInfo.holdingsSize = 7;
6066 case VariantCapaRandom:
6067 shuffleOpenings = TRUE;
6068 appData.fischerCastling = TRUE;
6069 case VariantCapablanca:
6070 pieces = CapablancaArray;
6071 gameInfo.boardWidth = 10;
6072 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6075 pieces = GothicArray;
6076 gameInfo.boardWidth = 10;
6077 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6080 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6081 gameInfo.holdingsSize = 7;
6082 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6085 pieces = JanusArray;
6086 gameInfo.boardWidth = 10;
6087 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6088 nrCastlingRights = 6;
6089 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6090 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6091 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6092 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6093 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6094 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6097 pieces = FalconArray;
6098 gameInfo.boardWidth = 10;
6099 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6101 case VariantXiangqi:
6102 pieces = XiangqiArray;
6103 gameInfo.boardWidth = 9;
6104 gameInfo.boardHeight = 10;
6105 nrCastlingRights = 0;
6106 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6109 pieces = ShogiArray;
6110 gameInfo.boardWidth = 9;
6111 gameInfo.boardHeight = 9;
6112 gameInfo.holdingsSize = 7;
6113 nrCastlingRights = 0;
6114 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6117 pieces = ChuArray; pieceRows = 3;
6118 gameInfo.boardWidth = 12;
6119 gameInfo.boardHeight = 12;
6120 nrCastlingRights = 0;
6121 SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6122 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6124 case VariantCourier:
6125 pieces = CourierArray;
6126 gameInfo.boardWidth = 12;
6127 nrCastlingRights = 0;
6128 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6130 case VariantKnightmate:
6131 pieces = KnightmateArray;
6132 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6134 case VariantSpartan:
6135 pieces = SpartanArray;
6136 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6140 SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6142 case VariantChuChess:
6143 pieces = ChuChessArray;
6144 gameInfo.boardWidth = 10;
6145 gameInfo.boardHeight = 10;
6146 SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6149 pieces = fairyArray;
6150 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6153 pieces = GreatArray;
6154 gameInfo.boardWidth = 10;
6155 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6156 gameInfo.holdingsSize = 8;
6160 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6161 gameInfo.holdingsSize = 8;
6162 startedFromSetupPosition = TRUE;
6164 case VariantCrazyhouse:
6165 case VariantBughouse:
6167 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6168 gameInfo.holdingsSize = 5;
6170 case VariantWildCastle:
6172 /* !!?shuffle with kings guaranteed to be on d or e file */
6173 shuffleOpenings = 1;
6175 case VariantNoCastle:
6177 nrCastlingRights = 0;
6178 /* !!?unconstrained back-rank shuffle */
6179 shuffleOpenings = 1;
6184 if(appData.NrFiles >= 0) {
6185 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6186 gameInfo.boardWidth = appData.NrFiles;
6188 if(appData.NrRanks >= 0) {
6189 gameInfo.boardHeight = appData.NrRanks;
6191 if(appData.holdingsSize >= 0) {
6192 i = appData.holdingsSize;
6193 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6194 gameInfo.holdingsSize = i;
6196 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6197 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6198 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6200 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6201 if(pawnRow < 1) pawnRow = 1;
6202 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6203 gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6204 if(gameInfo.variant == VariantChu) pawnRow = 3;
6206 /* User pieceToChar list overrules defaults */
6207 if(appData.pieceToCharTable != NULL)
6208 SetCharTable(pieceToChar, appData.pieceToCharTable);
6210 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6212 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6213 s = (ChessSquare) 0; /* account holding counts in guard band */
6214 for( i=0; i<BOARD_HEIGHT; i++ )
6215 initialPosition[i][j] = s;
6217 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6218 initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6219 initialPosition[pawnRow][j] = WhitePawn;
6220 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6221 if(gameInfo.variant == VariantXiangqi) {
6223 initialPosition[pawnRow][j] =
6224 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6225 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6226 initialPosition[2][j] = WhiteCannon;
6227 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6231 if(gameInfo.variant == VariantChu) {
6232 if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6233 initialPosition[pawnRow+1][j] = WhiteCobra,
6234 initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6235 for(i=1; i<pieceRows; i++) {
6236 initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6237 initialPosition[BOARD_HEIGHT-1-i][j] = pieces[2*i+1][j-gameInfo.holdingsWidth];
6240 if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6241 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6242 initialPosition[0][j] = WhiteRook;
6243 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6246 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] = pieces[1][j-gameInfo.holdingsWidth];
6248 if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6249 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6252 initialPosition[1][j] = WhiteBishop;
6253 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6255 initialPosition[1][j] = WhiteRook;
6256 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6259 if( nrCastlingRights == -1) {
6260 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6261 /* This sets default castling rights from none to normal corners */
6262 /* Variants with other castling rights must set them themselves above */
6263 nrCastlingRights = 6;
6265 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6266 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6267 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6268 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6269 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6270 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6273 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6274 if(gameInfo.variant == VariantGreat) { // promotion commoners
6275 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6276 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6277 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6278 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6280 if( gameInfo.variant == VariantSChess ) {
6281 initialPosition[1][0] = BlackMarshall;
6282 initialPosition[2][0] = BlackAngel;
6283 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6284 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6285 initialPosition[1][1] = initialPosition[2][1] =
6286 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6288 if (appData.debugMode) {
6289 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6291 if(shuffleOpenings) {
6292 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6293 startedFromSetupPosition = TRUE;
6295 if(startedFromPositionFile) {
6296 /* [HGM] loadPos: use PositionFile for every new game */
6297 CopyBoard(initialPosition, filePosition);
6298 for(i=0; i<nrCastlingRights; i++)
6299 initialRights[i] = filePosition[CASTLING][i];
6300 startedFromSetupPosition = TRUE;
6303 CopyBoard(boards[0], initialPosition);
6305 if(oldx != gameInfo.boardWidth ||
6306 oldy != gameInfo.boardHeight ||
6307 oldv != gameInfo.variant ||
6308 oldh != gameInfo.holdingsWidth
6310 InitDrawingSizes(-2 ,0);
6312 oldv = gameInfo.variant;
6314 DrawPosition(TRUE, boards[currentMove]);
6318 SendBoard (ChessProgramState *cps, int moveNum)
6320 char message[MSG_SIZ];
6322 if (cps->useSetboard) {
6323 char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6324 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6325 SendToProgram(message, cps);
6330 int i, j, left=0, right=BOARD_WIDTH;
6331 /* Kludge to set black to move, avoiding the troublesome and now
6332 * deprecated "black" command.
6334 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6335 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6337 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6339 SendToProgram("edit\n", cps);
6340 SendToProgram("#\n", cps);
6341 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6342 bp = &boards[moveNum][i][left];
6343 for (j = left; j < right; j++, bp++) {
6344 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6345 if ((int) *bp < (int) BlackPawn) {
6346 if(j == BOARD_RGHT+1)
6347 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6348 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6349 if(message[0] == '+' || message[0] == '~') {
6350 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6351 PieceToChar((ChessSquare)(DEMOTED *bp)),
6354 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6355 message[1] = BOARD_RGHT - 1 - j + '1';
6356 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6358 SendToProgram(message, cps);
6363 SendToProgram("c\n", cps);
6364 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6365 bp = &boards[moveNum][i][left];
6366 for (j = left; j < right; j++, bp++) {
6367 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6368 if (((int) *bp != (int) EmptySquare)
6369 && ((int) *bp >= (int) BlackPawn)) {
6370 if(j == BOARD_LEFT-2)
6371 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6372 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6374 if(message[0] == '+' || message[0] == '~') {
6375 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6376 PieceToChar((ChessSquare)(DEMOTED *bp)),
6379 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6380 message[1] = BOARD_RGHT - 1 - j + '1';
6381 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6383 SendToProgram(message, cps);
6388 SendToProgram(".\n", cps);
6390 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6393 char exclusionHeader[MSG_SIZ];
6394 int exCnt, excludePtr;
6395 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6396 static Exclusion excluTab[200];
6397 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6403 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6404 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6410 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6411 excludePtr = 24; exCnt = 0;
6416 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6417 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6418 char buf[2*MOVE_LEN], *p;
6419 Exclusion *e = excluTab;
6421 for(i=0; i<exCnt; i++)
6422 if(e[i].ff == fromX && e[i].fr == fromY &&
6423 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6424 if(i == exCnt) { // was not in exclude list; add it
6425 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6426 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6427 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6430 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6431 excludePtr++; e[i].mark = excludePtr++;
6432 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6435 exclusionHeader[e[i].mark] = state;
6439 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6440 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6444 if((signed char)promoChar == -1) { // kludge to indicate best move
6445 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6446 return 1; // if unparsable, abort
6448 // update exclusion map (resolving toggle by consulting existing state)
6449 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6451 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6452 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6453 excludeMap[k] |= 1<<j;
6454 else excludeMap[k] &= ~(1<<j);
6456 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6458 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6459 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6461 return (state == '+');
6465 ExcludeClick (int index)
6468 Exclusion *e = excluTab;
6469 if(index < 25) { // none, best or tail clicked
6470 if(index < 13) { // none: include all
6471 WriteMap(0); // clear map
6472 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6473 SendToBoth("include all\n"); // and inform engine
6474 } else if(index > 18) { // tail
6475 if(exclusionHeader[19] == '-') { // tail was excluded
6476 SendToBoth("include all\n");
6477 WriteMap(0); // clear map completely
6478 // now re-exclude selected moves
6479 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6480 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6481 } else { // tail was included or in mixed state
6482 SendToBoth("exclude all\n");
6483 WriteMap(0xFF); // fill map completely
6484 // now re-include selected moves
6485 j = 0; // count them
6486 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6487 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6488 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6491 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6494 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6495 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6496 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6503 DefaultPromoChoice (int white)
6506 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6507 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6508 result = WhiteFerz; // no choice
6509 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6510 result= WhiteKing; // in Suicide Q is the last thing we want
6511 else if(gameInfo.variant == VariantSpartan)
6512 result = white ? WhiteQueen : WhiteAngel;
6513 else result = WhiteQueen;
6514 if(!white) result = WHITE_TO_BLACK result;
6518 static int autoQueen; // [HGM] oneclick
6521 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6523 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6524 /* [HGM] add Shogi promotions */
6525 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6526 ChessSquare piece, partner;
6530 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6531 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6533 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6534 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6537 piece = boards[currentMove][fromY][fromX];
6538 if(gameInfo.variant == VariantChu) {
6539 int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6540 promotionZoneSize = BOARD_HEIGHT/3;
6541 highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6542 } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6543 promotionZoneSize = BOARD_HEIGHT/3;
6544 highestPromotingPiece = (int)WhiteAlfil;
6545 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6546 promotionZoneSize = 3;
6549 // Treat Lance as Pawn when it is not representing Amazon or Lance
6550 if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6551 if(piece == WhiteLance) piece = WhitePawn; else
6552 if(piece == BlackLance) piece = BlackPawn;
6555 // next weed out all moves that do not touch the promotion zone at all
6556 if((int)piece >= BlackPawn) {
6557 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6559 if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6560 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6562 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6563 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6564 if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6568 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6570 // weed out mandatory Shogi promotions
6571 if(gameInfo.variant == VariantShogi) {
6572 if(piece >= BlackPawn) {
6573 if(toY == 0 && piece == BlackPawn ||
6574 toY == 0 && piece == BlackQueen ||
6575 toY <= 1 && piece == BlackKnight) {
6580 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6581 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6582 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6589 // weed out obviously illegal Pawn moves
6590 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6591 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6592 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6593 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6594 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6595 // note we are not allowed to test for valid (non-)capture, due to premove
6598 // we either have a choice what to promote to, or (in Shogi) whether to promote
6599 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6600 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6601 ChessSquare p=BlackFerz; // no choice
6602 while(p < EmptySquare) { //but make sure we use piece that exists
6603 *promoChoice = PieceToChar(p++);
6604 if(*promoChoice != '.') break;
6608 // no sense asking what we must promote to if it is going to explode...
6609 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6610 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6613 // give caller the default choice even if we will not make it
6614 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6615 partner = piece; // pieces can promote if the pieceToCharTable says so
6616 if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6617 else if(Partner(&partner)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6618 if( sweepSelect && gameInfo.variant != VariantGreat
6619 && gameInfo.variant != VariantGrand
6620 && gameInfo.variant != VariantSuper) return FALSE;
6621 if(autoQueen) return FALSE; // predetermined
6623 // suppress promotion popup on illegal moves that are not premoves
6624 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6625 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6626 if(appData.testLegality && !premove) {
6627 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6628 fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6629 if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6630 if(moveType != WhitePromotion && moveType != BlackPromotion)
6638 InPalace (int row, int column)
6639 { /* [HGM] for Xiangqi */
6640 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6641 column < (BOARD_WIDTH + 4)/2 &&
6642 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6647 PieceForSquare (int x, int y)
6649 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6652 return boards[currentMove][y][x];
6656 OKToStartUserMove (int x, int y)
6658 ChessSquare from_piece;
6661 if (matchMode) return FALSE;
6662 if (gameMode == EditPosition) return TRUE;
6664 if (x >= 0 && y >= 0)
6665 from_piece = boards[currentMove][y][x];
6667 from_piece = EmptySquare;
6669 if (from_piece == EmptySquare) return FALSE;
6671 white_piece = (int)from_piece >= (int)WhitePawn &&
6672 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6676 case TwoMachinesPlay:
6684 case MachinePlaysWhite:
6685 case IcsPlayingBlack:
6686 if (appData.zippyPlay) return FALSE;
6688 DisplayMoveError(_("You are playing Black"));
6693 case MachinePlaysBlack:
6694 case IcsPlayingWhite:
6695 if (appData.zippyPlay) return FALSE;
6697 DisplayMoveError(_("You are playing White"));
6702 case PlayFromGameFile:
6703 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6705 if (!white_piece && WhiteOnMove(currentMove)) {
6706 DisplayMoveError(_("It is White's turn"));
6709 if (white_piece && !WhiteOnMove(currentMove)) {
6710 DisplayMoveError(_("It is Black's turn"));
6713 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6714 /* Editing correspondence game history */
6715 /* Could disallow this or prompt for confirmation */
6720 case BeginningOfGame:
6721 if (appData.icsActive) return FALSE;
6722 if (!appData.noChessProgram) {
6724 DisplayMoveError(_("You are playing White"));
6731 if (!white_piece && WhiteOnMove(currentMove)) {
6732 DisplayMoveError(_("It is White's turn"));
6735 if (white_piece && !WhiteOnMove(currentMove)) {
6736 DisplayMoveError(_("It is Black's turn"));
6745 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6746 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6747 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6748 && gameMode != AnalyzeFile && gameMode != Training) {
6749 DisplayMoveError(_("Displayed position is not current"));
6756 OnlyMove (int *x, int *y, Boolean captures)
6758 DisambiguateClosure cl;
6759 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6761 case MachinePlaysBlack:
6762 case IcsPlayingWhite:
6763 case BeginningOfGame:
6764 if(!WhiteOnMove(currentMove)) return FALSE;
6766 case MachinePlaysWhite:
6767 case IcsPlayingBlack:
6768 if(WhiteOnMove(currentMove)) return FALSE;
6775 cl.pieceIn = EmptySquare;
6780 cl.promoCharIn = NULLCHAR;
6781 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6782 if( cl.kind == NormalMove ||
6783 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6784 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6785 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6792 if(cl.kind != ImpossibleMove) return FALSE;
6793 cl.pieceIn = EmptySquare;
6798 cl.promoCharIn = NULLCHAR;
6799 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6800 if( cl.kind == NormalMove ||
6801 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6802 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6803 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6808 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6814 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6815 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6816 int lastLoadGameUseList = FALSE;
6817 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6818 ChessMove lastLoadGameStart = EndOfFile;
6820 Boolean addToBookFlag;
6823 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6827 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6829 /* Check if the user is playing in turn. This is complicated because we
6830 let the user "pick up" a piece before it is his turn. So the piece he
6831 tried to pick up may have been captured by the time he puts it down!
6832 Therefore we use the color the user is supposed to be playing in this
6833 test, not the color of the piece that is currently on the starting
6834 square---except in EditGame mode, where the user is playing both
6835 sides; fortunately there the capture race can't happen. (It can
6836 now happen in IcsExamining mode, but that's just too bad. The user
6837 will get a somewhat confusing message in that case.)
6842 case TwoMachinesPlay:
6846 /* We switched into a game mode where moves are not accepted,
6847 perhaps while the mouse button was down. */
6850 case MachinePlaysWhite:
6851 /* User is moving for Black */
6852 if (WhiteOnMove(currentMove)) {
6853 DisplayMoveError(_("It is White's turn"));
6858 case MachinePlaysBlack:
6859 /* User is moving for White */
6860 if (!WhiteOnMove(currentMove)) {
6861 DisplayMoveError(_("It is Black's turn"));
6866 case PlayFromGameFile:
6867 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6870 case BeginningOfGame:
6873 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6874 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6875 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6876 /* User is moving for Black */
6877 if (WhiteOnMove(currentMove)) {
6878 DisplayMoveError(_("It is White's turn"));
6882 /* User is moving for White */
6883 if (!WhiteOnMove(currentMove)) {
6884 DisplayMoveError(_("It is Black's turn"));
6890 case IcsPlayingBlack:
6891 /* User is moving for Black */
6892 if (WhiteOnMove(currentMove)) {
6893 if (!appData.premove) {
6894 DisplayMoveError(_("It is White's turn"));
6895 } else if (toX >= 0 && toY >= 0) {
6898 premoveFromX = fromX;
6899 premoveFromY = fromY;
6900 premovePromoChar = promoChar;
6902 if (appData.debugMode)
6903 fprintf(debugFP, "Got premove: fromX %d,"
6904 "fromY %d, toX %d, toY %d\n",
6905 fromX, fromY, toX, toY);
6911 case IcsPlayingWhite:
6912 /* User is moving for White */
6913 if (!WhiteOnMove(currentMove)) {
6914 if (!appData.premove) {
6915 DisplayMoveError(_("It is Black's turn"));
6916 } else if (toX >= 0 && toY >= 0) {
6919 premoveFromX = fromX;
6920 premoveFromY = fromY;
6921 premovePromoChar = promoChar;
6923 if (appData.debugMode)
6924 fprintf(debugFP, "Got premove: fromX %d,"
6925 "fromY %d, toX %d, toY %d\n",
6926 fromX, fromY, toX, toY);
6936 /* EditPosition, empty square, or different color piece;
6937 click-click move is possible */
6938 if (toX == -2 || toY == -2) {
6939 boards[0][fromY][fromX] = EmptySquare;
6940 DrawPosition(FALSE, boards[currentMove]);
6942 } else if (toX >= 0 && toY >= 0) {
6943 boards[0][toY][toX] = boards[0][fromY][fromX];
6944 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6945 if(boards[0][fromY][0] != EmptySquare) {
6946 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6947 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6950 if(fromX == BOARD_RGHT+1) {
6951 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6952 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6953 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6956 boards[0][fromY][fromX] = gatingPiece;
6957 DrawPosition(FALSE, boards[currentMove]);
6963 if(toX < 0 || toY < 0) return;
6964 pup = boards[currentMove][toY][toX];
6966 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6967 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6968 if( pup != EmptySquare ) return;
6969 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6970 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6971 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6972 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6973 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6974 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6975 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6979 /* [HGM] always test for legality, to get promotion info */
6980 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6981 fromY, fromX, toY, toX, promoChar);
6983 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6985 /* [HGM] but possibly ignore an IllegalMove result */
6986 if (appData.testLegality) {
6987 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6988 DisplayMoveError(_("Illegal move"));
6993 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6994 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6995 ClearPremoveHighlights(); // was included
6996 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
7000 if(addToBookFlag) { // adding moves to book
7001 char buf[MSG_SIZ], move[MSG_SIZ];
7002 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7003 snprintf(buf, MSG_SIZ, " 0.0%% 1 %s\n", move);
7005 addToBookFlag = FALSE;
7010 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7013 /* Common tail of UserMoveEvent and DropMenuEvent */
7015 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7019 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7020 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7021 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7022 if(WhiteOnMove(currentMove)) {
7023 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7025 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7029 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7030 move type in caller when we know the move is a legal promotion */
7031 if(moveType == NormalMove && promoChar)
7032 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7034 /* [HGM] <popupFix> The following if has been moved here from
7035 UserMoveEvent(). Because it seemed to belong here (why not allow
7036 piece drops in training games?), and because it can only be
7037 performed after it is known to what we promote. */
7038 if (gameMode == Training) {
7039 /* compare the move played on the board to the next move in the
7040 * game. If they match, display the move and the opponent's response.
7041 * If they don't match, display an error message.
7045 CopyBoard(testBoard, boards[currentMove]);
7046 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7048 if (CompareBoards(testBoard, boards[currentMove+1])) {
7049 ForwardInner(currentMove+1);
7051 /* Autoplay the opponent's response.
7052 * if appData.animate was TRUE when Training mode was entered,
7053 * the response will be animated.
7055 saveAnimate = appData.animate;
7056 appData.animate = animateTraining;
7057 ForwardInner(currentMove+1);
7058 appData.animate = saveAnimate;
7060 /* check for the end of the game */
7061 if (currentMove >= forwardMostMove) {
7062 gameMode = PlayFromGameFile;
7064 SetTrainingModeOff();
7065 DisplayInformation(_("End of game"));
7068 DisplayError(_("Incorrect move"), 0);
7073 /* Ok, now we know that the move is good, so we can kill
7074 the previous line in Analysis Mode */
7075 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7076 && currentMove < forwardMostMove) {
7077 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7078 else forwardMostMove = currentMove;
7083 /* If we need the chess program but it's dead, restart it */
7084 ResurrectChessProgram();
7086 /* A user move restarts a paused game*/
7090 thinkOutput[0] = NULLCHAR;
7092 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7094 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7095 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7099 if (gameMode == BeginningOfGame) {
7100 if (appData.noChessProgram) {
7101 gameMode = EditGame;
7105 gameMode = MachinePlaysBlack;
7108 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7110 if (first.sendName) {
7111 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7112 SendToProgram(buf, &first);
7119 /* Relay move to ICS or chess engine */
7120 if (appData.icsActive) {
7121 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7122 gameMode == IcsExamining) {
7123 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7124 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7126 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7128 // also send plain move, in case ICS does not understand atomic claims
7129 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7133 if (first.sendTime && (gameMode == BeginningOfGame ||
7134 gameMode == MachinePlaysWhite ||
7135 gameMode == MachinePlaysBlack)) {
7136 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7138 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7139 // [HGM] book: if program might be playing, let it use book
7140 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7141 first.maybeThinking = TRUE;
7142 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7143 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7144 SendBoard(&first, currentMove+1);
7145 if(second.analyzing) {
7146 if(!second.useSetboard) SendToProgram("undo\n", &second);
7147 SendBoard(&second, currentMove+1);
7150 SendMoveToProgram(forwardMostMove-1, &first);
7151 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7153 if (currentMove == cmailOldMove + 1) {
7154 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7158 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7162 if(appData.testLegality)
7163 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7169 if (WhiteOnMove(currentMove)) {
7170 GameEnds(BlackWins, "Black mates", GE_PLAYER);
7172 GameEnds(WhiteWins, "White mates", GE_PLAYER);
7176 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7181 case MachinePlaysBlack:
7182 case MachinePlaysWhite:
7183 /* disable certain menu options while machine is thinking */
7184 SetMachineThinkingEnables();
7191 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7192 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7194 if(bookHit) { // [HGM] book: simulate book reply
7195 static char bookMove[MSG_SIZ]; // a bit generous?
7197 programStats.nodes = programStats.depth = programStats.time =
7198 programStats.score = programStats.got_only_move = 0;
7199 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7201 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7202 strcat(bookMove, bookHit);
7203 HandleMachineMove(bookMove, &first);
7209 MarkByFEN(char *fen)
7212 if(!appData.markers || !appData.highlightDragging) return;
7213 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7214 r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7218 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7219 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7220 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7221 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7222 if(*fen == 'T') marker[r][f++] = 0; else
7223 if(*fen == 'Y') marker[r][f++] = 1; else
7224 if(*fen == 'G') marker[r][f++] = 3; else
7225 if(*fen == 'B') marker[r][f++] = 4; else
7226 if(*fen == 'C') marker[r][f++] = 5; else
7227 if(*fen == 'M') marker[r][f++] = 6; else
7228 if(*fen == 'W') marker[r][f++] = 7; else
7229 if(*fen == 'D') marker[r][f++] = 8; else
7230 if(*fen == 'R') marker[r][f++] = 2; else {
7231 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7234 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7238 DrawPosition(TRUE, NULL);
7241 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7244 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7246 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7247 Markers *m = (Markers *) closure;
7248 if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7249 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7250 || kind == WhiteCapturesEnPassant
7251 || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7252 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7255 static int hoverSavedValid;
7258 MarkTargetSquares (int clear)
7261 if(clear) { // no reason to ever suppress clearing
7262 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7263 hoverSavedValid = 0;
7264 if(!sum) return; // nothing was cleared,no redraw needed
7267 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7268 !appData.testLegality || gameMode == EditPosition) return;
7269 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7270 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7271 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7273 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7276 DrawPosition(FALSE, NULL);
7280 Explode (Board board, int fromX, int fromY, int toX, int toY)
7282 if(gameInfo.variant == VariantAtomic &&
7283 (board[toY][toX] != EmptySquare || // capture?
7284 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7285 board[fromY][fromX] == BlackPawn )
7287 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7293 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7296 CanPromote (ChessSquare piece, int y)
7298 int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7299 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7300 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7301 if(IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantXiangqi ||
7302 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7303 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7304 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) return FALSE;
7305 return (piece == BlackPawn && y <= zone ||
7306 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7307 piece == BlackLance && y == 1 ||
7308 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7312 HoverEvent (int xPix, int yPix, int x, int y)
7314 static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7316 if(!first.highlight) return;
7317 if(fromX != oldFromX || fromY != oldFromY) oldX = oldY = -1; // kludge to fake entry on from-click
7318 if(x == oldX && y == oldY) return; // only do something if we enter new square
7319 oldFromX = fromX; oldFromY = fromY;
7320 if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7321 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7322 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7323 hoverSavedValid = 1;
7324 } else if(oldX != x || oldY != y) {
7325 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7326 if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7327 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7328 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7329 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7331 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7332 SendToProgram(buf, &first);
7335 // SetHighlights(fromX, fromY, x, y);
7339 void ReportClick(char *action, int x, int y)
7341 char buf[MSG_SIZ]; // Inform engine of what user does
7343 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7344 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7345 if(!first.highlight || gameMode == EditPosition) return;
7346 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7347 SendToProgram(buf, &first);
7351 LeftClick (ClickType clickType, int xPix, int yPix)
7354 Boolean saveAnimate;
7355 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7356 char promoChoice = NULLCHAR;
7358 static TimeMark lastClickTime, prevClickTime;
7360 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7362 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7364 if (clickType == Press) ErrorPopDown();
7365 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7367 x = EventToSquare(xPix, BOARD_WIDTH);
7368 y = EventToSquare(yPix, BOARD_HEIGHT);
7369 if (!flipView && y >= 0) {
7370 y = BOARD_HEIGHT - 1 - y;
7372 if (flipView && x >= 0) {
7373 x = BOARD_WIDTH - 1 - x;
7376 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7377 defaultPromoChoice = promoSweep;
7378 promoSweep = EmptySquare; // terminate sweep
7379 promoDefaultAltered = TRUE;
7380 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7383 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7384 if(clickType == Release) return; // ignore upclick of click-click destination
7385 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7386 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7387 if(gameInfo.holdingsWidth &&
7388 (WhiteOnMove(currentMove)
7389 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7390 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7391 // click in right holdings, for determining promotion piece
7392 ChessSquare p = boards[currentMove][y][x];
7393 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7394 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7395 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7396 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7401 DrawPosition(FALSE, boards[currentMove]);
7405 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7406 if(clickType == Press
7407 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7408 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7409 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7412 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7413 // could be static click on premove from-square: abort premove
7415 ClearPremoveHighlights();
7418 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7419 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7421 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7422 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7423 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7424 defaultPromoChoice = DefaultPromoChoice(side);
7427 autoQueen = appData.alwaysPromoteToQueen;
7431 gatingPiece = EmptySquare;
7432 if (clickType != Press) {
7433 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7434 DragPieceEnd(xPix, yPix); dragging = 0;
7435 DrawPosition(FALSE, NULL);
7439 doubleClick = FALSE;
7440 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7441 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7443 fromX = x; fromY = y; toX = toY = killX = killY = -1;
7444 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7445 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7446 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7448 if (OKToStartUserMove(fromX, fromY)) {
7450 ReportClick("lift", x, y);
7451 MarkTargetSquares(0);
7452 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7453 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7454 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7455 promoSweep = defaultPromoChoice;
7456 selectFlag = 0; lastX = xPix; lastY = yPix;
7457 Sweep(0); // Pawn that is going to promote: preview promotion piece
7458 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7460 if (appData.highlightDragging) {
7461 SetHighlights(fromX, fromY, -1, -1);
7465 } else fromX = fromY = -1;
7471 if (clickType == Press && gameMode != EditPosition) {
7476 // ignore off-board to clicks
7477 if(y < 0 || x < 0) return;
7479 /* Check if clicking again on the same color piece */
7480 fromP = boards[currentMove][fromY][fromX];
7481 toP = boards[currentMove][y][x];
7482 frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7483 if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7484 ((WhitePawn <= fromP && fromP <= WhiteKing &&
7485 WhitePawn <= toP && toP <= WhiteKing &&
7486 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7487 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7488 (BlackPawn <= fromP && fromP <= BlackKing &&
7489 BlackPawn <= toP && toP <= BlackKing &&
7490 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7491 !(fromP == BlackKing && toP == BlackRook && frc)))) {
7492 /* Clicked again on same color piece -- changed his mind */
7493 second = (x == fromX && y == fromY);
7495 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7496 second = FALSE; // first double-click rather than scond click
7497 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7499 promoDefaultAltered = FALSE;
7500 MarkTargetSquares(1);
7501 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7502 if (appData.highlightDragging) {
7503 SetHighlights(x, y, -1, -1);
7507 if (OKToStartUserMove(x, y)) {
7508 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7509 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7510 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7511 gatingPiece = boards[currentMove][fromY][fromX];
7512 else gatingPiece = doubleClick ? fromP : EmptySquare;
7514 fromY = y; dragging = 1;
7515 ReportClick("lift", x, y);
7516 MarkTargetSquares(0);
7517 DragPieceBegin(xPix, yPix, FALSE);
7518 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7519 promoSweep = defaultPromoChoice;
7520 selectFlag = 0; lastX = xPix; lastY = yPix;
7521 Sweep(0); // Pawn that is going to promote: preview promotion piece
7525 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7528 // ignore clicks on holdings
7529 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7532 if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7533 DragPieceEnd(xPix, yPix); dragging = 0;
7535 // a deferred attempt to click-click move an empty square on top of a piece
7536 boards[currentMove][y][x] = EmptySquare;
7538 DrawPosition(FALSE, boards[currentMove]);
7539 fromX = fromY = -1; clearFlag = 0;
7542 if (appData.animateDragging) {
7543 /* Undo animation damage if any */
7544 DrawPosition(FALSE, NULL);
7546 if (second || sweepSelecting) {
7547 /* Second up/down in same square; just abort move */
7548 if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7549 second = sweepSelecting = 0;
7551 gatingPiece = EmptySquare;
7552 MarkTargetSquares(1);
7555 ClearPremoveHighlights();
7557 /* First upclick in same square; start click-click mode */
7558 SetHighlights(x, y, -1, -1);
7565 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7566 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7567 DisplayMessage(_("only marked squares are legal"),"");
7568 DrawPosition(TRUE, NULL);
7569 return; // ignore to-click
7572 /* we now have a different from- and (possibly off-board) to-square */
7573 /* Completed move */
7574 if(!sweepSelecting) {
7579 piece = boards[currentMove][fromY][fromX];
7581 saveAnimate = appData.animate;
7582 if (clickType == Press) {
7583 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7584 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7585 // must be Edit Position mode with empty-square selected
7586 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7587 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7590 if(dragging == 2) { // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7593 if(x == killX && y == killY) { // second click on this square, which was selected as first-leg target
7594 killX = killY = -1; // this informs us no second leg is coming, so treat as to-click without intermediate
7596 if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7597 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7598 if(appData.sweepSelect) {
7599 promoSweep = defaultPromoChoice;
7600 if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7601 selectFlag = 0; lastX = xPix; lastY = yPix;
7602 Sweep(0); // Pawn that is going to promote: preview promotion piece
7604 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7605 MarkTargetSquares(1);
7607 return; // promo popup appears on up-click
7609 /* Finish clickclick move */
7610 if (appData.animate || appData.highlightLastMove) {
7611 SetHighlights(fromX, fromY, toX, toY);
7615 } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7616 sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7617 if (appData.animate || appData.highlightLastMove) {
7618 SetHighlights(fromX, fromY, toX, toY);
7624 // [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
7625 /* Finish drag move */
7626 if (appData.highlightLastMove) {
7627 SetHighlights(fromX, fromY, toX, toY);
7632 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7633 if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7634 dragging *= 2; // flag button-less dragging if we are dragging
7635 MarkTargetSquares(1);
7636 if(x == killX && y == killY) killX = killY = -1; else {
7637 killX = x; killY = y; //remeber this square as intermediate
7638 ReportClick("put", x, y); // and inform engine
7639 ReportClick("lift", x, y);
7640 MarkTargetSquares(0);
7644 DragPieceEnd(xPix, yPix); dragging = 0;
7645 /* Don't animate move and drag both */
7646 appData.animate = FALSE;
7649 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7650 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7651 ChessSquare piece = boards[currentMove][fromY][fromX];
7652 if(gameMode == EditPosition && piece != EmptySquare &&
7653 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7656 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7657 n = PieceToNumber(piece - (int)BlackPawn);
7658 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7659 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7660 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7662 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7663 n = PieceToNumber(piece);
7664 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7665 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7666 boards[currentMove][n][BOARD_WIDTH-2]++;
7668 boards[currentMove][fromY][fromX] = EmptySquare;
7672 MarkTargetSquares(1);
7673 DrawPosition(TRUE, boards[currentMove]);
7677 // off-board moves should not be highlighted
7678 if(x < 0 || y < 0) ClearHighlights();
7679 else ReportClick("put", x, y);
7681 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7683 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7684 SetHighlights(fromX, fromY, toX, toY);
7685 MarkTargetSquares(1);
7686 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7687 // [HGM] super: promotion to captured piece selected from holdings
7688 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7689 promotionChoice = TRUE;
7690 // kludge follows to temporarily execute move on display, without promoting yet
7691 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7692 boards[currentMove][toY][toX] = p;
7693 DrawPosition(FALSE, boards[currentMove]);
7694 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7695 boards[currentMove][toY][toX] = q;
7696 DisplayMessage("Click in holdings to choose piece", "");
7699 PromotionPopUp(promoChoice);
7701 int oldMove = currentMove;
7702 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7703 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7704 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7705 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7706 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7707 DrawPosition(TRUE, boards[currentMove]);
7708 MarkTargetSquares(1);
7711 appData.animate = saveAnimate;
7712 if (appData.animate || appData.animateDragging) {
7713 /* Undo animation damage if needed */
7714 DrawPosition(FALSE, NULL);
7719 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7720 { // front-end-free part taken out of PieceMenuPopup
7721 int whichMenu; int xSqr, ySqr;
7723 if(seekGraphUp) { // [HGM] seekgraph
7724 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7725 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7729 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7730 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7731 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7732 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7733 if(action == Press) {
7734 originalFlip = flipView;
7735 flipView = !flipView; // temporarily flip board to see game from partners perspective
7736 DrawPosition(TRUE, partnerBoard);
7737 DisplayMessage(partnerStatus, "");
7739 } else if(action == Release) {
7740 flipView = originalFlip;
7741 DrawPosition(TRUE, boards[currentMove]);
7747 xSqr = EventToSquare(x, BOARD_WIDTH);
7748 ySqr = EventToSquare(y, BOARD_HEIGHT);
7749 if (action == Release) {
7750 if(pieceSweep != EmptySquare) {
7751 EditPositionMenuEvent(pieceSweep, toX, toY);
7752 pieceSweep = EmptySquare;
7753 } else UnLoadPV(); // [HGM] pv
7755 if (action != Press) return -2; // return code to be ignored
7758 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7760 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7761 if (xSqr < 0 || ySqr < 0) return -1;
7762 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7763 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7764 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7765 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7769 if(!appData.icsEngineAnalyze) return -1;
7770 case IcsPlayingWhite:
7771 case IcsPlayingBlack:
7772 if(!appData.zippyPlay) goto noZip;
7775 case MachinePlaysWhite:
7776 case MachinePlaysBlack:
7777 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7778 if (!appData.dropMenu) {
7780 return 2; // flag front-end to grab mouse events
7782 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7783 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7786 if (xSqr < 0 || ySqr < 0) return -1;
7787 if (!appData.dropMenu || appData.testLegality &&
7788 gameInfo.variant != VariantBughouse &&
7789 gameInfo.variant != VariantCrazyhouse) return -1;
7790 whichMenu = 1; // drop menu
7796 if (((*fromX = xSqr) < 0) ||
7797 ((*fromY = ySqr) < 0)) {
7798 *fromX = *fromY = -1;
7802 *fromX = BOARD_WIDTH - 1 - *fromX;
7804 *fromY = BOARD_HEIGHT - 1 - *fromY;
7810 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7812 // char * hint = lastHint;
7813 FrontEndProgramStats stats;
7815 stats.which = cps == &first ? 0 : 1;
7816 stats.depth = cpstats->depth;
7817 stats.nodes = cpstats->nodes;
7818 stats.score = cpstats->score;
7819 stats.time = cpstats->time;
7820 stats.pv = cpstats->movelist;
7821 stats.hint = lastHint;
7822 stats.an_move_index = 0;
7823 stats.an_move_count = 0;
7825 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7826 stats.hint = cpstats->move_name;
7827 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7828 stats.an_move_count = cpstats->nr_moves;
7831 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
7833 SetProgramStats( &stats );
7837 ClearEngineOutputPane (int which)
7839 static FrontEndProgramStats dummyStats;
7840 dummyStats.which = which;
7841 dummyStats.pv = "#";
7842 SetProgramStats( &dummyStats );
7845 #define MAXPLAYERS 500
7848 TourneyStandings (int display)
7850 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7851 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7852 char result, *p, *names[MAXPLAYERS];
7854 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7855 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7856 names[0] = p = strdup(appData.participants);
7857 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7859 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7861 while(result = appData.results[nr]) {
7862 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7863 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7864 wScore = bScore = 0;
7866 case '+': wScore = 2; break;
7867 case '-': bScore = 2; break;
7868 case '=': wScore = bScore = 1; break;
7870 case '*': return strdup("busy"); // tourney not finished
7878 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7879 for(w=0; w<nPlayers; w++) {
7881 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7882 ranking[w] = b; points[w] = bScore; score[b] = -2;
7884 p = malloc(nPlayers*34+1);
7885 for(w=0; w<nPlayers && w<display; w++)
7886 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7892 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7893 { // count all piece types
7895 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7896 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7897 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7900 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7901 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7902 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7903 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7904 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7905 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7910 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7912 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7913 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7915 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7916 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7917 if(myPawns == 2 && nMine == 3) // KPP
7918 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7919 if(myPawns == 1 && nMine == 2) // KP
7920 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7921 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7922 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7923 if(myPawns) return FALSE;
7924 if(pCnt[WhiteRook+side])
7925 return pCnt[BlackRook-side] ||
7926 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7927 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7928 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7929 if(pCnt[WhiteCannon+side]) {
7930 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7931 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7933 if(pCnt[WhiteKnight+side])
7934 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7939 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7941 VariantClass v = gameInfo.variant;
7943 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7944 if(v == VariantShatranj) return TRUE; // always winnable through baring
7945 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7946 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7948 if(v == VariantXiangqi) {
7949 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7951 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7952 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7953 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7954 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7955 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7956 if(stale) // we have at least one last-rank P plus perhaps C
7957 return majors // KPKX
7958 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7960 return pCnt[WhiteFerz+side] // KCAK
7961 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7962 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7963 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7965 } else if(v == VariantKnightmate) {
7966 if(nMine == 1) return FALSE;
7967 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7968 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7969 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7971 if(nMine == 1) return FALSE; // bare King
7972 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
7973 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7974 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7975 // by now we have King + 1 piece (or multiple Bishops on the same color)
7976 if(pCnt[WhiteKnight+side])
7977 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7978 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7979 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7981 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7982 if(pCnt[WhiteAlfil+side])
7983 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7984 if(pCnt[WhiteWazir+side])
7985 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7992 CompareWithRights (Board b1, Board b2)
7995 if(!CompareBoards(b1, b2)) return FALSE;
7996 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7997 /* compare castling rights */
7998 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7999 rights++; /* King lost rights, while rook still had them */
8000 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8001 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8002 rights++; /* but at least one rook lost them */
8004 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8006 if( b1[CASTLING][5] != NoRights ) {
8007 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8014 Adjudicate (ChessProgramState *cps)
8015 { // [HGM] some adjudications useful with buggy engines
8016 // [HGM] adjudicate: made into separate routine, which now can be called after every move
8017 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8018 // Actually ending the game is now based on the additional internal condition canAdjudicate.
8019 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8020 int k, drop, count = 0; static int bare = 1;
8021 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8022 Boolean canAdjudicate = !appData.icsActive;
8024 // most tests only when we understand the game, i.e. legality-checking on
8025 if( appData.testLegality )
8026 { /* [HGM] Some more adjudications for obstinate engines */
8027 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8028 static int moveCount = 6;
8030 char *reason = NULL;
8032 /* Count what is on board. */
8033 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8035 /* Some material-based adjudications that have to be made before stalemate test */
8036 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8037 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8038 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8039 if(canAdjudicate && appData.checkMates) {
8041 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8042 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8043 "Xboard adjudication: King destroyed", GE_XBOARD );
8048 /* Bare King in Shatranj (loses) or Losers (wins) */
8049 if( nrW == 1 || nrB == 1) {
8050 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8051 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
8052 if(canAdjudicate && appData.checkMates) {
8054 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8055 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8056 "Xboard adjudication: Bare king", GE_XBOARD );
8060 if( gameInfo.variant == VariantShatranj && --bare < 0)
8062 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8063 if(canAdjudicate && appData.checkMates) {
8064 /* but only adjudicate if adjudication enabled */
8066 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8067 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8068 "Xboard adjudication: Bare king", GE_XBOARD );
8075 // don't wait for engine to announce game end if we can judge ourselves
8076 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8078 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8079 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
8080 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8081 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8084 reason = "Xboard adjudication: 3rd check";
8085 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8096 reason = "Xboard adjudication: Stalemate";
8097 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8098 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
8099 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8100 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
8101 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8102 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8103 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8104 EP_CHECKMATE : EP_WINS);
8105 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8106 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8110 reason = "Xboard adjudication: Checkmate";
8111 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8112 if(gameInfo.variant == VariantShogi) {
8113 if(forwardMostMove > backwardMostMove
8114 && moveList[forwardMostMove-1][1] == '@'
8115 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8116 reason = "XBoard adjudication: pawn-drop mate";
8117 boards[forwardMostMove][EP_STATUS] = EP_WINS;
8123 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8125 result = GameIsDrawn; break;
8127 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8129 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8133 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8135 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8136 GameEnds( result, reason, GE_XBOARD );
8140 /* Next absolutely insufficient mating material. */
8141 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8142 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8143 { /* includes KBK, KNK, KK of KBKB with like Bishops */
8145 /* always flag draws, for judging claims */
8146 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8148 if(canAdjudicate && appData.materialDraws) {
8149 /* but only adjudicate them if adjudication enabled */
8150 if(engineOpponent) {
8151 SendToProgram("force\n", engineOpponent); // suppress reply
8152 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8154 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8159 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8160 if(gameInfo.variant == VariantXiangqi ?
8161 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8163 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8164 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
8165 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
8166 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8168 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8169 { /* if the first 3 moves do not show a tactical win, declare draw */
8170 if(engineOpponent) {
8171 SendToProgram("force\n", engineOpponent); // suppress reply
8172 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8174 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8177 } else moveCount = 6;
8180 // Repetition draws and 50-move rule can be applied independently of legality testing
8182 /* Check for rep-draws */
8184 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8185 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8186 for(k = forwardMostMove-2;
8187 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8188 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8189 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8192 if(CompareBoards(boards[k], boards[forwardMostMove])) {
8193 /* compare castling rights */
8194 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8195 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8196 rights++; /* King lost rights, while rook still had them */
8197 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8198 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8199 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8200 rights++; /* but at least one rook lost them */
8202 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8203 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8205 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8206 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8207 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8210 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8211 && appData.drawRepeats > 1) {
8212 /* adjudicate after user-specified nr of repeats */
8213 int result = GameIsDrawn;
8214 char *details = "XBoard adjudication: repetition draw";
8215 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8216 // [HGM] xiangqi: check for forbidden perpetuals
8217 int m, ourPerpetual = 1, hisPerpetual = 1;
8218 for(m=forwardMostMove; m>k; m-=2) {
8219 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8220 ourPerpetual = 0; // the current mover did not always check
8221 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8222 hisPerpetual = 0; // the opponent did not always check
8224 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8225 ourPerpetual, hisPerpetual);
8226 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8227 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8228 details = "Xboard adjudication: perpetual checking";
8230 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8231 break; // (or we would have caught him before). Abort repetition-checking loop.
8233 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8234 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8236 details = "Xboard adjudication: repetition";
8238 } else // it must be XQ
8239 // Now check for perpetual chases
8240 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8241 hisPerpetual = PerpetualChase(k, forwardMostMove);
8242 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8243 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8244 static char resdet[MSG_SIZ];
8245 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8247 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8249 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8250 break; // Abort repetition-checking loop.
8252 // if neither of us is checking or chasing all the time, or both are, it is draw
8254 if(engineOpponent) {
8255 SendToProgram("force\n", engineOpponent); // suppress reply
8256 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8258 GameEnds( result, details, GE_XBOARD );
8261 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8262 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8266 /* Now we test for 50-move draws. Determine ply count */
8267 count = forwardMostMove;
8268 /* look for last irreversble move */
8269 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8271 /* if we hit starting position, add initial plies */
8272 if( count == backwardMostMove )
8273 count -= initialRulePlies;
8274 count = forwardMostMove - count;
8275 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8276 // adjust reversible move counter for checks in Xiangqi
8277 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8278 if(i < backwardMostMove) i = backwardMostMove;
8279 while(i <= forwardMostMove) {
8280 lastCheck = inCheck; // check evasion does not count
8281 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8282 if(inCheck || lastCheck) count--; // check does not count
8287 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8288 /* this is used to judge if draw claims are legal */
8289 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8290 if(engineOpponent) {
8291 SendToProgram("force\n", engineOpponent); // suppress reply
8292 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8294 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8298 /* if draw offer is pending, treat it as a draw claim
8299 * when draw condition present, to allow engines a way to
8300 * claim draws before making their move to avoid a race
8301 * condition occurring after their move
8303 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8305 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8306 p = "Draw claim: 50-move rule";
8307 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8308 p = "Draw claim: 3-fold repetition";
8309 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8310 p = "Draw claim: insufficient mating material";
8311 if( p != NULL && canAdjudicate) {
8312 if(engineOpponent) {
8313 SendToProgram("force\n", engineOpponent); // suppress reply
8314 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8316 GameEnds( GameIsDrawn, p, GE_XBOARD );
8321 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8322 if(engineOpponent) {
8323 SendToProgram("force\n", engineOpponent); // suppress reply
8324 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8326 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8332 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8333 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8334 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8339 int pieces[10], squares[10], cnt=0, r, f, res;
8341 static PPROBE_EGBB probeBB;
8342 if(!appData.testLegality) return 10;
8343 if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8344 if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8345 if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8346 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8347 ChessSquare piece = boards[forwardMostMove][r][f];
8348 int black = (piece >= BlackPawn);
8349 int type = piece - black*BlackPawn;
8350 if(piece == EmptySquare) continue;
8351 if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8352 if(type == WhiteKing) type = WhiteQueen + 1;
8353 type = egbbCode[type];
8354 squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8355 pieces[cnt] = type + black*6;
8356 if(++cnt > 5) return 11;
8358 pieces[cnt] = squares[cnt] = 0;
8360 if(loaded == 2) return 13; // loading failed before
8362 loaded = 2; // prepare for failure
8363 char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8366 if(!path) return 13; // no egbb installed
8367 strncpy(buf, path + 8, MSG_SIZ);
8368 if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8369 snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8370 lib = LoadLibrary(buf);
8371 if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8372 loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8373 probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8374 if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8375 p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8376 loaded = 1; // success!
8378 res = probeBB(forwardMostMove & 1, pieces, squares);
8379 return res > 0 ? 1 : res < 0 ? -1 : 0;
8383 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8384 { // [HGM] book: this routine intercepts moves to simulate book replies
8385 char *bookHit = NULL;
8387 if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8389 snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8390 SendToProgram(buf, cps);
8392 //first determine if the incoming move brings opponent into his book
8393 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8394 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8395 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8396 if(bookHit != NULL && !cps->bookSuspend) {
8397 // make sure opponent is not going to reply after receiving move to book position
8398 SendToProgram("force\n", cps);
8399 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8401 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8402 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8403 // now arrange restart after book miss
8405 // after a book hit we never send 'go', and the code after the call to this routine
8406 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8407 char buf[MSG_SIZ], *move = bookHit;
8409 int fromX, fromY, toX, toY;
8413 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8414 &fromX, &fromY, &toX, &toY, &promoChar)) {
8415 (void) CoordsToAlgebraic(boards[forwardMostMove],
8416 PosFlags(forwardMostMove),
8417 fromY, fromX, toY, toX, promoChar, move);
8419 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8423 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8424 SendToProgram(buf, cps);
8425 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8426 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8427 SendToProgram("go\n", cps);
8428 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8429 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8430 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8431 SendToProgram("go\n", cps);
8432 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8434 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8438 LoadError (char *errmess, ChessProgramState *cps)
8439 { // unloads engine and switches back to -ncp mode if it was first
8440 if(cps->initDone) return FALSE;
8441 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8442 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8445 appData.noChessProgram = TRUE;
8446 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8447 gameMode = BeginningOfGame; ModeHighlight();
8450 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8451 DisplayMessage("", ""); // erase waiting message
8452 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8457 ChessProgramState *savedState;
8459 DeferredBookMove (void)
8461 if(savedState->lastPing != savedState->lastPong)
8462 ScheduleDelayedEvent(DeferredBookMove, 10);
8464 HandleMachineMove(savedMessage, savedState);
8467 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8468 static ChessProgramState *stalledEngine;
8469 static char stashedInputMove[MSG_SIZ];
8472 HandleMachineMove (char *message, ChessProgramState *cps)
8474 static char firstLeg[20];
8475 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8476 char realname[MSG_SIZ];
8477 int fromX, fromY, toX, toY;
8479 char promoChar, roar;
8481 int machineWhite, oldError;
8484 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8485 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8486 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8487 DisplayError(_("Invalid pairing from pairing engine"), 0);
8490 pairingReceived = 1;
8492 return; // Skim the pairing messages here.
8495 oldError = cps->userError; cps->userError = 0;
8497 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8499 * Kludge to ignore BEL characters
8501 while (*message == '\007') message++;
8504 * [HGM] engine debug message: ignore lines starting with '#' character
8506 if(cps->debug && *message == '#') return;
8509 * Look for book output
8511 if (cps == &first && bookRequested) {
8512 if (message[0] == '\t' || message[0] == ' ') {
8513 /* Part of the book output is here; append it */
8514 strcat(bookOutput, message);
8515 strcat(bookOutput, " \n");
8517 } else if (bookOutput[0] != NULLCHAR) {
8518 /* All of book output has arrived; display it */
8519 char *p = bookOutput;
8520 while (*p != NULLCHAR) {
8521 if (*p == '\t') *p = ' ';
8524 DisplayInformation(bookOutput);
8525 bookRequested = FALSE;
8526 /* Fall through to parse the current output */
8531 * Look for machine move.
8533 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8534 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8536 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8537 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8538 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8539 stalledEngine = cps;
8540 if(appData.ponderNextMove) { // bring opponent out of ponder
8541 if(gameMode == TwoMachinesPlay) {
8542 if(cps->other->pause)
8543 PauseEngine(cps->other);
8545 SendToProgram("easy\n", cps->other);
8552 /* This method is only useful on engines that support ping */
8553 if (cps->lastPing != cps->lastPong) {
8554 if (gameMode == BeginningOfGame) {
8555 /* Extra move from before last new; ignore */
8556 if (appData.debugMode) {
8557 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8560 if (appData.debugMode) {
8561 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8562 cps->which, gameMode);
8565 SendToProgram("undo\n", cps);
8571 case BeginningOfGame:
8572 /* Extra move from before last reset; ignore */
8573 if (appData.debugMode) {
8574 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8581 /* Extra move after we tried to stop. The mode test is
8582 not a reliable way of detecting this problem, but it's
8583 the best we can do on engines that don't support ping.
8585 if (appData.debugMode) {
8586 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8587 cps->which, gameMode);
8589 SendToProgram("undo\n", cps);
8592 case MachinePlaysWhite:
8593 case IcsPlayingWhite:
8594 machineWhite = TRUE;
8597 case MachinePlaysBlack:
8598 case IcsPlayingBlack:
8599 machineWhite = FALSE;
8602 case TwoMachinesPlay:
8603 machineWhite = (cps->twoMachinesColor[0] == 'w');
8606 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8607 if (appData.debugMode) {
8609 "Ignoring move out of turn by %s, gameMode %d"
8610 ", forwardMost %d\n",
8611 cps->which, gameMode, forwardMostMove);
8616 if(cps->alphaRank) AlphaRank(machineMove, 4);
8618 // [HGM] lion: (some very limited) support for Alien protocol
8620 if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8621 safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8623 } else if(firstLeg[0]) { // there was a previous leg;
8624 // only support case where same piece makes two step (and don't even test that!)
8625 char buf[20], *p = machineMove+1, *q = buf+1, f;
8626 safeStrCpy(buf, machineMove, 20);
8627 while(isdigit(*q)) q++; // find start of to-square
8628 safeStrCpy(machineMove, firstLeg, 20);
8629 while(isdigit(*p)) p++;
8630 safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8631 sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8632 firstLeg[0] = NULLCHAR;
8635 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8636 &fromX, &fromY, &toX, &toY, &promoChar)) {
8637 /* Machine move could not be parsed; ignore it. */
8638 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8639 machineMove, _(cps->which));
8640 DisplayMoveError(buf1);
8641 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8642 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8643 if (gameMode == TwoMachinesPlay) {
8644 GameEnds(machineWhite ? BlackWins : WhiteWins,
8650 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8651 /* So we have to redo legality test with true e.p. status here, */
8652 /* to make sure an illegal e.p. capture does not slip through, */
8653 /* to cause a forfeit on a justified illegal-move complaint */
8654 /* of the opponent. */
8655 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8657 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8658 fromY, fromX, toY, toX, promoChar);
8659 if(moveType == IllegalMove) {
8660 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8661 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8662 GameEnds(machineWhite ? BlackWins : WhiteWins,
8665 } else if(!appData.fischerCastling)
8666 /* [HGM] Kludge to handle engines that send FRC-style castling
8667 when they shouldn't (like TSCP-Gothic) */
8669 case WhiteASideCastleFR:
8670 case BlackASideCastleFR:
8672 currentMoveString[2]++;
8674 case WhiteHSideCastleFR:
8675 case BlackHSideCastleFR:
8677 currentMoveString[2]--;
8679 default: ; // nothing to do, but suppresses warning of pedantic compilers
8682 hintRequested = FALSE;
8683 lastHint[0] = NULLCHAR;
8684 bookRequested = FALSE;
8685 /* Program may be pondering now */
8686 cps->maybeThinking = TRUE;
8687 if (cps->sendTime == 2) cps->sendTime = 1;
8688 if (cps->offeredDraw) cps->offeredDraw--;
8690 /* [AS] Save move info*/
8691 pvInfoList[ forwardMostMove ].score = programStats.score;
8692 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8693 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8695 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8697 /* Test suites abort the 'game' after one move */
8698 if(*appData.finger) {
8700 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8701 if(!f) f = fopen(appData.finger, "w");
8702 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8703 else { DisplayFatalError("Bad output file", errno, 0); return; }
8705 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8708 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8709 if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8712 while( count < adjudicateLossPlies ) {
8713 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8716 score = -score; /* Flip score for winning side */
8718 printf("score=%d count=%d\n",score,count);
8719 if( score > appData.adjudicateLossThreshold ) {
8726 if( count >= adjudicateLossPlies ) {
8727 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8729 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8730 "Xboard adjudication",
8737 if(Adjudicate(cps)) {
8738 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8739 return; // [HGM] adjudicate: for all automatic game ends
8743 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8745 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8746 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8748 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8750 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8752 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8753 char buf[3*MSG_SIZ];
8755 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8756 programStats.score / 100.,
8758 programStats.time / 100.,
8759 (unsigned int)programStats.nodes,
8760 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8761 programStats.movelist);
8767 /* [AS] Clear stats for next move */
8768 ClearProgramStats();
8769 thinkOutput[0] = NULLCHAR;
8770 hiddenThinkOutputState = 0;
8773 if (gameMode == TwoMachinesPlay) {
8774 /* [HGM] relaying draw offers moved to after reception of move */
8775 /* and interpreting offer as claim if it brings draw condition */
8776 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8777 SendToProgram("draw\n", cps->other);
8779 if (cps->other->sendTime) {
8780 SendTimeRemaining(cps->other,
8781 cps->other->twoMachinesColor[0] == 'w');
8783 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8784 if (firstMove && !bookHit) {
8786 if (cps->other->useColors) {
8787 SendToProgram(cps->other->twoMachinesColor, cps->other);
8789 SendToProgram("go\n", cps->other);
8791 cps->other->maybeThinking = TRUE;
8794 roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8796 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8798 if (!pausing && appData.ringBellAfterMoves) {
8799 if(!roar) RingBell();
8803 * Reenable menu items that were disabled while
8804 * machine was thinking
8806 if (gameMode != TwoMachinesPlay)
8807 SetUserThinkingEnables();
8809 // [HGM] book: after book hit opponent has received move and is now in force mode
8810 // force the book reply into it, and then fake that it outputted this move by jumping
8811 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8813 static char bookMove[MSG_SIZ]; // a bit generous?
8815 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8816 strcat(bookMove, bookHit);
8819 programStats.nodes = programStats.depth = programStats.time =
8820 programStats.score = programStats.got_only_move = 0;
8821 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8823 if(cps->lastPing != cps->lastPong) {
8824 savedMessage = message; // args for deferred call
8826 ScheduleDelayedEvent(DeferredBookMove, 10);
8835 /* Set special modes for chess engines. Later something general
8836 * could be added here; for now there is just one kludge feature,
8837 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8838 * when "xboard" is given as an interactive command.
8840 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8841 cps->useSigint = FALSE;
8842 cps->useSigterm = FALSE;
8844 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8845 ParseFeatures(message+8, cps);
8846 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8849 if (!strncmp(message, "setup ", 6) &&
8850 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8851 NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8852 ) { // [HGM] allow first engine to define opening position
8853 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8854 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8856 if(sscanf(message, "setup (%s", buf) == 1) {
8857 s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8858 ASSIGN(appData.pieceToCharTable, buf);
8860 if(startedFromSetupPosition) return;
8861 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8863 while(message[s] && message[s++] != ' ');
8864 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8865 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8866 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8867 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
8868 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8869 if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8872 ParseFEN(boards[0], &dummy, message+s, FALSE);
8873 DrawPosition(TRUE, boards[0]);
8874 startedFromSetupPosition = TRUE;
8877 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8878 * want this, I was asked to put it in, and obliged.
8880 if (!strncmp(message, "setboard ", 9)) {
8881 Board initial_position;
8883 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8885 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8886 DisplayError(_("Bad FEN received from engine"), 0);
8890 CopyBoard(boards[0], initial_position);
8891 initialRulePlies = FENrulePlies;
8892 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8893 else gameMode = MachinePlaysBlack;
8894 DrawPosition(FALSE, boards[currentMove]);
8900 * Look for communication commands
8902 if (!strncmp(message, "telluser ", 9)) {
8903 if(message[9] == '\\' && message[10] == '\\')
8904 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8906 DisplayNote(message + 9);
8909 if (!strncmp(message, "tellusererror ", 14)) {
8911 if(message[14] == '\\' && message[15] == '\\')
8912 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8914 DisplayError(message + 14, 0);
8917 if (!strncmp(message, "tellopponent ", 13)) {
8918 if (appData.icsActive) {
8920 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8924 DisplayNote(message + 13);
8928 if (!strncmp(message, "tellothers ", 11)) {
8929 if (appData.icsActive) {
8931 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8934 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8937 if (!strncmp(message, "tellall ", 8)) {
8938 if (appData.icsActive) {
8940 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8944 DisplayNote(message + 8);
8948 if (strncmp(message, "warning", 7) == 0) {
8949 /* Undocumented feature, use tellusererror in new code */
8950 DisplayError(message, 0);
8953 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8954 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8955 strcat(realname, " query");
8956 AskQuestion(realname, buf2, buf1, cps->pr);
8959 /* Commands from the engine directly to ICS. We don't allow these to be
8960 * sent until we are logged on. Crafty kibitzes have been known to
8961 * interfere with the login process.
8964 if (!strncmp(message, "tellics ", 8)) {
8965 SendToICS(message + 8);
8969 if (!strncmp(message, "tellicsnoalias ", 15)) {
8970 SendToICS(ics_prefix);
8971 SendToICS(message + 15);
8975 /* The following are for backward compatibility only */
8976 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8977 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8978 SendToICS(ics_prefix);
8984 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8985 if(initPing == cps->lastPong) {
8986 if(gameInfo.variant == VariantUnknown) {
8987 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8988 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8989 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8995 if(!strncmp(message, "highlight ", 10)) {
8996 if(appData.testLegality && appData.markers) return;
8997 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9000 if(!strncmp(message, "click ", 6)) {
9001 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9002 if(appData.testLegality || !appData.oneClick) return;
9003 sscanf(message+6, "%c%d%c", &f, &y, &c);
9004 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9005 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9006 x = x*squareSize + (x+1)*lineGap + squareSize/2;
9007 y = y*squareSize + (y+1)*lineGap + squareSize/2;
9008 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9009 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9010 LeftClick(Release, lastLeftX, lastLeftY);
9011 controlKey = (c == ',');
9012 LeftClick(Press, x, y);
9013 LeftClick(Release, x, y);
9014 first.highlight = f;
9018 * If the move is illegal, cancel it and redraw the board.
9019 * Also deal with other error cases. Matching is rather loose
9020 * here to accommodate engines written before the spec.
9022 if (strncmp(message + 1, "llegal move", 11) == 0 ||
9023 strncmp(message, "Error", 5) == 0) {
9024 if (StrStr(message, "name") ||
9025 StrStr(message, "rating") || StrStr(message, "?") ||
9026 StrStr(message, "result") || StrStr(message, "board") ||
9027 StrStr(message, "bk") || StrStr(message, "computer") ||
9028 StrStr(message, "variant") || StrStr(message, "hint") ||
9029 StrStr(message, "random") || StrStr(message, "depth") ||
9030 StrStr(message, "accepted")) {
9033 if (StrStr(message, "protover")) {
9034 /* Program is responding to input, so it's apparently done
9035 initializing, and this error message indicates it is
9036 protocol version 1. So we don't need to wait any longer
9037 for it to initialize and send feature commands. */
9038 FeatureDone(cps, 1);
9039 cps->protocolVersion = 1;
9042 cps->maybeThinking = FALSE;
9044 if (StrStr(message, "draw")) {
9045 /* Program doesn't have "draw" command */
9046 cps->sendDrawOffers = 0;
9049 if (cps->sendTime != 1 &&
9050 (StrStr(message, "time") || StrStr(message, "otim"))) {
9051 /* Program apparently doesn't have "time" or "otim" command */
9055 if (StrStr(message, "analyze")) {
9056 cps->analysisSupport = FALSE;
9057 cps->analyzing = FALSE;
9058 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9059 EditGameEvent(); // [HGM] try to preserve loaded game
9060 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9061 DisplayError(buf2, 0);
9064 if (StrStr(message, "(no matching move)st")) {
9065 /* Special kludge for GNU Chess 4 only */
9066 cps->stKludge = TRUE;
9067 SendTimeControl(cps, movesPerSession, timeControl,
9068 timeIncrement, appData.searchDepth,
9072 if (StrStr(message, "(no matching move)sd")) {
9073 /* Special kludge for GNU Chess 4 only */
9074 cps->sdKludge = TRUE;
9075 SendTimeControl(cps, movesPerSession, timeControl,
9076 timeIncrement, appData.searchDepth,
9080 if (!StrStr(message, "llegal")) {
9083 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9084 gameMode == IcsIdle) return;
9085 if (forwardMostMove <= backwardMostMove) return;
9086 if (pausing) PauseEvent();
9087 if(appData.forceIllegal) {
9088 // [HGM] illegal: machine refused move; force position after move into it
9089 SendToProgram("force\n", cps);
9090 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9091 // we have a real problem now, as SendBoard will use the a2a3 kludge
9092 // when black is to move, while there might be nothing on a2 or black
9093 // might already have the move. So send the board as if white has the move.
9094 // But first we must change the stm of the engine, as it refused the last move
9095 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9096 if(WhiteOnMove(forwardMostMove)) {
9097 SendToProgram("a7a6\n", cps); // for the engine black still had the move
9098 SendBoard(cps, forwardMostMove); // kludgeless board
9100 SendToProgram("a2a3\n", cps); // for the engine white still had the move
9101 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9102 SendBoard(cps, forwardMostMove+1); // kludgeless board
9104 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9105 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9106 gameMode == TwoMachinesPlay)
9107 SendToProgram("go\n", cps);
9110 if (gameMode == PlayFromGameFile) {
9111 /* Stop reading this game file */
9112 gameMode = EditGame;
9115 /* [HGM] illegal-move claim should forfeit game when Xboard */
9116 /* only passes fully legal moves */
9117 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9118 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9119 "False illegal-move claim", GE_XBOARD );
9120 return; // do not take back move we tested as valid
9122 currentMove = forwardMostMove-1;
9123 DisplayMove(currentMove-1); /* before DisplayMoveError */
9124 SwitchClocks(forwardMostMove-1); // [HGM] race
9125 DisplayBothClocks();
9126 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9127 parseList[currentMove], _(cps->which));
9128 DisplayMoveError(buf1);
9129 DrawPosition(FALSE, boards[currentMove]);
9131 SetUserThinkingEnables();
9134 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9135 /* Program has a broken "time" command that
9136 outputs a string not ending in newline.
9140 if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9141 if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9142 sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1 ) return;
9146 * If chess program startup fails, exit with an error message.
9147 * Attempts to recover here are futile. [HGM] Well, we try anyway
9149 if ((StrStr(message, "unknown host") != NULL)
9150 || (StrStr(message, "No remote directory") != NULL)
9151 || (StrStr(message, "not found") != NULL)
9152 || (StrStr(message, "No such file") != NULL)
9153 || (StrStr(message, "can't alloc") != NULL)
9154 || (StrStr(message, "Permission denied") != NULL)) {
9156 cps->maybeThinking = FALSE;
9157 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9158 _(cps->which), cps->program, cps->host, message);
9159 RemoveInputSource(cps->isr);
9160 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9161 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9162 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9168 * Look for hint output
9170 if (sscanf(message, "Hint: %s", buf1) == 1) {
9171 if (cps == &first && hintRequested) {
9172 hintRequested = FALSE;
9173 if (ParseOneMove(buf1, forwardMostMove, &moveType,
9174 &fromX, &fromY, &toX, &toY, &promoChar)) {
9175 (void) CoordsToAlgebraic(boards[forwardMostMove],
9176 PosFlags(forwardMostMove),
9177 fromY, fromX, toY, toX, promoChar, buf1);
9178 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9179 DisplayInformation(buf2);
9181 /* Hint move could not be parsed!? */
9182 snprintf(buf2, sizeof(buf2),
9183 _("Illegal hint move \"%s\"\nfrom %s chess program"),
9184 buf1, _(cps->which));
9185 DisplayError(buf2, 0);
9188 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9194 * Ignore other messages if game is not in progress
9196 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9197 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9200 * look for win, lose, draw, or draw offer
9202 if (strncmp(message, "1-0", 3) == 0) {
9203 char *p, *q, *r = "";
9204 p = strchr(message, '{');
9212 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9214 } else if (strncmp(message, "0-1", 3) == 0) {
9215 char *p, *q, *r = "";
9216 p = strchr(message, '{');
9224 /* Kludge for Arasan 4.1 bug */
9225 if (strcmp(r, "Black resigns") == 0) {
9226 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9229 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9231 } else if (strncmp(message, "1/2", 3) == 0) {
9232 char *p, *q, *r = "";
9233 p = strchr(message, '{');
9242 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9245 } else if (strncmp(message, "White resign", 12) == 0) {
9246 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9248 } else if (strncmp(message, "Black resign", 12) == 0) {
9249 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9251 } else if (strncmp(message, "White matches", 13) == 0 ||
9252 strncmp(message, "Black matches", 13) == 0 ) {
9253 /* [HGM] ignore GNUShogi noises */
9255 } else if (strncmp(message, "White", 5) == 0 &&
9256 message[5] != '(' &&
9257 StrStr(message, "Black") == NULL) {
9258 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9260 } else if (strncmp(message, "Black", 5) == 0 &&
9261 message[5] != '(') {
9262 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9264 } else if (strcmp(message, "resign") == 0 ||
9265 strcmp(message, "computer resigns") == 0) {
9267 case MachinePlaysBlack:
9268 case IcsPlayingBlack:
9269 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9271 case MachinePlaysWhite:
9272 case IcsPlayingWhite:
9273 GameEnds(BlackWins, "White resigns", GE_ENGINE);
9275 case TwoMachinesPlay:
9276 if (cps->twoMachinesColor[0] == 'w')
9277 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9279 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9286 } else if (strncmp(message, "opponent mates", 14) == 0) {
9288 case MachinePlaysBlack:
9289 case IcsPlayingBlack:
9290 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9292 case MachinePlaysWhite:
9293 case IcsPlayingWhite:
9294 GameEnds(BlackWins, "Black mates", GE_ENGINE);
9296 case TwoMachinesPlay:
9297 if (cps->twoMachinesColor[0] == 'w')
9298 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9300 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9307 } else if (strncmp(message, "computer mates", 14) == 0) {
9309 case MachinePlaysBlack:
9310 case IcsPlayingBlack:
9311 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9313 case MachinePlaysWhite:
9314 case IcsPlayingWhite:
9315 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9317 case TwoMachinesPlay:
9318 if (cps->twoMachinesColor[0] == 'w')
9319 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9321 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9328 } else if (strncmp(message, "checkmate", 9) == 0) {
9329 if (WhiteOnMove(forwardMostMove)) {
9330 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9332 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9335 } else if (strstr(message, "Draw") != NULL ||
9336 strstr(message, "game is a draw") != NULL) {
9337 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9339 } else if (strstr(message, "offer") != NULL &&
9340 strstr(message, "draw") != NULL) {
9342 if (appData.zippyPlay && first.initDone) {
9343 /* Relay offer to ICS */
9344 SendToICS(ics_prefix);
9345 SendToICS("draw\n");
9348 cps->offeredDraw = 2; /* valid until this engine moves twice */
9349 if (gameMode == TwoMachinesPlay) {
9350 if (cps->other->offeredDraw) {
9351 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9352 /* [HGM] in two-machine mode we delay relaying draw offer */
9353 /* until after we also have move, to see if it is really claim */
9355 } else if (gameMode == MachinePlaysWhite ||
9356 gameMode == MachinePlaysBlack) {
9357 if (userOfferedDraw) {
9358 DisplayInformation(_("Machine accepts your draw offer"));
9359 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9361 DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9368 * Look for thinking output
9370 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9371 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9373 int plylev, mvleft, mvtot, curscore, time;
9374 char mvname[MOVE_LEN];
9378 int prefixHint = FALSE;
9379 mvname[0] = NULLCHAR;
9382 case MachinePlaysBlack:
9383 case IcsPlayingBlack:
9384 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9386 case MachinePlaysWhite:
9387 case IcsPlayingWhite:
9388 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9393 case IcsObserving: /* [DM] icsEngineAnalyze */
9394 if (!appData.icsEngineAnalyze) ignore = TRUE;
9396 case TwoMachinesPlay:
9397 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9407 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9409 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9410 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9412 if (plyext != ' ' && plyext != '\t') {
9416 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9417 if( cps->scoreIsAbsolute &&
9418 ( gameMode == MachinePlaysBlack ||
9419 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9420 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9421 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9422 !WhiteOnMove(currentMove)
9425 curscore = -curscore;
9428 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9430 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9433 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9434 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9435 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9436 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9437 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9438 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9442 /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9443 DisplayError(_("failed writing PV"), 0);
9446 tempStats.depth = plylev;
9447 tempStats.nodes = nodes;
9448 tempStats.time = time;
9449 tempStats.score = curscore;
9450 tempStats.got_only_move = 0;
9452 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9455 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9456 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9457 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9458 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9459 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9460 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9461 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9462 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9465 /* Buffer overflow protection */
9466 if (pv[0] != NULLCHAR) {
9467 if (strlen(pv) >= sizeof(tempStats.movelist)
9468 && appData.debugMode) {
9470 "PV is too long; using the first %u bytes.\n",
9471 (unsigned) sizeof(tempStats.movelist) - 1);
9474 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9476 sprintf(tempStats.movelist, " no PV\n");
9479 if (tempStats.seen_stat) {
9480 tempStats.ok_to_send = 1;
9483 if (strchr(tempStats.movelist, '(') != NULL) {
9484 tempStats.line_is_book = 1;
9485 tempStats.nr_moves = 0;
9486 tempStats.moves_left = 0;
9488 tempStats.line_is_book = 0;
9491 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9492 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9494 SendProgramStatsToFrontend( cps, &tempStats );
9497 [AS] Protect the thinkOutput buffer from overflow... this
9498 is only useful if buf1 hasn't overflowed first!
9500 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9502 (gameMode == TwoMachinesPlay ?
9503 ToUpper(cps->twoMachinesColor[0]) : ' '),
9504 ((double) curscore) / 100.0,
9505 prefixHint ? lastHint : "",
9506 prefixHint ? " " : "" );
9508 if( buf1[0] != NULLCHAR ) {
9509 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9511 if( strlen(pv) > max_len ) {
9512 if( appData.debugMode) {
9513 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9515 pv[max_len+1] = '\0';
9518 strcat( thinkOutput, pv);
9521 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9522 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9523 DisplayMove(currentMove - 1);
9527 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9528 /* crafty (9.25+) says "(only move) <move>"
9529 * if there is only 1 legal move
9531 sscanf(p, "(only move) %s", buf1);
9532 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9533 sprintf(programStats.movelist, "%s (only move)", buf1);
9534 programStats.depth = 1;
9535 programStats.nr_moves = 1;
9536 programStats.moves_left = 1;
9537 programStats.nodes = 1;
9538 programStats.time = 1;
9539 programStats.got_only_move = 1;
9541 /* Not really, but we also use this member to
9542 mean "line isn't going to change" (Crafty
9543 isn't searching, so stats won't change) */
9544 programStats.line_is_book = 1;
9546 SendProgramStatsToFrontend( cps, &programStats );
9548 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9549 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9550 DisplayMove(currentMove - 1);
9553 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9554 &time, &nodes, &plylev, &mvleft,
9555 &mvtot, mvname) >= 5) {
9556 /* The stat01: line is from Crafty (9.29+) in response
9557 to the "." command */
9558 programStats.seen_stat = 1;
9559 cps->maybeThinking = TRUE;
9561 if (programStats.got_only_move || !appData.periodicUpdates)
9564 programStats.depth = plylev;
9565 programStats.time = time;
9566 programStats.nodes = nodes;
9567 programStats.moves_left = mvleft;
9568 programStats.nr_moves = mvtot;
9569 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9570 programStats.ok_to_send = 1;
9571 programStats.movelist[0] = '\0';
9573 SendProgramStatsToFrontend( cps, &programStats );
9577 } else if (strncmp(message,"++",2) == 0) {
9578 /* Crafty 9.29+ outputs this */
9579 programStats.got_fail = 2;
9582 } else if (strncmp(message,"--",2) == 0) {
9583 /* Crafty 9.29+ outputs this */
9584 programStats.got_fail = 1;
9587 } else if (thinkOutput[0] != NULLCHAR &&
9588 strncmp(message, " ", 4) == 0) {
9589 unsigned message_len;
9592 while (*p && *p == ' ') p++;
9594 message_len = strlen( p );
9596 /* [AS] Avoid buffer overflow */
9597 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9598 strcat(thinkOutput, " ");
9599 strcat(thinkOutput, p);
9602 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9603 strcat(programStats.movelist, " ");
9604 strcat(programStats.movelist, p);
9607 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9608 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9609 DisplayMove(currentMove - 1);
9617 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9618 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9620 ChessProgramStats cpstats;
9622 if (plyext != ' ' && plyext != '\t') {
9626 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9627 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9628 curscore = -curscore;
9631 cpstats.depth = plylev;
9632 cpstats.nodes = nodes;
9633 cpstats.time = time;
9634 cpstats.score = curscore;
9635 cpstats.got_only_move = 0;
9636 cpstats.movelist[0] = '\0';
9638 if (buf1[0] != NULLCHAR) {
9639 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9642 cpstats.ok_to_send = 0;
9643 cpstats.line_is_book = 0;
9644 cpstats.nr_moves = 0;
9645 cpstats.moves_left = 0;
9647 SendProgramStatsToFrontend( cps, &cpstats );
9654 /* Parse a game score from the character string "game", and
9655 record it as the history of the current game. The game
9656 score is NOT assumed to start from the standard position.
9657 The display is not updated in any way.
9660 ParseGameHistory (char *game)
9663 int fromX, fromY, toX, toY, boardIndex;
9668 if (appData.debugMode)
9669 fprintf(debugFP, "Parsing game history: %s\n", game);
9671 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9672 gameInfo.site = StrSave(appData.icsHost);
9673 gameInfo.date = PGNDate();
9674 gameInfo.round = StrSave("-");
9676 /* Parse out names of players */
9677 while (*game == ' ') game++;
9679 while (*game != ' ') *p++ = *game++;
9681 gameInfo.white = StrSave(buf);
9682 while (*game == ' ') game++;
9684 while (*game != ' ' && *game != '\n') *p++ = *game++;
9686 gameInfo.black = StrSave(buf);
9689 boardIndex = blackPlaysFirst ? 1 : 0;
9692 yyboardindex = boardIndex;
9693 moveType = (ChessMove) Myylex();
9695 case IllegalMove: /* maybe suicide chess, etc. */
9696 if (appData.debugMode) {
9697 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9698 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9699 setbuf(debugFP, NULL);
9701 case WhitePromotion:
9702 case BlackPromotion:
9703 case WhiteNonPromotion:
9704 case BlackNonPromotion:
9707 case WhiteCapturesEnPassant:
9708 case BlackCapturesEnPassant:
9709 case WhiteKingSideCastle:
9710 case WhiteQueenSideCastle:
9711 case BlackKingSideCastle:
9712 case BlackQueenSideCastle:
9713 case WhiteKingSideCastleWild:
9714 case WhiteQueenSideCastleWild:
9715 case BlackKingSideCastleWild:
9716 case BlackQueenSideCastleWild:
9718 case WhiteHSideCastleFR:
9719 case WhiteASideCastleFR:
9720 case BlackHSideCastleFR:
9721 case BlackASideCastleFR:
9723 fromX = currentMoveString[0] - AAA;
9724 fromY = currentMoveString[1] - ONE;
9725 toX = currentMoveString[2] - AAA;
9726 toY = currentMoveString[3] - ONE;
9727 promoChar = currentMoveString[4];
9731 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9732 fromX = moveType == WhiteDrop ?
9733 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9734 (int) CharToPiece(ToLower(currentMoveString[0]));
9736 toX = currentMoveString[2] - AAA;
9737 toY = currentMoveString[3] - ONE;
9738 promoChar = NULLCHAR;
9742 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9743 if (appData.debugMode) {
9744 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9745 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9746 setbuf(debugFP, NULL);
9748 DisplayError(buf, 0);
9750 case ImpossibleMove:
9752 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9753 if (appData.debugMode) {
9754 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9755 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9756 setbuf(debugFP, NULL);
9758 DisplayError(buf, 0);
9761 if (boardIndex < backwardMostMove) {
9762 /* Oops, gap. How did that happen? */
9763 DisplayError(_("Gap in move list"), 0);
9766 backwardMostMove = blackPlaysFirst ? 1 : 0;
9767 if (boardIndex > forwardMostMove) {
9768 forwardMostMove = boardIndex;
9772 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9773 strcat(parseList[boardIndex-1], " ");
9774 strcat(parseList[boardIndex-1], yy_text);
9786 case GameUnfinished:
9787 if (gameMode == IcsExamining) {
9788 if (boardIndex < backwardMostMove) {
9789 /* Oops, gap. How did that happen? */
9792 backwardMostMove = blackPlaysFirst ? 1 : 0;
9795 gameInfo.result = moveType;
9796 p = strchr(yy_text, '{');
9797 if (p == NULL) p = strchr(yy_text, '(');
9800 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9802 q = strchr(p, *p == '{' ? '}' : ')');
9803 if (q != NULL) *q = NULLCHAR;
9806 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9807 gameInfo.resultDetails = StrSave(p);
9810 if (boardIndex >= forwardMostMove &&
9811 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9812 backwardMostMove = blackPlaysFirst ? 1 : 0;
9815 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9816 fromY, fromX, toY, toX, promoChar,
9817 parseList[boardIndex]);
9818 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9819 /* currentMoveString is set as a side-effect of yylex */
9820 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9821 strcat(moveList[boardIndex], "\n");
9823 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9824 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9830 if(!IS_SHOGI(gameInfo.variant))
9831 strcat(parseList[boardIndex - 1], "+");
9835 strcat(parseList[boardIndex - 1], "#");
9842 /* Apply a move to the given board */
9844 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9846 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9847 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9849 /* [HGM] compute & store e.p. status and castling rights for new position */
9850 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9852 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9853 oldEP = (signed char)board[EP_STATUS];
9854 board[EP_STATUS] = EP_NONE;
9856 if (fromY == DROP_RANK) {
9858 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9859 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9862 piece = board[toY][toX] = (ChessSquare) fromX;
9864 // ChessSquare victim;
9867 if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9868 // victim = board[killY][killX],
9869 board[killY][killX] = EmptySquare,
9870 board[EP_STATUS] = EP_CAPTURE;
9872 if( board[toY][toX] != EmptySquare ) {
9873 board[EP_STATUS] = EP_CAPTURE;
9874 if( (fromX != toX || fromY != toY) && // not igui!
9875 (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9876 captured == BlackLion && board[fromY][fromX] != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules
9877 board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9881 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9882 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9883 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9885 if( board[fromY][fromX] == WhitePawn ) {
9886 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9887 board[EP_STATUS] = EP_PAWN_MOVE;
9889 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9890 gameInfo.variant != VariantBerolina || toX < fromX)
9891 board[EP_STATUS] = toX | berolina;
9892 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9893 gameInfo.variant != VariantBerolina || toX > fromX)
9894 board[EP_STATUS] = toX;
9897 if( board[fromY][fromX] == BlackPawn ) {
9898 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9899 board[EP_STATUS] = EP_PAWN_MOVE;
9900 if( toY-fromY== -2) {
9901 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9902 gameInfo.variant != VariantBerolina || toX < fromX)
9903 board[EP_STATUS] = toX | berolina;
9904 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9905 gameInfo.variant != VariantBerolina || toX > fromX)
9906 board[EP_STATUS] = toX;
9910 for(i=0; i<nrCastlingRights; i++) {
9911 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9912 board[CASTLING][i] == toX && castlingRank[i] == toY
9913 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9916 if(gameInfo.variant == VariantSChess) { // update virginity
9917 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9918 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9919 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
9920 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
9923 if (fromX == toX && fromY == toY) return;
9925 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9926 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9927 if(gameInfo.variant == VariantKnightmate)
9928 king += (int) WhiteUnicorn - (int) WhiteKing;
9930 /* Code added by Tord: */
9931 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9932 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9933 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9934 board[fromY][fromX] = EmptySquare;
9935 board[toY][toX] = EmptySquare;
9936 if((toX > fromX) != (piece == WhiteRook)) {
9937 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9939 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9941 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9942 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9943 board[fromY][fromX] = EmptySquare;
9944 board[toY][toX] = EmptySquare;
9945 if((toX > fromX) != (piece == BlackRook)) {
9946 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9948 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9950 /* End of code added by Tord */
9952 } else if (board[fromY][fromX] == king
9953 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9954 && toY == fromY && toX > fromX+1) {
9955 board[fromY][fromX] = EmptySquare;
9956 board[toY][toX] = king;
9957 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9958 board[fromY][BOARD_RGHT-1] = EmptySquare;
9959 } else if (board[fromY][fromX] == king
9960 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9961 && toY == fromY && toX < fromX-1) {
9962 board[fromY][fromX] = EmptySquare;
9963 board[toY][toX] = king;
9964 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9965 board[fromY][BOARD_LEFT] = EmptySquare;
9966 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9967 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9968 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9970 /* white pawn promotion */
9971 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9972 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9973 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9974 board[fromY][fromX] = EmptySquare;
9975 } else if ((fromY >= BOARD_HEIGHT>>1)
9976 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9978 && gameInfo.variant != VariantXiangqi
9979 && gameInfo.variant != VariantBerolina
9980 && (board[fromY][fromX] == WhitePawn)
9981 && (board[toY][toX] == EmptySquare)) {
9982 board[fromY][fromX] = EmptySquare;
9983 board[toY][toX] = WhitePawn;
9984 captured = board[toY - 1][toX];
9985 board[toY - 1][toX] = EmptySquare;
9986 } else if ((fromY == BOARD_HEIGHT-4)
9988 && gameInfo.variant == VariantBerolina
9989 && (board[fromY][fromX] == WhitePawn)
9990 && (board[toY][toX] == EmptySquare)) {
9991 board[fromY][fromX] = EmptySquare;
9992 board[toY][toX] = WhitePawn;
9993 if(oldEP & EP_BEROLIN_A) {
9994 captured = board[fromY][fromX-1];
9995 board[fromY][fromX-1] = EmptySquare;
9996 }else{ captured = board[fromY][fromX+1];
9997 board[fromY][fromX+1] = EmptySquare;
9999 } else if (board[fromY][fromX] == king
10000 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10001 && toY == fromY && toX > fromX+1) {
10002 board[fromY][fromX] = EmptySquare;
10003 board[toY][toX] = king;
10004 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
10005 board[fromY][BOARD_RGHT-1] = EmptySquare;
10006 } else if (board[fromY][fromX] == king
10007 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10008 && toY == fromY && toX < fromX-1) {
10009 board[fromY][fromX] = EmptySquare;
10010 board[toY][toX] = king;
10011 board[toY][toX+1] = board[fromY][BOARD_LEFT];
10012 board[fromY][BOARD_LEFT] = EmptySquare;
10013 } else if (fromY == 7 && fromX == 3
10014 && board[fromY][fromX] == BlackKing
10015 && toY == 7 && toX == 5) {
10016 board[fromY][fromX] = EmptySquare;
10017 board[toY][toX] = BlackKing;
10018 board[fromY][7] = EmptySquare;
10019 board[toY][4] = BlackRook;
10020 } else if (fromY == 7 && fromX == 3
10021 && board[fromY][fromX] == BlackKing
10022 && toY == 7 && toX == 1) {
10023 board[fromY][fromX] = EmptySquare;
10024 board[toY][toX] = BlackKing;
10025 board[fromY][0] = EmptySquare;
10026 board[toY][2] = BlackRook;
10027 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10028 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10029 && toY < promoRank && promoChar
10031 /* black pawn promotion */
10032 board[toY][toX] = CharToPiece(ToLower(promoChar));
10033 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10034 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10035 board[fromY][fromX] = EmptySquare;
10036 } else if ((fromY < BOARD_HEIGHT>>1)
10037 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
10039 && gameInfo.variant != VariantXiangqi
10040 && gameInfo.variant != VariantBerolina
10041 && (board[fromY][fromX] == BlackPawn)
10042 && (board[toY][toX] == EmptySquare)) {
10043 board[fromY][fromX] = EmptySquare;
10044 board[toY][toX] = BlackPawn;
10045 captured = board[toY + 1][toX];
10046 board[toY + 1][toX] = EmptySquare;
10047 } else if ((fromY == 3)
10049 && gameInfo.variant == VariantBerolina
10050 && (board[fromY][fromX] == BlackPawn)
10051 && (board[toY][toX] == EmptySquare)) {
10052 board[fromY][fromX] = EmptySquare;
10053 board[toY][toX] = BlackPawn;
10054 if(oldEP & EP_BEROLIN_A) {
10055 captured = board[fromY][fromX-1];
10056 board[fromY][fromX-1] = EmptySquare;
10057 }else{ captured = board[fromY][fromX+1];
10058 board[fromY][fromX+1] = EmptySquare;
10061 ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10062 board[fromY][fromX] = EmptySquare;
10063 board[toY][toX] = piece;
10067 if (gameInfo.holdingsWidth != 0) {
10069 /* !!A lot more code needs to be written to support holdings */
10070 /* [HGM] OK, so I have written it. Holdings are stored in the */
10071 /* penultimate board files, so they are automaticlly stored */
10072 /* in the game history. */
10073 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10074 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10075 /* Delete from holdings, by decreasing count */
10076 /* and erasing image if necessary */
10077 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10078 if(p < (int) BlackPawn) { /* white drop */
10079 p -= (int)WhitePawn;
10080 p = PieceToNumber((ChessSquare)p);
10081 if(p >= gameInfo.holdingsSize) p = 0;
10082 if(--board[p][BOARD_WIDTH-2] <= 0)
10083 board[p][BOARD_WIDTH-1] = EmptySquare;
10084 if((int)board[p][BOARD_WIDTH-2] < 0)
10085 board[p][BOARD_WIDTH-2] = 0;
10086 } else { /* black drop */
10087 p -= (int)BlackPawn;
10088 p = PieceToNumber((ChessSquare)p);
10089 if(p >= gameInfo.holdingsSize) p = 0;
10090 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10091 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10092 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10093 board[BOARD_HEIGHT-1-p][1] = 0;
10096 if (captured != EmptySquare && gameInfo.holdingsSize > 0
10097 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
10098 /* [HGM] holdings: Add to holdings, if holdings exist */
10099 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10100 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10101 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10103 p = (int) captured;
10104 if (p >= (int) BlackPawn) {
10105 p -= (int)BlackPawn;
10106 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10107 /* in Shogi restore piece to its original first */
10108 captured = (ChessSquare) (DEMOTED captured);
10111 p = PieceToNumber((ChessSquare)p);
10112 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10113 board[p][BOARD_WIDTH-2]++;
10114 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10116 p -= (int)WhitePawn;
10117 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10118 captured = (ChessSquare) (DEMOTED captured);
10121 p = PieceToNumber((ChessSquare)p);
10122 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10123 board[BOARD_HEIGHT-1-p][1]++;
10124 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10127 } else if (gameInfo.variant == VariantAtomic) {
10128 if (captured != EmptySquare) {
10130 for (y = toY-1; y <= toY+1; y++) {
10131 for (x = toX-1; x <= toX+1; x++) {
10132 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10133 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10134 board[y][x] = EmptySquare;
10138 board[toY][toX] = EmptySquare;
10142 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10143 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10145 if(promoChar == '+') {
10146 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10147 board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10148 if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10149 board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10150 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10151 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10152 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10153 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10154 board[toY][toX] = newPiece;
10156 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10157 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10158 // [HGM] superchess: take promotion piece out of holdings
10159 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10160 if((int)piece < (int)BlackPawn) { // determine stm from piece color
10161 if(!--board[k][BOARD_WIDTH-2])
10162 board[k][BOARD_WIDTH-1] = EmptySquare;
10164 if(!--board[BOARD_HEIGHT-1-k][1])
10165 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10170 /* Updates forwardMostMove */
10172 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10174 int x = toX, y = toY;
10175 char *s = parseList[forwardMostMove];
10176 ChessSquare p = boards[forwardMostMove][toY][toX];
10177 // forwardMostMove++; // [HGM] bare: moved downstream
10179 if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10180 (void) CoordsToAlgebraic(boards[forwardMostMove],
10181 PosFlags(forwardMostMove),
10182 fromY, fromX, y, x, promoChar,
10184 if(killX >= 0 && killY >= 0)
10185 sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10187 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10188 int timeLeft; static int lastLoadFlag=0; int king, piece;
10189 piece = boards[forwardMostMove][fromY][fromX];
10190 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10191 if(gameInfo.variant == VariantKnightmate)
10192 king += (int) WhiteUnicorn - (int) WhiteKing;
10193 if(forwardMostMove == 0) {
10194 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10195 fprintf(serverMoves, "%s;", UserName());
10196 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10197 fprintf(serverMoves, "%s;", second.tidy);
10198 fprintf(serverMoves, "%s;", first.tidy);
10199 if(gameMode == MachinePlaysWhite)
10200 fprintf(serverMoves, "%s;", UserName());
10201 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10202 fprintf(serverMoves, "%s;", second.tidy);
10203 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10204 lastLoadFlag = loadFlag;
10206 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10207 // print castling suffix
10208 if( toY == fromY && piece == king ) {
10210 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10212 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10215 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10216 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
10217 boards[forwardMostMove][toY][toX] == EmptySquare
10218 && fromX != toX && fromY != toY)
10219 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10220 // promotion suffix
10221 if(promoChar != NULLCHAR) {
10222 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10223 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10224 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10225 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10228 char buf[MOVE_LEN*2], *p; int len;
10229 fprintf(serverMoves, "/%d/%d",
10230 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10231 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10232 else timeLeft = blackTimeRemaining/1000;
10233 fprintf(serverMoves, "/%d", timeLeft);
10234 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10235 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10236 if(p = strchr(buf, '=')) *p = NULLCHAR;
10237 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10238 fprintf(serverMoves, "/%s", buf);
10240 fflush(serverMoves);
10243 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10244 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10247 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10248 if (commentList[forwardMostMove+1] != NULL) {
10249 free(commentList[forwardMostMove+1]);
10250 commentList[forwardMostMove+1] = NULL;
10252 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10253 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10254 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10255 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10256 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10257 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10258 adjustedClock = FALSE;
10259 gameInfo.result = GameUnfinished;
10260 if (gameInfo.resultDetails != NULL) {
10261 free(gameInfo.resultDetails);
10262 gameInfo.resultDetails = NULL;
10264 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10265 moveList[forwardMostMove - 1]);
10266 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10272 if(!IS_SHOGI(gameInfo.variant))
10273 strcat(parseList[forwardMostMove - 1], "+");
10277 strcat(parseList[forwardMostMove - 1], "#");
10282 /* Updates currentMove if not pausing */
10284 ShowMove (int fromX, int fromY, int toX, int toY)
10286 int instant = (gameMode == PlayFromGameFile) ?
10287 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10288 if(appData.noGUI) return;
10289 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10291 if (forwardMostMove == currentMove + 1) {
10292 AnimateMove(boards[forwardMostMove - 1],
10293 fromX, fromY, toX, toY);
10296 currentMove = forwardMostMove;
10299 killX = killY = -1; // [HGM] lion: used up
10301 if (instant) return;
10303 DisplayMove(currentMove - 1);
10304 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10305 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10306 SetHighlights(fromX, fromY, toX, toY);
10309 DrawPosition(FALSE, boards[currentMove]);
10310 DisplayBothClocks();
10311 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10315 SendEgtPath (ChessProgramState *cps)
10316 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10317 char buf[MSG_SIZ], name[MSG_SIZ], *p;
10319 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10322 char c, *q = name+1, *r, *s;
10324 name[0] = ','; // extract next format name from feature and copy with prefixed ','
10325 while(*p && *p != ',') *q++ = *p++;
10326 *q++ = ':'; *q = 0;
10327 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10328 strcmp(name, ",nalimov:") == 0 ) {
10329 // take nalimov path from the menu-changeable option first, if it is defined
10330 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10331 SendToProgram(buf,cps); // send egtbpath command for nalimov
10333 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10334 (s = StrStr(appData.egtFormats, name)) != NULL) {
10335 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10336 s = r = StrStr(s, ":") + 1; // beginning of path info
10337 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10338 c = *r; *r = 0; // temporarily null-terminate path info
10339 *--q = 0; // strip of trailig ':' from name
10340 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10342 SendToProgram(buf,cps); // send egtbpath command for this format
10344 if(*p == ',') p++; // read away comma to position for next format name
10349 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10351 int width = 8, height = 8, holdings = 0; // most common sizes
10352 if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10353 // correct the deviations default for each variant
10354 if( v == VariantXiangqi ) width = 9, height = 10;
10355 if( v == VariantShogi ) width = 9, height = 9, holdings = 7;
10356 if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10357 if( v == VariantCapablanca || v == VariantCapaRandom ||
10358 v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10360 if( v == VariantCourier ) width = 12;
10361 if( v == VariantSuper ) holdings = 8;
10362 if( v == VariantGreat ) width = 10, holdings = 8;
10363 if( v == VariantSChess ) holdings = 7;
10364 if( v == VariantGrand ) width = 10, height = 10, holdings = 7;
10365 if( v == VariantChuChess) width = 10, height = 10;
10366 if( v == VariantChu ) width = 12, height = 12;
10367 return boardWidth >= 0 && boardWidth != width || // -1 is default,
10368 boardHeight >= 0 && boardHeight != height || // and thus by definition OK
10369 holdingsSize >= 0 && holdingsSize != holdings;
10372 char variantError[MSG_SIZ];
10375 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10376 { // returns error message (recognizable by upper-case) if engine does not support the variant
10377 char *p, *variant = VariantName(v);
10378 static char b[MSG_SIZ];
10379 if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10380 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10381 holdingsSize, variant); // cook up sized variant name
10382 /* [HGM] varsize: try first if this deviant size variant is specifically known */
10383 if(StrStr(list, b) == NULL) {
10384 // specific sized variant not known, check if general sizing allowed
10385 if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10386 snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10387 boardWidth, boardHeight, holdingsSize, engine);
10390 /* [HGM] here we really should compare with the maximum supported board size */
10392 } else snprintf(b, MSG_SIZ,"%s", variant);
10393 if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10394 p = StrStr(list, b);
10395 while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10397 // occurs not at all in list, or only as sub-string
10398 snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10399 if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10400 int l = strlen(variantError);
10402 while(p != list && p[-1] != ',') p--;
10403 q = strchr(p, ',');
10404 if(q) *q = NULLCHAR;
10405 snprintf(variantError + l, MSG_SIZ - l, _(", but %s is"), p);
10414 InitChessProgram (ChessProgramState *cps, int setup)
10415 /* setup needed to setup FRC opening position */
10417 char buf[MSG_SIZ], *b;
10418 if (appData.noChessProgram) return;
10419 hintRequested = FALSE;
10420 bookRequested = FALSE;
10422 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10423 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10424 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10425 if(cps->memSize) { /* [HGM] memory */
10426 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10427 SendToProgram(buf, cps);
10429 SendEgtPath(cps); /* [HGM] EGT */
10430 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10431 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10432 SendToProgram(buf, cps);
10435 setboardSpoiledMachineBlack = FALSE;
10436 SendToProgram(cps->initString, cps);
10437 if (gameInfo.variant != VariantNormal &&
10438 gameInfo.variant != VariantLoadable
10439 /* [HGM] also send variant if board size non-standard */
10440 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10442 b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10443 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10445 DisplayFatalError(variantError, 0, 1);
10449 snprintf(buf, MSG_SIZ, "variant %s\n", b);
10450 SendToProgram(buf, cps);
10452 currentlyInitializedVariant = gameInfo.variant;
10454 /* [HGM] send opening position in FRC to first engine */
10456 SendToProgram("force\n", cps);
10458 /* engine is now in force mode! Set flag to wake it up after first move. */
10459 setboardSpoiledMachineBlack = 1;
10462 if (cps->sendICS) {
10463 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10464 SendToProgram(buf, cps);
10466 cps->maybeThinking = FALSE;
10467 cps->offeredDraw = 0;
10468 if (!appData.icsActive) {
10469 SendTimeControl(cps, movesPerSession, timeControl,
10470 timeIncrement, appData.searchDepth,
10473 if (appData.showThinking
10474 // [HGM] thinking: four options require thinking output to be sent
10475 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10477 SendToProgram("post\n", cps);
10479 SendToProgram("hard\n", cps);
10480 if (!appData.ponderNextMove) {
10481 /* Warning: "easy" is a toggle in GNU Chess, so don't send
10482 it without being sure what state we are in first. "hard"
10483 is not a toggle, so that one is OK.
10485 SendToProgram("easy\n", cps);
10487 if (cps->usePing) {
10488 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10489 SendToProgram(buf, cps);
10491 cps->initDone = TRUE;
10492 ClearEngineOutputPane(cps == &second);
10497 ResendOptions (ChessProgramState *cps)
10498 { // send the stored value of the options
10501 Option *opt = cps->option;
10502 for(i=0; i<cps->nrOptions; i++, opt++) {
10503 switch(opt->type) {
10507 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10510 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10513 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10519 SendToProgram(buf, cps);
10524 StartChessProgram (ChessProgramState *cps)
10529 if (appData.noChessProgram) return;
10530 cps->initDone = FALSE;
10532 if (strcmp(cps->host, "localhost") == 0) {
10533 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10534 } else if (*appData.remoteShell == NULLCHAR) {
10535 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10537 if (*appData.remoteUser == NULLCHAR) {
10538 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10541 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10542 cps->host, appData.remoteUser, cps->program);
10544 err = StartChildProcess(buf, "", &cps->pr);
10548 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10549 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10550 if(cps != &first) return;
10551 appData.noChessProgram = TRUE;
10554 // DisplayFatalError(buf, err, 1);
10555 // cps->pr = NoProc;
10556 // cps->isr = NULL;
10560 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10561 if (cps->protocolVersion > 1) {
10562 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10563 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10564 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10565 cps->comboCnt = 0; // and values of combo boxes
10567 SendToProgram(buf, cps);
10568 if(cps->reload) ResendOptions(cps);
10570 SendToProgram("xboard\n", cps);
10575 TwoMachinesEventIfReady P((void))
10577 static int curMess = 0;
10578 if (first.lastPing != first.lastPong) {
10579 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10580 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10583 if (second.lastPing != second.lastPong) {
10584 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10585 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10588 DisplayMessage("", ""); curMess = 0;
10589 TwoMachinesEvent();
10593 MakeName (char *template)
10597 static char buf[MSG_SIZ];
10601 clock = time((time_t *)NULL);
10602 tm = localtime(&clock);
10604 while(*p++ = *template++) if(p[-1] == '%') {
10605 switch(*template++) {
10606 case 0: *p = 0; return buf;
10607 case 'Y': i = tm->tm_year+1900; break;
10608 case 'y': i = tm->tm_year-100; break;
10609 case 'M': i = tm->tm_mon+1; break;
10610 case 'd': i = tm->tm_mday; break;
10611 case 'h': i = tm->tm_hour; break;
10612 case 'm': i = tm->tm_min; break;
10613 case 's': i = tm->tm_sec; break;
10616 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10622 CountPlayers (char *p)
10625 while(p = strchr(p, '\n')) p++, n++; // count participants
10630 WriteTourneyFile (char *results, FILE *f)
10631 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10632 if(f == NULL) f = fopen(appData.tourneyFile, "w");
10633 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10634 // create a file with tournament description
10635 fprintf(f, "-participants {%s}\n", appData.participants);
10636 fprintf(f, "-seedBase %d\n", appData.seedBase);
10637 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10638 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10639 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10640 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10641 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10642 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10643 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10644 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10645 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10646 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10647 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10648 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10649 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10650 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10651 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10652 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10653 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10654 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10655 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10656 fprintf(f, "-smpCores %d\n", appData.smpCores);
10658 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10660 fprintf(f, "-mps %d\n", appData.movesPerSession);
10661 fprintf(f, "-tc %s\n", appData.timeControl);
10662 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10664 fprintf(f, "-results \"%s\"\n", results);
10669 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10672 Substitute (char *participants, int expunge)
10674 int i, changed, changes=0, nPlayers=0;
10675 char *p, *q, *r, buf[MSG_SIZ];
10676 if(participants == NULL) return;
10677 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10678 r = p = participants; q = appData.participants;
10679 while(*p && *p == *q) {
10680 if(*p == '\n') r = p+1, nPlayers++;
10683 if(*p) { // difference
10684 while(*p && *p++ != '\n');
10685 while(*q && *q++ != '\n');
10686 changed = nPlayers;
10687 changes = 1 + (strcmp(p, q) != 0);
10689 if(changes == 1) { // a single engine mnemonic was changed
10690 q = r; while(*q) nPlayers += (*q++ == '\n');
10691 p = buf; while(*r && (*p = *r++) != '\n') p++;
10693 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10694 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10695 if(mnemonic[i]) { // The substitute is valid
10697 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10698 flock(fileno(f), LOCK_EX);
10699 ParseArgsFromFile(f);
10700 fseek(f, 0, SEEK_SET);
10701 FREE(appData.participants); appData.participants = participants;
10702 if(expunge) { // erase results of replaced engine
10703 int len = strlen(appData.results), w, b, dummy;
10704 for(i=0; i<len; i++) {
10705 Pairing(i, nPlayers, &w, &b, &dummy);
10706 if((w == changed || b == changed) && appData.results[i] == '*') {
10707 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10712 for(i=0; i<len; i++) {
10713 Pairing(i, nPlayers, &w, &b, &dummy);
10714 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10717 WriteTourneyFile(appData.results, f);
10718 fclose(f); // release lock
10721 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10723 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10724 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10725 free(participants);
10730 CheckPlayers (char *participants)
10733 char buf[MSG_SIZ], *p;
10734 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10735 while(p = strchr(participants, '\n')) {
10737 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10739 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10741 DisplayError(buf, 0);
10745 participants = p + 1;
10751 CreateTourney (char *name)
10754 if(matchMode && strcmp(name, appData.tourneyFile)) {
10755 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10757 if(name[0] == NULLCHAR) {
10758 if(appData.participants[0])
10759 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10762 f = fopen(name, "r");
10763 if(f) { // file exists
10764 ASSIGN(appData.tourneyFile, name);
10765 ParseArgsFromFile(f); // parse it
10767 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10768 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10769 DisplayError(_("Not enough participants"), 0);
10772 if(CheckPlayers(appData.participants)) return 0;
10773 ASSIGN(appData.tourneyFile, name);
10774 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10775 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10778 appData.noChessProgram = FALSE;
10779 appData.clockMode = TRUE;
10785 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10787 char buf[MSG_SIZ], *p, *q;
10788 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10789 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10790 skip = !all && group[0]; // if group requested, we start in skip mode
10791 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10792 p = names; q = buf; header = 0;
10793 while(*p && *p != '\n') *q++ = *p++;
10795 if(*p == '\n') p++;
10796 if(buf[0] == '#') {
10797 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10798 depth++; // we must be entering a new group
10799 if(all) continue; // suppress printing group headers when complete list requested
10801 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10803 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10804 if(engineList[i]) free(engineList[i]);
10805 engineList[i] = strdup(buf);
10806 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10807 if(engineMnemonic[i]) free(engineMnemonic[i]);
10808 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10810 sscanf(q + 8, "%s", buf + strlen(buf));
10813 engineMnemonic[i] = strdup(buf);
10816 engineList[i] = engineMnemonic[i] = NULL;
10820 // following implemented as macro to avoid type limitations
10821 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10824 SwapEngines (int n)
10825 { // swap settings for first engine and other engine (so far only some selected options)
10830 SWAP(chessProgram, p)
10832 SWAP(hasOwnBookUCI, h)
10833 SWAP(protocolVersion, h)
10835 SWAP(scoreIsAbsolute, h)
10840 SWAP(engOptions, p)
10841 SWAP(engInitString, p)
10842 SWAP(computerString, p)
10844 SWAP(fenOverride, p)
10846 SWAP(accumulateTC, h)
10853 GetEngineLine (char *s, int n)
10857 extern char *icsNames;
10858 if(!s || !*s) return 0;
10859 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10860 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10861 if(!mnemonic[i]) return 0;
10862 if(n == 11) return 1; // just testing if there was a match
10863 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10864 if(n == 1) SwapEngines(n);
10865 ParseArgsFromString(buf);
10866 if(n == 1) SwapEngines(n);
10867 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10868 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10869 ParseArgsFromString(buf);
10875 SetPlayer (int player, char *p)
10876 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10878 char buf[MSG_SIZ], *engineName;
10879 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10880 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10881 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10883 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10884 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10885 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10886 ParseArgsFromString(buf);
10887 } else { // no engine with this nickname is installed!
10888 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10889 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10890 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10892 DisplayError(buf, 0);
10899 char *recentEngines;
10902 RecentEngineEvent (int nr)
10905 // SwapEngines(1); // bump first to second
10906 // ReplaceEngine(&second, 1); // and load it there
10907 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10908 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10909 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10910 ReplaceEngine(&first, 0);
10911 FloatToFront(&appData.recentEngineList, command[n]);
10916 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10917 { // determine players from game number
10918 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10920 if(appData.tourneyType == 0) {
10921 roundsPerCycle = (nPlayers - 1) | 1;
10922 pairingsPerRound = nPlayers / 2;
10923 } else if(appData.tourneyType > 0) {
10924 roundsPerCycle = nPlayers - appData.tourneyType;
10925 pairingsPerRound = appData.tourneyType;
10927 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10928 gamesPerCycle = gamesPerRound * roundsPerCycle;
10929 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10930 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10931 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10932 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10933 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10934 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10936 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10937 if(appData.roundSync) *syncInterval = gamesPerRound;
10939 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10941 if(appData.tourneyType == 0) {
10942 if(curPairing == (nPlayers-1)/2 ) {
10943 *whitePlayer = curRound;
10944 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10946 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10947 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10948 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10949 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10951 } else if(appData.tourneyType > 1) {
10952 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10953 *whitePlayer = curRound + appData.tourneyType;
10954 } else if(appData.tourneyType > 0) {
10955 *whitePlayer = curPairing;
10956 *blackPlayer = curRound + appData.tourneyType;
10959 // take care of white/black alternation per round.
10960 // For cycles and games this is already taken care of by default, derived from matchGame!
10961 return curRound & 1;
10965 NextTourneyGame (int nr, int *swapColors)
10966 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10968 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10970 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10971 tf = fopen(appData.tourneyFile, "r");
10972 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10973 ParseArgsFromFile(tf); fclose(tf);
10974 InitTimeControls(); // TC might be altered from tourney file
10976 nPlayers = CountPlayers(appData.participants); // count participants
10977 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10978 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10981 p = q = appData.results;
10982 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10983 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10984 DisplayMessage(_("Waiting for other game(s)"),"");
10985 waitingForGame = TRUE;
10986 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10989 waitingForGame = FALSE;
10992 if(appData.tourneyType < 0) {
10993 if(nr>=0 && !pairingReceived) {
10995 if(pairing.pr == NoProc) {
10996 if(!appData.pairingEngine[0]) {
10997 DisplayFatalError(_("No pairing engine specified"), 0, 1);
11000 StartChessProgram(&pairing); // starts the pairing engine
11002 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11003 SendToProgram(buf, &pairing);
11004 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11005 SendToProgram(buf, &pairing);
11006 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11008 pairingReceived = 0; // ... so we continue here
11010 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11011 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11012 matchGame = 1; roundNr = nr / syncInterval + 1;
11015 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11017 // redefine engines, engine dir, etc.
11018 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11019 if(first.pr == NoProc) {
11020 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11021 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
11023 if(second.pr == NoProc) {
11025 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11026 SwapEngines(1); // and make that valid for second engine by swapping
11027 InitEngine(&second, 1);
11029 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
11030 UpdateLogos(FALSE); // leave display to ModeHiglight()
11036 { // performs game initialization that does not invoke engines, and then tries to start the game
11037 int res, firstWhite, swapColors = 0;
11038 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11039 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
11041 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11042 if(strcmp(buf, currentDebugFile)) { // name has changed
11043 FILE *f = fopen(buf, "w");
11044 if(f) { // if opening the new file failed, just keep using the old one
11045 ASSIGN(currentDebugFile, buf);
11049 if(appData.serverFileName) {
11050 if(serverFP) fclose(serverFP);
11051 serverFP = fopen(appData.serverFileName, "w");
11052 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11053 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11057 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11058 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11059 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
11060 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11061 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11062 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11063 Reset(FALSE, first.pr != NoProc);
11064 res = LoadGameOrPosition(matchGame); // setup game
11065 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11066 if(!res) return; // abort when bad game/pos file
11067 TwoMachinesEvent();
11071 UserAdjudicationEvent (int result)
11073 ChessMove gameResult = GameIsDrawn;
11076 gameResult = WhiteWins;
11078 else if( result < 0 ) {
11079 gameResult = BlackWins;
11082 if( gameMode == TwoMachinesPlay ) {
11083 GameEnds( gameResult, "User adjudication", GE_XBOARD );
11088 // [HGM] save: calculate checksum of game to make games easily identifiable
11090 StringCheckSum (char *s)
11093 if(s==NULL) return 0;
11094 while(*s) i = i*259 + *s++;
11102 for(i=backwardMostMove; i<forwardMostMove; i++) {
11103 sum += pvInfoList[i].depth;
11104 sum += StringCheckSum(parseList[i]);
11105 sum += StringCheckSum(commentList[i]);
11108 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11109 return sum + StringCheckSum(commentList[i]);
11110 } // end of save patch
11113 GameEnds (ChessMove result, char *resultDetails, int whosays)
11115 GameMode nextGameMode;
11117 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11119 if(endingGame) return; /* [HGM] crash: forbid recursion */
11121 if(twoBoards) { // [HGM] dual: switch back to one board
11122 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11123 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11125 if (appData.debugMode) {
11126 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11127 result, resultDetails ? resultDetails : "(null)", whosays);
11130 fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11132 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11134 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11135 /* If we are playing on ICS, the server decides when the
11136 game is over, but the engine can offer to draw, claim
11140 if (appData.zippyPlay && first.initDone) {
11141 if (result == GameIsDrawn) {
11142 /* In case draw still needs to be claimed */
11143 SendToICS(ics_prefix);
11144 SendToICS("draw\n");
11145 } else if (StrCaseStr(resultDetails, "resign")) {
11146 SendToICS(ics_prefix);
11147 SendToICS("resign\n");
11151 endingGame = 0; /* [HGM] crash */
11155 /* If we're loading the game from a file, stop */
11156 if (whosays == GE_FILE) {
11157 (void) StopLoadGameTimer();
11161 /* Cancel draw offers */
11162 first.offeredDraw = second.offeredDraw = 0;
11164 /* If this is an ICS game, only ICS can really say it's done;
11165 if not, anyone can. */
11166 isIcsGame = (gameMode == IcsPlayingWhite ||
11167 gameMode == IcsPlayingBlack ||
11168 gameMode == IcsObserving ||
11169 gameMode == IcsExamining);
11171 if (!isIcsGame || whosays == GE_ICS) {
11172 /* OK -- not an ICS game, or ICS said it was done */
11174 if (!isIcsGame && !appData.noChessProgram)
11175 SetUserThinkingEnables();
11177 /* [HGM] if a machine claims the game end we verify this claim */
11178 if(gameMode == TwoMachinesPlay && appData.testClaims) {
11179 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11181 ChessMove trueResult = (ChessMove) -1;
11183 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
11184 first.twoMachinesColor[0] :
11185 second.twoMachinesColor[0] ;
11187 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11188 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11189 /* [HGM] verify: engine mate claims accepted if they were flagged */
11190 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11192 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11193 /* [HGM] verify: engine mate claims accepted if they were flagged */
11194 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11196 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11197 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11200 // now verify win claims, but not in drop games, as we don't understand those yet
11201 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11202 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11203 (result == WhiteWins && claimer == 'w' ||
11204 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
11205 if (appData.debugMode) {
11206 fprintf(debugFP, "result=%d sp=%d move=%d\n",
11207 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11209 if(result != trueResult) {
11210 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11211 result = claimer == 'w' ? BlackWins : WhiteWins;
11212 resultDetails = buf;
11215 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11216 && (forwardMostMove <= backwardMostMove ||
11217 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11218 (claimer=='b')==(forwardMostMove&1))
11220 /* [HGM] verify: draws that were not flagged are false claims */
11221 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11222 result = claimer == 'w' ? BlackWins : WhiteWins;
11223 resultDetails = buf;
11225 /* (Claiming a loss is accepted no questions asked!) */
11226 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11227 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11228 result = GameUnfinished;
11229 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11231 /* [HGM] bare: don't allow bare King to win */
11232 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11233 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11234 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11235 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11236 && result != GameIsDrawn)
11237 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11238 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11239 int p = (signed char)boards[forwardMostMove][i][j] - color;
11240 if(p >= 0 && p <= (int)WhiteKing) k++;
11242 if (appData.debugMode) {
11243 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11244 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11247 result = GameIsDrawn;
11248 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11249 resultDetails = buf;
11255 if(serverMoves != NULL && !loadFlag) { char c = '=';
11256 if(result==WhiteWins) c = '+';
11257 if(result==BlackWins) c = '-';
11258 if(resultDetails != NULL)
11259 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11261 if (resultDetails != NULL) {
11262 gameInfo.result = result;
11263 gameInfo.resultDetails = StrSave(resultDetails);
11265 /* display last move only if game was not loaded from file */
11266 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11267 DisplayMove(currentMove - 1);
11269 if (forwardMostMove != 0) {
11270 if (gameMode != PlayFromGameFile && gameMode != EditGame
11271 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11273 if (*appData.saveGameFile != NULLCHAR) {
11274 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11275 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11277 SaveGameToFile(appData.saveGameFile, TRUE);
11278 } else if (appData.autoSaveGames) {
11279 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11281 if (*appData.savePositionFile != NULLCHAR) {
11282 SavePositionToFile(appData.savePositionFile);
11284 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11288 /* Tell program how game ended in case it is learning */
11289 /* [HGM] Moved this to after saving the PGN, just in case */
11290 /* engine died and we got here through time loss. In that */
11291 /* case we will get a fatal error writing the pipe, which */
11292 /* would otherwise lose us the PGN. */
11293 /* [HGM] crash: not needed anymore, but doesn't hurt; */
11294 /* output during GameEnds should never be fatal anymore */
11295 if (gameMode == MachinePlaysWhite ||
11296 gameMode == MachinePlaysBlack ||
11297 gameMode == TwoMachinesPlay ||
11298 gameMode == IcsPlayingWhite ||
11299 gameMode == IcsPlayingBlack ||
11300 gameMode == BeginningOfGame) {
11302 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11304 if (first.pr != NoProc) {
11305 SendToProgram(buf, &first);
11307 if (second.pr != NoProc &&
11308 gameMode == TwoMachinesPlay) {
11309 SendToProgram(buf, &second);
11314 if (appData.icsActive) {
11315 if (appData.quietPlay &&
11316 (gameMode == IcsPlayingWhite ||
11317 gameMode == IcsPlayingBlack)) {
11318 SendToICS(ics_prefix);
11319 SendToICS("set shout 1\n");
11321 nextGameMode = IcsIdle;
11322 ics_user_moved = FALSE;
11323 /* clean up premove. It's ugly when the game has ended and the
11324 * premove highlights are still on the board.
11327 gotPremove = FALSE;
11328 ClearPremoveHighlights();
11329 DrawPosition(FALSE, boards[currentMove]);
11331 if (whosays == GE_ICS) {
11334 if (gameMode == IcsPlayingWhite)
11336 else if(gameMode == IcsPlayingBlack)
11337 PlayIcsLossSound();
11340 if (gameMode == IcsPlayingBlack)
11342 else if(gameMode == IcsPlayingWhite)
11343 PlayIcsLossSound();
11346 PlayIcsDrawSound();
11349 PlayIcsUnfinishedSound();
11352 if(appData.quitNext) { ExitEvent(0); return; }
11353 } else if (gameMode == EditGame ||
11354 gameMode == PlayFromGameFile ||
11355 gameMode == AnalyzeMode ||
11356 gameMode == AnalyzeFile) {
11357 nextGameMode = gameMode;
11359 nextGameMode = EndOfGame;
11364 nextGameMode = gameMode;
11367 if (appData.noChessProgram) {
11368 gameMode = nextGameMode;
11370 endingGame = 0; /* [HGM] crash */
11375 /* Put first chess program into idle state */
11376 if (first.pr != NoProc &&
11377 (gameMode == MachinePlaysWhite ||
11378 gameMode == MachinePlaysBlack ||
11379 gameMode == TwoMachinesPlay ||
11380 gameMode == IcsPlayingWhite ||
11381 gameMode == IcsPlayingBlack ||
11382 gameMode == BeginningOfGame)) {
11383 SendToProgram("force\n", &first);
11384 if (first.usePing) {
11386 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11387 SendToProgram(buf, &first);
11390 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11391 /* Kill off first chess program */
11392 if (first.isr != NULL)
11393 RemoveInputSource(first.isr);
11396 if (first.pr != NoProc) {
11398 DoSleep( appData.delayBeforeQuit );
11399 SendToProgram("quit\n", &first);
11400 DestroyChildProcess(first.pr, 4 + first.useSigterm);
11401 first.reload = TRUE;
11405 if (second.reuse) {
11406 /* Put second chess program into idle state */
11407 if (second.pr != NoProc &&
11408 gameMode == TwoMachinesPlay) {
11409 SendToProgram("force\n", &second);
11410 if (second.usePing) {
11412 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11413 SendToProgram(buf, &second);
11416 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11417 /* Kill off second chess program */
11418 if (second.isr != NULL)
11419 RemoveInputSource(second.isr);
11422 if (second.pr != NoProc) {
11423 DoSleep( appData.delayBeforeQuit );
11424 SendToProgram("quit\n", &second);
11425 DestroyChildProcess(second.pr, 4 + second.useSigterm);
11426 second.reload = TRUE;
11428 second.pr = NoProc;
11431 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11432 char resChar = '=';
11436 if (first.twoMachinesColor[0] == 'w') {
11439 second.matchWins++;
11444 if (first.twoMachinesColor[0] == 'b') {
11447 second.matchWins++;
11450 case GameUnfinished:
11456 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11457 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11458 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11459 ReserveGame(nextGame, resChar); // sets nextGame
11460 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11461 else ranking = strdup("busy"); //suppress popup when aborted but not finished
11462 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11464 if (nextGame <= appData.matchGames && !abortMatch) {
11465 gameMode = nextGameMode;
11466 matchGame = nextGame; // this will be overruled in tourney mode!
11467 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11468 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11469 endingGame = 0; /* [HGM] crash */
11472 gameMode = nextGameMode;
11473 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11474 first.tidy, second.tidy,
11475 first.matchWins, second.matchWins,
11476 appData.matchGames - (first.matchWins + second.matchWins));
11477 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11478 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11479 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11480 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11481 first.twoMachinesColor = "black\n";
11482 second.twoMachinesColor = "white\n";
11484 first.twoMachinesColor = "white\n";
11485 second.twoMachinesColor = "black\n";
11489 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11490 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11492 gameMode = nextGameMode;
11494 endingGame = 0; /* [HGM] crash */
11495 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11496 if(matchMode == TRUE) { // match through command line: exit with or without popup
11498 ToNrEvent(forwardMostMove);
11499 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11501 } else DisplayFatalError(buf, 0, 0);
11502 } else { // match through menu; just stop, with or without popup
11503 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11506 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11507 } else DisplayNote(buf);
11509 if(ranking) free(ranking);
11513 /* Assumes program was just initialized (initString sent).
11514 Leaves program in force mode. */
11516 FeedMovesToProgram (ChessProgramState *cps, int upto)
11520 if (appData.debugMode)
11521 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11522 startedFromSetupPosition ? "position and " : "",
11523 backwardMostMove, upto, cps->which);
11524 if(currentlyInitializedVariant != gameInfo.variant) {
11526 // [HGM] variantswitch: make engine aware of new variant
11527 if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11528 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11529 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11530 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11531 SendToProgram(buf, cps);
11532 currentlyInitializedVariant = gameInfo.variant;
11534 SendToProgram("force\n", cps);
11535 if (startedFromSetupPosition) {
11536 SendBoard(cps, backwardMostMove);
11537 if (appData.debugMode) {
11538 fprintf(debugFP, "feedMoves\n");
11541 for (i = backwardMostMove; i < upto; i++) {
11542 SendMoveToProgram(i, cps);
11548 ResurrectChessProgram ()
11550 /* The chess program may have exited.
11551 If so, restart it and feed it all the moves made so far. */
11552 static int doInit = 0;
11554 if (appData.noChessProgram) return 1;
11556 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11557 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11558 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11559 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11561 if (first.pr != NoProc) return 1;
11562 StartChessProgram(&first);
11564 InitChessProgram(&first, FALSE);
11565 FeedMovesToProgram(&first, currentMove);
11567 if (!first.sendTime) {
11568 /* can't tell gnuchess what its clock should read,
11569 so we bow to its notion. */
11571 timeRemaining[0][currentMove] = whiteTimeRemaining;
11572 timeRemaining[1][currentMove] = blackTimeRemaining;
11575 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11576 appData.icsEngineAnalyze) && first.analysisSupport) {
11577 SendToProgram("analyze\n", &first);
11578 first.analyzing = TRUE;
11584 * Button procedures
11587 Reset (int redraw, int init)
11591 if (appData.debugMode) {
11592 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11593 redraw, init, gameMode);
11595 CleanupTail(); // [HGM] vari: delete any stored variations
11596 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11597 pausing = pauseExamInvalid = FALSE;
11598 startedFromSetupPosition = blackPlaysFirst = FALSE;
11600 whiteFlag = blackFlag = FALSE;
11601 userOfferedDraw = FALSE;
11602 hintRequested = bookRequested = FALSE;
11603 first.maybeThinking = FALSE;
11604 second.maybeThinking = FALSE;
11605 first.bookSuspend = FALSE; // [HGM] book
11606 second.bookSuspend = FALSE;
11607 thinkOutput[0] = NULLCHAR;
11608 lastHint[0] = NULLCHAR;
11609 ClearGameInfo(&gameInfo);
11610 gameInfo.variant = StringToVariant(appData.variant);
11611 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11612 ics_user_moved = ics_clock_paused = FALSE;
11613 ics_getting_history = H_FALSE;
11615 white_holding[0] = black_holding[0] = NULLCHAR;
11616 ClearProgramStats();
11617 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11621 flipView = appData.flipView;
11622 ClearPremoveHighlights();
11623 gotPremove = FALSE;
11624 alarmSounded = FALSE;
11625 killX = killY = -1; // [HGM] lion
11627 GameEnds(EndOfFile, NULL, GE_PLAYER);
11628 if(appData.serverMovesName != NULL) {
11629 /* [HGM] prepare to make moves file for broadcasting */
11630 clock_t t = clock();
11631 if(serverMoves != NULL) fclose(serverMoves);
11632 serverMoves = fopen(appData.serverMovesName, "r");
11633 if(serverMoves != NULL) {
11634 fclose(serverMoves);
11635 /* delay 15 sec before overwriting, so all clients can see end */
11636 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11638 serverMoves = fopen(appData.serverMovesName, "w");
11642 gameMode = BeginningOfGame;
11644 if(appData.icsActive) gameInfo.variant = VariantNormal;
11645 currentMove = forwardMostMove = backwardMostMove = 0;
11646 MarkTargetSquares(1);
11647 InitPosition(redraw);
11648 for (i = 0; i < MAX_MOVES; i++) {
11649 if (commentList[i] != NULL) {
11650 free(commentList[i]);
11651 commentList[i] = NULL;
11655 timeRemaining[0][0] = whiteTimeRemaining;
11656 timeRemaining[1][0] = blackTimeRemaining;
11658 if (first.pr == NoProc) {
11659 StartChessProgram(&first);
11662 InitChessProgram(&first, startedFromSetupPosition);
11665 DisplayMessage("", "");
11666 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11667 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11668 ClearMap(); // [HGM] exclude: invalidate map
11672 AutoPlayGameLoop ()
11675 if (!AutoPlayOneMove())
11677 if (matchMode || appData.timeDelay == 0)
11679 if (appData.timeDelay < 0)
11681 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11689 ReloadGame(1); // next game
11695 int fromX, fromY, toX, toY;
11697 if (appData.debugMode) {
11698 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11701 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11704 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11705 pvInfoList[currentMove].depth = programStats.depth;
11706 pvInfoList[currentMove].score = programStats.score;
11707 pvInfoList[currentMove].time = 0;
11708 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11709 else { // append analysis of final position as comment
11711 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11712 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11714 programStats.depth = 0;
11717 if (currentMove >= forwardMostMove) {
11718 if(gameMode == AnalyzeFile) {
11719 if(appData.loadGameIndex == -1) {
11720 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11721 ScheduleDelayedEvent(AnalyzeNextGame, 10);
11723 ExitAnalyzeMode(); SendToProgram("force\n", &first);
11726 // gameMode = EndOfGame;
11727 // ModeHighlight();
11729 /* [AS] Clear current move marker at the end of a game */
11730 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11735 toX = moveList[currentMove][2] - AAA;
11736 toY = moveList[currentMove][3] - ONE;
11738 if (moveList[currentMove][1] == '@') {
11739 if (appData.highlightLastMove) {
11740 SetHighlights(-1, -1, toX, toY);
11743 fromX = moveList[currentMove][0] - AAA;
11744 fromY = moveList[currentMove][1] - ONE;
11746 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11748 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11750 if (appData.highlightLastMove) {
11751 SetHighlights(fromX, fromY, toX, toY);
11754 DisplayMove(currentMove);
11755 SendMoveToProgram(currentMove++, &first);
11756 DisplayBothClocks();
11757 DrawPosition(FALSE, boards[currentMove]);
11758 // [HGM] PV info: always display, routine tests if empty
11759 DisplayComment(currentMove - 1, commentList[currentMove]);
11765 LoadGameOneMove (ChessMove readAhead)
11767 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11768 char promoChar = NULLCHAR;
11769 ChessMove moveType;
11770 char move[MSG_SIZ];
11773 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11774 gameMode != AnalyzeMode && gameMode != Training) {
11779 yyboardindex = forwardMostMove;
11780 if (readAhead != EndOfFile) {
11781 moveType = readAhead;
11783 if (gameFileFP == NULL)
11785 moveType = (ChessMove) Myylex();
11789 switch (moveType) {
11791 if (appData.debugMode)
11792 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11795 /* append the comment but don't display it */
11796 AppendComment(currentMove, p, FALSE);
11799 case WhiteCapturesEnPassant:
11800 case BlackCapturesEnPassant:
11801 case WhitePromotion:
11802 case BlackPromotion:
11803 case WhiteNonPromotion:
11804 case BlackNonPromotion:
11807 case WhiteKingSideCastle:
11808 case WhiteQueenSideCastle:
11809 case BlackKingSideCastle:
11810 case BlackQueenSideCastle:
11811 case WhiteKingSideCastleWild:
11812 case WhiteQueenSideCastleWild:
11813 case BlackKingSideCastleWild:
11814 case BlackQueenSideCastleWild:
11816 case WhiteHSideCastleFR:
11817 case WhiteASideCastleFR:
11818 case BlackHSideCastleFR:
11819 case BlackASideCastleFR:
11821 if (appData.debugMode)
11822 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11823 fromX = currentMoveString[0] - AAA;
11824 fromY = currentMoveString[1] - ONE;
11825 toX = currentMoveString[2] - AAA;
11826 toY = currentMoveString[3] - ONE;
11827 promoChar = currentMoveString[4];
11828 if(promoChar == ';') promoChar = NULLCHAR;
11833 if (appData.debugMode)
11834 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11835 fromX = moveType == WhiteDrop ?
11836 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11837 (int) CharToPiece(ToLower(currentMoveString[0]));
11839 toX = currentMoveString[2] - AAA;
11840 toY = currentMoveString[3] - ONE;
11846 case GameUnfinished:
11847 if (appData.debugMode)
11848 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11849 p = strchr(yy_text, '{');
11850 if (p == NULL) p = strchr(yy_text, '(');
11853 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11855 q = strchr(p, *p == '{' ? '}' : ')');
11856 if (q != NULL) *q = NULLCHAR;
11859 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11860 GameEnds(moveType, p, GE_FILE);
11862 if (cmailMsgLoaded) {
11864 flipView = WhiteOnMove(currentMove);
11865 if (moveType == GameUnfinished) flipView = !flipView;
11866 if (appData.debugMode)
11867 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11872 if (appData.debugMode)
11873 fprintf(debugFP, "Parser hit end of file\n");
11874 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11880 if (WhiteOnMove(currentMove)) {
11881 GameEnds(BlackWins, "Black mates", GE_FILE);
11883 GameEnds(WhiteWins, "White mates", GE_FILE);
11887 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11893 case MoveNumberOne:
11894 if (lastLoadGameStart == GNUChessGame) {
11895 /* GNUChessGames have numbers, but they aren't move numbers */
11896 if (appData.debugMode)
11897 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11898 yy_text, (int) moveType);
11899 return LoadGameOneMove(EndOfFile); /* tail recursion */
11901 /* else fall thru */
11906 /* Reached start of next game in file */
11907 if (appData.debugMode)
11908 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11909 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11915 if (WhiteOnMove(currentMove)) {
11916 GameEnds(BlackWins, "Black mates", GE_FILE);
11918 GameEnds(WhiteWins, "White mates", GE_FILE);
11922 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11928 case PositionDiagram: /* should not happen; ignore */
11929 case ElapsedTime: /* ignore */
11930 case NAG: /* ignore */
11931 if (appData.debugMode)
11932 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11933 yy_text, (int) moveType);
11934 return LoadGameOneMove(EndOfFile); /* tail recursion */
11937 if (appData.testLegality) {
11938 if (appData.debugMode)
11939 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11940 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11941 (forwardMostMove / 2) + 1,
11942 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11943 DisplayError(move, 0);
11946 if (appData.debugMode)
11947 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11948 yy_text, currentMoveString);
11949 fromX = currentMoveString[0] - AAA;
11950 fromY = currentMoveString[1] - ONE;
11951 toX = currentMoveString[2] - AAA;
11952 toY = currentMoveString[3] - ONE;
11953 promoChar = currentMoveString[4];
11957 case AmbiguousMove:
11958 if (appData.debugMode)
11959 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11960 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11961 (forwardMostMove / 2) + 1,
11962 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11963 DisplayError(move, 0);
11968 case ImpossibleMove:
11969 if (appData.debugMode)
11970 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11971 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11972 (forwardMostMove / 2) + 1,
11973 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11974 DisplayError(move, 0);
11980 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11981 DrawPosition(FALSE, boards[currentMove]);
11982 DisplayBothClocks();
11983 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11984 DisplayComment(currentMove - 1, commentList[currentMove]);
11986 (void) StopLoadGameTimer();
11988 cmailOldMove = forwardMostMove;
11991 /* currentMoveString is set as a side-effect of yylex */
11993 thinkOutput[0] = NULLCHAR;
11994 MakeMove(fromX, fromY, toX, toY, promoChar);
11995 killX = killY = -1; // [HGM] lion: used up
11996 currentMove = forwardMostMove;
12001 /* Load the nth game from the given file */
12003 LoadGameFromFile (char *filename, int n, char *title, int useList)
12008 if (strcmp(filename, "-") == 0) {
12012 f = fopen(filename, "rb");
12014 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12015 DisplayError(buf, errno);
12019 if (fseek(f, 0, 0) == -1) {
12020 /* f is not seekable; probably a pipe */
12023 if (useList && n == 0) {
12024 int error = GameListBuild(f);
12026 DisplayError(_("Cannot build game list"), error);
12027 } else if (!ListEmpty(&gameList) &&
12028 ((ListGame *) gameList.tailPred)->number > 1) {
12029 GameListPopUp(f, title);
12036 return LoadGame(f, n, title, FALSE);
12041 MakeRegisteredMove ()
12043 int fromX, fromY, toX, toY;
12045 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12046 switch (cmailMoveType[lastLoadGameNumber - 1]) {
12049 if (appData.debugMode)
12050 fprintf(debugFP, "Restoring %s for game %d\n",
12051 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12053 thinkOutput[0] = NULLCHAR;
12054 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12055 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12056 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12057 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12058 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12059 promoChar = cmailMove[lastLoadGameNumber - 1][4];
12060 MakeMove(fromX, fromY, toX, toY, promoChar);
12061 ShowMove(fromX, fromY, toX, toY);
12063 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12070 if (WhiteOnMove(currentMove)) {
12071 GameEnds(BlackWins, "Black mates", GE_PLAYER);
12073 GameEnds(WhiteWins, "White mates", GE_PLAYER);
12078 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12085 if (WhiteOnMove(currentMove)) {
12086 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12088 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12093 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12104 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12106 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12110 if (gameNumber > nCmailGames) {
12111 DisplayError(_("No more games in this message"), 0);
12114 if (f == lastLoadGameFP) {
12115 int offset = gameNumber - lastLoadGameNumber;
12117 cmailMsg[0] = NULLCHAR;
12118 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12119 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12120 nCmailMovesRegistered--;
12122 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12123 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12124 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12127 if (! RegisterMove()) return FALSE;
12131 retVal = LoadGame(f, gameNumber, title, useList);
12133 /* Make move registered during previous look at this game, if any */
12134 MakeRegisteredMove();
12136 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12137 commentList[currentMove]
12138 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12139 DisplayComment(currentMove - 1, commentList[currentMove]);
12145 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12147 ReloadGame (int offset)
12149 int gameNumber = lastLoadGameNumber + offset;
12150 if (lastLoadGameFP == NULL) {
12151 DisplayError(_("No game has been loaded yet"), 0);
12154 if (gameNumber <= 0) {
12155 DisplayError(_("Can't back up any further"), 0);
12158 if (cmailMsgLoaded) {
12159 return CmailLoadGame(lastLoadGameFP, gameNumber,
12160 lastLoadGameTitle, lastLoadGameUseList);
12162 return LoadGame(lastLoadGameFP, gameNumber,
12163 lastLoadGameTitle, lastLoadGameUseList);
12167 int keys[EmptySquare+1];
12170 PositionMatches (Board b1, Board b2)
12173 switch(appData.searchMode) {
12174 case 1: return CompareWithRights(b1, b2);
12176 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12177 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12181 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12182 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12183 sum += keys[b1[r][f]] - keys[b2[r][f]];
12187 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12188 sum += keys[b1[r][f]] - keys[b2[r][f]];
12200 int pieceList[256], quickBoard[256];
12201 ChessSquare pieceType[256] = { EmptySquare };
12202 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12203 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12204 int soughtTotal, turn;
12205 Boolean epOK, flipSearch;
12208 unsigned char piece, to;
12211 #define DSIZE (250000)
12213 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12214 Move *moveDatabase = initialSpace;
12215 unsigned int movePtr, dataSize = DSIZE;
12218 MakePieceList (Board board, int *counts)
12220 int r, f, n=Q_PROMO, total=0;
12221 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12222 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12223 int sq = f + (r<<4);
12224 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12225 quickBoard[sq] = ++n;
12227 pieceType[n] = board[r][f];
12228 counts[board[r][f]]++;
12229 if(board[r][f] == WhiteKing) pieceList[1] = n; else
12230 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12234 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12239 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12241 int sq = fromX + (fromY<<4);
12242 int piece = quickBoard[sq], rook;
12243 quickBoard[sq] = 0;
12244 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12245 if(piece == pieceList[1] && fromY == toY) {
12246 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12247 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12248 moveDatabase[movePtr++].piece = Q_WCASTL;
12249 quickBoard[sq] = piece;
12250 piece = quickBoard[from]; quickBoard[from] = 0;
12251 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12252 } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12253 quickBoard[sq] = 0; // remove Rook
12254 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12255 moveDatabase[movePtr++].piece = Q_WCASTL;
12256 quickBoard[sq] = pieceList[1]; // put King
12258 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12261 if(piece == pieceList[2] && fromY == toY) {
12262 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12263 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12264 moveDatabase[movePtr++].piece = Q_BCASTL;
12265 quickBoard[sq] = piece;
12266 piece = quickBoard[from]; quickBoard[from] = 0;
12267 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12268 } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12269 quickBoard[sq] = 0; // remove Rook
12270 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12271 moveDatabase[movePtr++].piece = Q_BCASTL;
12272 quickBoard[sq] = pieceList[2]; // put King
12274 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12277 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12278 quickBoard[(fromY<<4)+toX] = 0;
12279 moveDatabase[movePtr].piece = Q_EP;
12280 moveDatabase[movePtr++].to = (fromY<<4)+toX;
12281 moveDatabase[movePtr].to = sq;
12283 if(promoPiece != pieceType[piece]) {
12284 moveDatabase[movePtr++].piece = Q_PROMO;
12285 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12287 moveDatabase[movePtr].piece = piece;
12288 quickBoard[sq] = piece;
12293 PackGame (Board board)
12295 Move *newSpace = NULL;
12296 moveDatabase[movePtr].piece = 0; // terminate previous game
12297 if(movePtr > dataSize) {
12298 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12299 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12300 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12303 Move *p = moveDatabase, *q = newSpace;
12304 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
12305 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12306 moveDatabase = newSpace;
12307 } else { // calloc failed, we must be out of memory. Too bad...
12308 dataSize = 0; // prevent calloc events for all subsequent games
12309 return 0; // and signal this one isn't cached
12313 MakePieceList(board, counts);
12318 QuickCompare (Board board, int *minCounts, int *maxCounts)
12319 { // compare according to search mode
12321 switch(appData.searchMode)
12323 case 1: // exact position match
12324 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12325 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12326 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12329 case 2: // can have extra material on empty squares
12330 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12331 if(board[r][f] == EmptySquare) continue;
12332 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12335 case 3: // material with exact Pawn structure
12336 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12337 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12338 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12339 } // fall through to material comparison
12340 case 4: // exact material
12341 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12343 case 6: // material range with given imbalance
12344 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12345 // fall through to range comparison
12346 case 5: // material range
12347 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12353 QuickScan (Board board, Move *move)
12354 { // reconstruct game,and compare all positions in it
12355 int cnt=0, stretch=0, total = MakePieceList(board, counts);
12357 int piece = move->piece;
12358 int to = move->to, from = pieceList[piece];
12359 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12360 if(!piece) return -1;
12361 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12362 piece = (++move)->piece;
12363 from = pieceList[piece];
12364 counts[pieceType[piece]]--;
12365 pieceType[piece] = (ChessSquare) move->to;
12366 counts[move->to]++;
12367 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12368 counts[pieceType[quickBoard[to]]]--;
12369 quickBoard[to] = 0; total--;
12372 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12373 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12374 from = pieceList[piece]; // so this must be King
12375 quickBoard[from] = 0;
12376 pieceList[piece] = to;
12377 from = pieceList[(++move)->piece]; // for FRC this has to be done here
12378 quickBoard[from] = 0; // rook
12379 quickBoard[to] = piece;
12380 to = move->to; piece = move->piece;
12384 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12385 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12386 quickBoard[from] = 0;
12388 quickBoard[to] = piece;
12389 pieceList[piece] = to;
12391 if(QuickCompare(soughtBoard, minSought, maxSought) ||
12392 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12393 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12394 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12396 static int lastCounts[EmptySquare+1];
12398 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12399 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12400 } else stretch = 0;
12401 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12410 flipSearch = FALSE;
12411 CopyBoard(soughtBoard, boards[currentMove]);
12412 soughtTotal = MakePieceList(soughtBoard, maxSought);
12413 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12414 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12415 CopyBoard(reverseBoard, boards[currentMove]);
12416 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12417 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12418 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12419 reverseBoard[r][f] = piece;
12421 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12422 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12423 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12424 || (boards[currentMove][CASTLING][2] == NoRights ||
12425 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12426 && (boards[currentMove][CASTLING][5] == NoRights ||
12427 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12430 CopyBoard(flipBoard, soughtBoard);
12431 CopyBoard(rotateBoard, reverseBoard);
12432 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12433 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
12434 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12437 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12438 if(appData.searchMode >= 5) {
12439 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12440 MakePieceList(soughtBoard, minSought);
12441 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12443 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12444 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12447 GameInfo dummyInfo;
12448 static int creatingBook;
12451 GameContainsPosition (FILE *f, ListGame *lg)
12453 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12454 int fromX, fromY, toX, toY;
12456 static int initDone=FALSE;
12458 // weed out games based on numerical tag comparison
12459 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12460 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12461 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12462 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12464 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12467 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12468 else CopyBoard(boards[scratch], initialPosition); // default start position
12471 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12472 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12475 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12476 fseek(f, lg->offset, 0);
12479 yyboardindex = scratch;
12480 quickFlag = plyNr+1;
12485 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12491 if(plyNr) return -1; // after we have seen moves, this is for new game
12494 case AmbiguousMove: // we cannot reconstruct the game beyond these two
12495 case ImpossibleMove:
12496 case WhiteWins: // game ends here with these four
12499 case GameUnfinished:
12503 if(appData.testLegality) return -1;
12504 case WhiteCapturesEnPassant:
12505 case BlackCapturesEnPassant:
12506 case WhitePromotion:
12507 case BlackPromotion:
12508 case WhiteNonPromotion:
12509 case BlackNonPromotion:
12512 case WhiteKingSideCastle:
12513 case WhiteQueenSideCastle:
12514 case BlackKingSideCastle:
12515 case BlackQueenSideCastle:
12516 case WhiteKingSideCastleWild:
12517 case WhiteQueenSideCastleWild:
12518 case BlackKingSideCastleWild:
12519 case BlackQueenSideCastleWild:
12520 case WhiteHSideCastleFR:
12521 case WhiteASideCastleFR:
12522 case BlackHSideCastleFR:
12523 case BlackASideCastleFR:
12524 fromX = currentMoveString[0] - AAA;
12525 fromY = currentMoveString[1] - ONE;
12526 toX = currentMoveString[2] - AAA;
12527 toY = currentMoveString[3] - ONE;
12528 promoChar = currentMoveString[4];
12532 fromX = next == WhiteDrop ?
12533 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12534 (int) CharToPiece(ToLower(currentMoveString[0]));
12536 toX = currentMoveString[2] - AAA;
12537 toY = currentMoveString[3] - ONE;
12541 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12543 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12544 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12545 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12546 if(appData.findMirror) {
12547 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12548 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12553 /* Load the nth game from open file f */
12555 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12559 int gn = gameNumber;
12560 ListGame *lg = NULL;
12561 int numPGNTags = 0;
12563 GameMode oldGameMode;
12564 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12566 if (appData.debugMode)
12567 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12569 if (gameMode == Training )
12570 SetTrainingModeOff();
12572 oldGameMode = gameMode;
12573 if (gameMode != BeginningOfGame) {
12574 Reset(FALSE, TRUE);
12576 killX = killY = -1; // [HGM] lion: in case we did not Reset
12579 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12580 fclose(lastLoadGameFP);
12584 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12587 fseek(f, lg->offset, 0);
12588 GameListHighlight(gameNumber);
12589 pos = lg->position;
12593 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12594 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12596 DisplayError(_("Game number out of range"), 0);
12601 if (fseek(f, 0, 0) == -1) {
12602 if (f == lastLoadGameFP ?
12603 gameNumber == lastLoadGameNumber + 1 :
12607 DisplayError(_("Can't seek on game file"), 0);
12612 lastLoadGameFP = f;
12613 lastLoadGameNumber = gameNumber;
12614 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12615 lastLoadGameUseList = useList;
12619 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12620 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12621 lg->gameInfo.black);
12623 } else if (*title != NULLCHAR) {
12624 if (gameNumber > 1) {
12625 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12628 DisplayTitle(title);
12632 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12633 gameMode = PlayFromGameFile;
12637 currentMove = forwardMostMove = backwardMostMove = 0;
12638 CopyBoard(boards[0], initialPosition);
12642 * Skip the first gn-1 games in the file.
12643 * Also skip over anything that precedes an identifiable
12644 * start of game marker, to avoid being confused by
12645 * garbage at the start of the file. Currently
12646 * recognized start of game markers are the move number "1",
12647 * the pattern "gnuchess .* game", the pattern
12648 * "^[#;%] [^ ]* game file", and a PGN tag block.
12649 * A game that starts with one of the latter two patterns
12650 * will also have a move number 1, possibly
12651 * following a position diagram.
12652 * 5-4-02: Let's try being more lenient and allowing a game to
12653 * start with an unnumbered move. Does that break anything?
12655 cm = lastLoadGameStart = EndOfFile;
12657 yyboardindex = forwardMostMove;
12658 cm = (ChessMove) Myylex();
12661 if (cmailMsgLoaded) {
12662 nCmailGames = CMAIL_MAX_GAMES - gn;
12665 DisplayError(_("Game not found in file"), 0);
12672 lastLoadGameStart = cm;
12675 case MoveNumberOne:
12676 switch (lastLoadGameStart) {
12681 case MoveNumberOne:
12683 gn--; /* count this game */
12684 lastLoadGameStart = cm;
12693 switch (lastLoadGameStart) {
12696 case MoveNumberOne:
12698 gn--; /* count this game */
12699 lastLoadGameStart = cm;
12702 lastLoadGameStart = cm; /* game counted already */
12710 yyboardindex = forwardMostMove;
12711 cm = (ChessMove) Myylex();
12712 } while (cm == PGNTag || cm == Comment);
12719 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12720 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
12721 != CMAIL_OLD_RESULT) {
12723 cmailResult[ CMAIL_MAX_GAMES
12724 - gn - 1] = CMAIL_OLD_RESULT;
12731 /* Only a NormalMove can be at the start of a game
12732 * without a position diagram. */
12733 if (lastLoadGameStart == EndOfFile ) {
12735 lastLoadGameStart = MoveNumberOne;
12744 if (appData.debugMode)
12745 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12747 if (cm == XBoardGame) {
12748 /* Skip any header junk before position diagram and/or move 1 */
12750 yyboardindex = forwardMostMove;
12751 cm = (ChessMove) Myylex();
12753 if (cm == EndOfFile ||
12754 cm == GNUChessGame || cm == XBoardGame) {
12755 /* Empty game; pretend end-of-file and handle later */
12760 if (cm == MoveNumberOne || cm == PositionDiagram ||
12761 cm == PGNTag || cm == Comment)
12764 } else if (cm == GNUChessGame) {
12765 if (gameInfo.event != NULL) {
12766 free(gameInfo.event);
12768 gameInfo.event = StrSave(yy_text);
12771 startedFromSetupPosition = FALSE;
12772 while (cm == PGNTag) {
12773 if (appData.debugMode)
12774 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12775 err = ParsePGNTag(yy_text, &gameInfo);
12776 if (!err) numPGNTags++;
12778 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12779 if(gameInfo.variant != oldVariant) {
12780 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12781 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12782 InitPosition(TRUE);
12783 oldVariant = gameInfo.variant;
12784 if (appData.debugMode)
12785 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12789 if (gameInfo.fen != NULL) {
12790 Board initial_position;
12791 startedFromSetupPosition = TRUE;
12792 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12794 DisplayError(_("Bad FEN position in file"), 0);
12797 CopyBoard(boards[0], initial_position);
12798 if (blackPlaysFirst) {
12799 currentMove = forwardMostMove = backwardMostMove = 1;
12800 CopyBoard(boards[1], initial_position);
12801 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12802 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12803 timeRemaining[0][1] = whiteTimeRemaining;
12804 timeRemaining[1][1] = blackTimeRemaining;
12805 if (commentList[0] != NULL) {
12806 commentList[1] = commentList[0];
12807 commentList[0] = NULL;
12810 currentMove = forwardMostMove = backwardMostMove = 0;
12812 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12814 initialRulePlies = FENrulePlies;
12815 for( i=0; i< nrCastlingRights; i++ )
12816 initialRights[i] = initial_position[CASTLING][i];
12818 yyboardindex = forwardMostMove;
12819 free(gameInfo.fen);
12820 gameInfo.fen = NULL;
12823 yyboardindex = forwardMostMove;
12824 cm = (ChessMove) Myylex();
12826 /* Handle comments interspersed among the tags */
12827 while (cm == Comment) {
12829 if (appData.debugMode)
12830 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12832 AppendComment(currentMove, p, FALSE);
12833 yyboardindex = forwardMostMove;
12834 cm = (ChessMove) Myylex();
12838 /* don't rely on existence of Event tag since if game was
12839 * pasted from clipboard the Event tag may not exist
12841 if (numPGNTags > 0){
12843 if (gameInfo.variant == VariantNormal) {
12844 VariantClass v = StringToVariant(gameInfo.event);
12845 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12846 if(v < VariantShogi) gameInfo.variant = v;
12849 if( appData.autoDisplayTags ) {
12850 tags = PGNTags(&gameInfo);
12851 TagsPopUp(tags, CmailMsg());
12856 /* Make something up, but don't display it now */
12861 if (cm == PositionDiagram) {
12864 Board initial_position;
12866 if (appData.debugMode)
12867 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12869 if (!startedFromSetupPosition) {
12871 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12872 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12883 initial_position[i][j++] = CharToPiece(*p);
12886 while (*p == ' ' || *p == '\t' ||
12887 *p == '\n' || *p == '\r') p++;
12889 if (strncmp(p, "black", strlen("black"))==0)
12890 blackPlaysFirst = TRUE;
12892 blackPlaysFirst = FALSE;
12893 startedFromSetupPosition = TRUE;
12895 CopyBoard(boards[0], initial_position);
12896 if (blackPlaysFirst) {
12897 currentMove = forwardMostMove = backwardMostMove = 1;
12898 CopyBoard(boards[1], initial_position);
12899 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12900 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12901 timeRemaining[0][1] = whiteTimeRemaining;
12902 timeRemaining[1][1] = blackTimeRemaining;
12903 if (commentList[0] != NULL) {
12904 commentList[1] = commentList[0];
12905 commentList[0] = NULL;
12908 currentMove = forwardMostMove = backwardMostMove = 0;
12911 yyboardindex = forwardMostMove;
12912 cm = (ChessMove) Myylex();
12915 if(!creatingBook) {
12916 if (first.pr == NoProc) {
12917 StartChessProgram(&first);
12919 InitChessProgram(&first, FALSE);
12920 SendToProgram("force\n", &first);
12921 if (startedFromSetupPosition) {
12922 SendBoard(&first, forwardMostMove);
12923 if (appData.debugMode) {
12924 fprintf(debugFP, "Load Game\n");
12926 DisplayBothClocks();
12930 /* [HGM] server: flag to write setup moves in broadcast file as one */
12931 loadFlag = appData.suppressLoadMoves;
12933 while (cm == Comment) {
12935 if (appData.debugMode)
12936 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12938 AppendComment(currentMove, p, FALSE);
12939 yyboardindex = forwardMostMove;
12940 cm = (ChessMove) Myylex();
12943 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12944 cm == WhiteWins || cm == BlackWins ||
12945 cm == GameIsDrawn || cm == GameUnfinished) {
12946 DisplayMessage("", _("No moves in game"));
12947 if (cmailMsgLoaded) {
12948 if (appData.debugMode)
12949 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12953 DrawPosition(FALSE, boards[currentMove]);
12954 DisplayBothClocks();
12955 gameMode = EditGame;
12962 // [HGM] PV info: routine tests if comment empty
12963 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12964 DisplayComment(currentMove - 1, commentList[currentMove]);
12966 if (!matchMode && appData.timeDelay != 0)
12967 DrawPosition(FALSE, boards[currentMove]);
12969 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12970 programStats.ok_to_send = 1;
12973 /* if the first token after the PGN tags is a move
12974 * and not move number 1, retrieve it from the parser
12976 if (cm != MoveNumberOne)
12977 LoadGameOneMove(cm);
12979 /* load the remaining moves from the file */
12980 while (LoadGameOneMove(EndOfFile)) {
12981 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12982 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12985 /* rewind to the start of the game */
12986 currentMove = backwardMostMove;
12988 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12990 if (oldGameMode == AnalyzeFile) {
12991 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12992 AnalyzeFileEvent();
12994 if (oldGameMode == AnalyzeMode) {
12995 AnalyzeFileEvent();
12998 if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12999 long int w, b; // [HGM] adjourn: restore saved clock times
13000 char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13001 if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13002 timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13003 timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13007 if(creatingBook) return TRUE;
13008 if (!matchMode && pos > 0) {
13009 ToNrEvent(pos); // [HGM] no autoplay if selected on position
13011 if (matchMode || appData.timeDelay == 0) {
13013 } else if (appData.timeDelay > 0) {
13014 AutoPlayGameLoop();
13017 if (appData.debugMode)
13018 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13020 loadFlag = 0; /* [HGM] true game starts */
13024 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13026 ReloadPosition (int offset)
13028 int positionNumber = lastLoadPositionNumber + offset;
13029 if (lastLoadPositionFP == NULL) {
13030 DisplayError(_("No position has been loaded yet"), 0);
13033 if (positionNumber <= 0) {
13034 DisplayError(_("Can't back up any further"), 0);
13037 return LoadPosition(lastLoadPositionFP, positionNumber,
13038 lastLoadPositionTitle);
13041 /* Load the nth position from the given file */
13043 LoadPositionFromFile (char *filename, int n, char *title)
13048 if (strcmp(filename, "-") == 0) {
13049 return LoadPosition(stdin, n, "stdin");
13051 f = fopen(filename, "rb");
13053 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13054 DisplayError(buf, errno);
13057 return LoadPosition(f, n, title);
13062 /* Load the nth position from the given open file, and close it */
13064 LoadPosition (FILE *f, int positionNumber, char *title)
13066 char *p, line[MSG_SIZ];
13067 Board initial_position;
13068 int i, j, fenMode, pn;
13070 if (gameMode == Training )
13071 SetTrainingModeOff();
13073 if (gameMode != BeginningOfGame) {
13074 Reset(FALSE, TRUE);
13076 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13077 fclose(lastLoadPositionFP);
13079 if (positionNumber == 0) positionNumber = 1;
13080 lastLoadPositionFP = f;
13081 lastLoadPositionNumber = positionNumber;
13082 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13083 if (first.pr == NoProc && !appData.noChessProgram) {
13084 StartChessProgram(&first);
13085 InitChessProgram(&first, FALSE);
13087 pn = positionNumber;
13088 if (positionNumber < 0) {
13089 /* Negative position number means to seek to that byte offset */
13090 if (fseek(f, -positionNumber, 0) == -1) {
13091 DisplayError(_("Can't seek on position file"), 0);
13096 if (fseek(f, 0, 0) == -1) {
13097 if (f == lastLoadPositionFP ?
13098 positionNumber == lastLoadPositionNumber + 1 :
13099 positionNumber == 1) {
13102 DisplayError(_("Can't seek on position file"), 0);
13107 /* See if this file is FEN or old-style xboard */
13108 if (fgets(line, MSG_SIZ, f) == NULL) {
13109 DisplayError(_("Position not found in file"), 0);
13112 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13113 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13116 if (fenMode || line[0] == '#') pn--;
13118 /* skip positions before number pn */
13119 if (fgets(line, MSG_SIZ, f) == NULL) {
13121 DisplayError(_("Position not found in file"), 0);
13124 if (fenMode || line[0] == '#') pn--;
13129 if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13130 DisplayError(_("Bad FEN position in file"), 0);
13134 (void) fgets(line, MSG_SIZ, f);
13135 (void) fgets(line, MSG_SIZ, f);
13137 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13138 (void) fgets(line, MSG_SIZ, f);
13139 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13142 initial_position[i][j++] = CharToPiece(*p);
13146 blackPlaysFirst = FALSE;
13148 (void) fgets(line, MSG_SIZ, f);
13149 if (strncmp(line, "black", strlen("black"))==0)
13150 blackPlaysFirst = TRUE;
13153 startedFromSetupPosition = TRUE;
13155 CopyBoard(boards[0], initial_position);
13156 if (blackPlaysFirst) {
13157 currentMove = forwardMostMove = backwardMostMove = 1;
13158 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13159 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13160 CopyBoard(boards[1], initial_position);
13161 DisplayMessage("", _("Black to play"));
13163 currentMove = forwardMostMove = backwardMostMove = 0;
13164 DisplayMessage("", _("White to play"));
13166 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13167 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13168 SendToProgram("force\n", &first);
13169 SendBoard(&first, forwardMostMove);
13171 if (appData.debugMode) {
13173 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13174 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13175 fprintf(debugFP, "Load Position\n");
13178 if (positionNumber > 1) {
13179 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13180 DisplayTitle(line);
13182 DisplayTitle(title);
13184 gameMode = EditGame;
13187 timeRemaining[0][1] = whiteTimeRemaining;
13188 timeRemaining[1][1] = blackTimeRemaining;
13189 DrawPosition(FALSE, boards[currentMove]);
13196 CopyPlayerNameIntoFileName (char **dest, char *src)
13198 while (*src != NULLCHAR && *src != ',') {
13203 *(*dest)++ = *src++;
13209 DefaultFileName (char *ext)
13211 static char def[MSG_SIZ];
13214 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13216 CopyPlayerNameIntoFileName(&p, gameInfo.white);
13218 CopyPlayerNameIntoFileName(&p, gameInfo.black);
13220 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13227 /* Save the current game to the given file */
13229 SaveGameToFile (char *filename, int append)
13233 int result, i, t,tot=0;
13235 if (strcmp(filename, "-") == 0) {
13236 return SaveGame(stdout, 0, NULL);
13238 for(i=0; i<10; i++) { // upto 10 tries
13239 f = fopen(filename, append ? "a" : "w");
13240 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13241 if(f || errno != 13) break;
13242 DoSleep(t = 5 + random()%11); // wait 5-15 msec
13246 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13247 DisplayError(buf, errno);
13250 safeStrCpy(buf, lastMsg, MSG_SIZ);
13251 DisplayMessage(_("Waiting for access to save file"), "");
13252 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13253 DisplayMessage(_("Saving game"), "");
13254 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
13255 result = SaveGame(f, 0, NULL);
13256 DisplayMessage(buf, "");
13263 SavePart (char *str)
13265 static char buf[MSG_SIZ];
13268 p = strchr(str, ' ');
13269 if (p == NULL) return str;
13270 strncpy(buf, str, p - str);
13271 buf[p - str] = NULLCHAR;
13275 #define PGN_MAX_LINE 75
13277 #define PGN_SIDE_WHITE 0
13278 #define PGN_SIDE_BLACK 1
13281 FindFirstMoveOutOfBook (int side)
13285 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13286 int index = backwardMostMove;
13287 int has_book_hit = 0;
13289 if( (index % 2) != side ) {
13293 while( index < forwardMostMove ) {
13294 /* Check to see if engine is in book */
13295 int depth = pvInfoList[index].depth;
13296 int score = pvInfoList[index].score;
13302 else if( score == 0 && depth == 63 ) {
13303 in_book = 1; /* Zappa */
13305 else if( score == 2 && depth == 99 ) {
13306 in_book = 1; /* Abrok */
13309 has_book_hit += in_book;
13325 GetOutOfBookInfo (char * buf)
13329 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13331 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13332 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13336 if( oob[0] >= 0 || oob[1] >= 0 ) {
13337 for( i=0; i<2; i++ ) {
13341 if( i > 0 && oob[0] >= 0 ) {
13342 strcat( buf, " " );
13345 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13346 sprintf( buf+strlen(buf), "%s%.2f",
13347 pvInfoList[idx].score >= 0 ? "+" : "",
13348 pvInfoList[idx].score / 100.0 );
13354 /* Save game in PGN style and close the file */
13356 SaveGamePGN (FILE *f)
13358 int i, offset, linelen, newblock;
13361 int movelen, numlen, blank;
13362 char move_buffer[100]; /* [AS] Buffer for move+PV info */
13364 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13366 PrintPGNTags(f, &gameInfo);
13368 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13370 if (backwardMostMove > 0 || startedFromSetupPosition) {
13371 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13372 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13373 fprintf(f, "\n{--------------\n");
13374 PrintPosition(f, backwardMostMove);
13375 fprintf(f, "--------------}\n");
13379 /* [AS] Out of book annotation */
13380 if( appData.saveOutOfBookInfo ) {
13383 GetOutOfBookInfo( buf );
13385 if( buf[0] != '\0' ) {
13386 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13393 i = backwardMostMove;
13397 while (i < forwardMostMove) {
13398 /* Print comments preceding this move */
13399 if (commentList[i] != NULL) {
13400 if (linelen > 0) fprintf(f, "\n");
13401 fprintf(f, "%s", commentList[i]);
13406 /* Format move number */
13408 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13411 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13413 numtext[0] = NULLCHAR;
13415 numlen = strlen(numtext);
13418 /* Print move number */
13419 blank = linelen > 0 && numlen > 0;
13420 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13429 fprintf(f, "%s", numtext);
13433 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13434 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13437 blank = linelen > 0 && movelen > 0;
13438 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13447 fprintf(f, "%s", move_buffer);
13448 linelen += movelen;
13450 /* [AS] Add PV info if present */
13451 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13452 /* [HGM] add time */
13453 char buf[MSG_SIZ]; int seconds;
13455 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13461 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13464 seconds = (seconds + 4)/10; // round to full seconds
13466 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13468 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13471 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13472 pvInfoList[i].score >= 0 ? "+" : "",
13473 pvInfoList[i].score / 100.0,
13474 pvInfoList[i].depth,
13477 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13479 /* Print score/depth */
13480 blank = linelen > 0 && movelen > 0;
13481 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13490 fprintf(f, "%s", move_buffer);
13491 linelen += movelen;
13497 /* Start a new line */
13498 if (linelen > 0) fprintf(f, "\n");
13500 /* Print comments after last move */
13501 if (commentList[i] != NULL) {
13502 fprintf(f, "%s\n", commentList[i]);
13506 if (gameInfo.resultDetails != NULL &&
13507 gameInfo.resultDetails[0] != NULLCHAR) {
13508 char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13509 if(gameInfo.result == GameUnfinished && appData.clockMode &&
13510 (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13511 snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13512 fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13514 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13518 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13522 /* Save game in old style and close the file */
13524 SaveGameOldStyle (FILE *f)
13529 tm = time((time_t *) NULL);
13531 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13534 if (backwardMostMove > 0 || startedFromSetupPosition) {
13535 fprintf(f, "\n[--------------\n");
13536 PrintPosition(f, backwardMostMove);
13537 fprintf(f, "--------------]\n");
13542 i = backwardMostMove;
13543 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13545 while (i < forwardMostMove) {
13546 if (commentList[i] != NULL) {
13547 fprintf(f, "[%s]\n", commentList[i]);
13550 if ((i % 2) == 1) {
13551 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
13554 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
13556 if (commentList[i] != NULL) {
13560 if (i >= forwardMostMove) {
13564 fprintf(f, "%s\n", parseList[i]);
13569 if (commentList[i] != NULL) {
13570 fprintf(f, "[%s]\n", commentList[i]);
13573 /* This isn't really the old style, but it's close enough */
13574 if (gameInfo.resultDetails != NULL &&
13575 gameInfo.resultDetails[0] != NULLCHAR) {
13576 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13577 gameInfo.resultDetails);
13579 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13586 /* Save the current game to open file f and close the file */
13588 SaveGame (FILE *f, int dummy, char *dummy2)
13590 if (gameMode == EditPosition) EditPositionDone(TRUE);
13591 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13592 if (appData.oldSaveStyle)
13593 return SaveGameOldStyle(f);
13595 return SaveGamePGN(f);
13598 /* Save the current position to the given file */
13600 SavePositionToFile (char *filename)
13605 if (strcmp(filename, "-") == 0) {
13606 return SavePosition(stdout, 0, NULL);
13608 f = fopen(filename, "a");
13610 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13611 DisplayError(buf, errno);
13614 safeStrCpy(buf, lastMsg, MSG_SIZ);
13615 DisplayMessage(_("Waiting for access to save file"), "");
13616 flock(fileno(f), LOCK_EX); // [HGM] lock
13617 DisplayMessage(_("Saving position"), "");
13618 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
13619 SavePosition(f, 0, NULL);
13620 DisplayMessage(buf, "");
13626 /* Save the current position to the given open file and close the file */
13628 SavePosition (FILE *f, int dummy, char *dummy2)
13633 if (gameMode == EditPosition) EditPositionDone(TRUE);
13634 if (appData.oldSaveStyle) {
13635 tm = time((time_t *) NULL);
13637 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13639 fprintf(f, "[--------------\n");
13640 PrintPosition(f, currentMove);
13641 fprintf(f, "--------------]\n");
13643 fen = PositionToFEN(currentMove, NULL, 1);
13644 fprintf(f, "%s\n", fen);
13652 ReloadCmailMsgEvent (int unregister)
13655 static char *inFilename = NULL;
13656 static char *outFilename;
13658 struct stat inbuf, outbuf;
13661 /* Any registered moves are unregistered if unregister is set, */
13662 /* i.e. invoked by the signal handler */
13664 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13665 cmailMoveRegistered[i] = FALSE;
13666 if (cmailCommentList[i] != NULL) {
13667 free(cmailCommentList[i]);
13668 cmailCommentList[i] = NULL;
13671 nCmailMovesRegistered = 0;
13674 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13675 cmailResult[i] = CMAIL_NOT_RESULT;
13679 if (inFilename == NULL) {
13680 /* Because the filenames are static they only get malloced once */
13681 /* and they never get freed */
13682 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13683 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13685 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13686 sprintf(outFilename, "%s.out", appData.cmailGameName);
13689 status = stat(outFilename, &outbuf);
13691 cmailMailedMove = FALSE;
13693 status = stat(inFilename, &inbuf);
13694 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13697 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13698 counts the games, notes how each one terminated, etc.
13700 It would be nice to remove this kludge and instead gather all
13701 the information while building the game list. (And to keep it
13702 in the game list nodes instead of having a bunch of fixed-size
13703 parallel arrays.) Note this will require getting each game's
13704 termination from the PGN tags, as the game list builder does
13705 not process the game moves. --mann
13707 cmailMsgLoaded = TRUE;
13708 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13710 /* Load first game in the file or popup game menu */
13711 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13713 #endif /* !WIN32 */
13721 char string[MSG_SIZ];
13723 if ( cmailMailedMove
13724 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13725 return TRUE; /* Allow free viewing */
13728 /* Unregister move to ensure that we don't leave RegisterMove */
13729 /* with the move registered when the conditions for registering no */
13731 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13732 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13733 nCmailMovesRegistered --;
13735 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13737 free(cmailCommentList[lastLoadGameNumber - 1]);
13738 cmailCommentList[lastLoadGameNumber - 1] = NULL;
13742 if (cmailOldMove == -1) {
13743 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13747 if (currentMove > cmailOldMove + 1) {
13748 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13752 if (currentMove < cmailOldMove) {
13753 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13757 if (forwardMostMove > currentMove) {
13758 /* Silently truncate extra moves */
13762 if ( (currentMove == cmailOldMove + 1)
13763 || ( (currentMove == cmailOldMove)
13764 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13765 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13766 if (gameInfo.result != GameUnfinished) {
13767 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13770 if (commentList[currentMove] != NULL) {
13771 cmailCommentList[lastLoadGameNumber - 1]
13772 = StrSave(commentList[currentMove]);
13774 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13776 if (appData.debugMode)
13777 fprintf(debugFP, "Saving %s for game %d\n",
13778 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13780 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13782 f = fopen(string, "w");
13783 if (appData.oldSaveStyle) {
13784 SaveGameOldStyle(f); /* also closes the file */
13786 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13787 f = fopen(string, "w");
13788 SavePosition(f, 0, NULL); /* also closes the file */
13790 fprintf(f, "{--------------\n");
13791 PrintPosition(f, currentMove);
13792 fprintf(f, "--------------}\n\n");
13794 SaveGame(f, 0, NULL); /* also closes the file*/
13797 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13798 nCmailMovesRegistered ++;
13799 } else if (nCmailGames == 1) {
13800 DisplayError(_("You have not made a move yet"), 0);
13811 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13812 FILE *commandOutput;
13813 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13814 int nBytes = 0; /* Suppress warnings on uninitialized variables */
13820 if (! cmailMsgLoaded) {
13821 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13825 if (nCmailGames == nCmailResults) {
13826 DisplayError(_("No unfinished games"), 0);
13830 #if CMAIL_PROHIBIT_REMAIL
13831 if (cmailMailedMove) {
13832 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);
13833 DisplayError(msg, 0);
13838 if (! (cmailMailedMove || RegisterMove())) return;
13840 if ( cmailMailedMove
13841 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13842 snprintf(string, MSG_SIZ, partCommandString,
13843 appData.debugMode ? " -v" : "", appData.cmailGameName);
13844 commandOutput = popen(string, "r");
13846 if (commandOutput == NULL) {
13847 DisplayError(_("Failed to invoke cmail"), 0);
13849 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13850 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13852 if (nBuffers > 1) {
13853 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13854 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13855 nBytes = MSG_SIZ - 1;
13857 (void) memcpy(msg, buffer, nBytes);
13859 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13861 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13862 cmailMailedMove = TRUE; /* Prevent >1 moves */
13865 for (i = 0; i < nCmailGames; i ++) {
13866 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13871 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13873 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13875 appData.cmailGameName,
13877 LoadGameFromFile(buffer, 1, buffer, FALSE);
13878 cmailMsgLoaded = FALSE;
13882 DisplayInformation(msg);
13883 pclose(commandOutput);
13886 if ((*cmailMsg) != '\0') {
13887 DisplayInformation(cmailMsg);
13892 #endif /* !WIN32 */
13901 int prependComma = 0;
13903 char string[MSG_SIZ]; /* Space for game-list */
13906 if (!cmailMsgLoaded) return "";
13908 if (cmailMailedMove) {
13909 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13911 /* Create a list of games left */
13912 snprintf(string, MSG_SIZ, "[");
13913 for (i = 0; i < nCmailGames; i ++) {
13914 if (! ( cmailMoveRegistered[i]
13915 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13916 if (prependComma) {
13917 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13919 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13923 strcat(string, number);
13926 strcat(string, "]");
13928 if (nCmailMovesRegistered + nCmailResults == 0) {
13929 switch (nCmailGames) {
13931 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13935 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13939 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13944 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13946 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13951 if (nCmailResults == nCmailGames) {
13952 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13954 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13959 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13971 if (gameMode == Training)
13972 SetTrainingModeOff();
13975 cmailMsgLoaded = FALSE;
13976 if (appData.icsActive) {
13977 SendToICS(ics_prefix);
13978 SendToICS("refresh\n");
13983 ExitEvent (int status)
13987 /* Give up on clean exit */
13991 /* Keep trying for clean exit */
13995 if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
13996 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13998 if (telnetISR != NULL) {
13999 RemoveInputSource(telnetISR);
14001 if (icsPR != NoProc) {
14002 DestroyChildProcess(icsPR, TRUE);
14005 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14006 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14008 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14009 /* make sure this other one finishes before killing it! */
14010 if(endingGame) { int count = 0;
14011 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14012 while(endingGame && count++ < 10) DoSleep(1);
14013 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14016 /* Kill off chess programs */
14017 if (first.pr != NoProc) {
14020 DoSleep( appData.delayBeforeQuit );
14021 SendToProgram("quit\n", &first);
14022 DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14024 if (second.pr != NoProc) {
14025 DoSleep( appData.delayBeforeQuit );
14026 SendToProgram("quit\n", &second);
14027 DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14029 if (first.isr != NULL) {
14030 RemoveInputSource(first.isr);
14032 if (second.isr != NULL) {
14033 RemoveInputSource(second.isr);
14036 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14037 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14039 ShutDownFrontEnd();
14044 PauseEngine (ChessProgramState *cps)
14046 SendToProgram("pause\n", cps);
14051 UnPauseEngine (ChessProgramState *cps)
14053 SendToProgram("resume\n", cps);
14060 if (appData.debugMode)
14061 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14065 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14067 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14068 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14069 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14071 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14072 HandleMachineMove(stashedInputMove, stalledEngine);
14073 stalledEngine = NULL;
14076 if (gameMode == MachinePlaysWhite ||
14077 gameMode == TwoMachinesPlay ||
14078 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14079 if(first.pause) UnPauseEngine(&first);
14080 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14081 if(second.pause) UnPauseEngine(&second);
14082 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14085 DisplayBothClocks();
14087 if (gameMode == PlayFromGameFile) {
14088 if (appData.timeDelay >= 0)
14089 AutoPlayGameLoop();
14090 } else if (gameMode == IcsExamining && pauseExamInvalid) {
14091 Reset(FALSE, TRUE);
14092 SendToICS(ics_prefix);
14093 SendToICS("refresh\n");
14094 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14095 ForwardInner(forwardMostMove);
14097 pauseExamInvalid = FALSE;
14099 switch (gameMode) {
14103 pauseExamForwardMostMove = forwardMostMove;
14104 pauseExamInvalid = FALSE;
14107 case IcsPlayingWhite:
14108 case IcsPlayingBlack:
14112 case PlayFromGameFile:
14113 (void) StopLoadGameTimer();
14117 case BeginningOfGame:
14118 if (appData.icsActive) return;
14119 /* else fall through */
14120 case MachinePlaysWhite:
14121 case MachinePlaysBlack:
14122 case TwoMachinesPlay:
14123 if (forwardMostMove == 0)
14124 return; /* don't pause if no one has moved */
14125 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14126 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14127 if(onMove->pause) { // thinking engine can be paused
14128 PauseEngine(onMove); // do it
14129 if(onMove->other->pause) // pondering opponent can always be paused immediately
14130 PauseEngine(onMove->other);
14132 SendToProgram("easy\n", onMove->other);
14134 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14135 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14137 PauseEngine(&first);
14139 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14140 } else { // human on move, pause pondering by either method
14142 PauseEngine(&first);
14143 else if(appData.ponderNextMove)
14144 SendToProgram("easy\n", &first);
14147 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14157 EditCommentEvent ()
14159 char title[MSG_SIZ];
14161 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14162 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14164 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14165 WhiteOnMove(currentMove - 1) ? " " : ".. ",
14166 parseList[currentMove - 1]);
14169 EditCommentPopUp(currentMove, title, commentList[currentMove]);
14176 char *tags = PGNTags(&gameInfo);
14178 EditTagsPopUp(tags, NULL);
14185 if(second.analyzing) {
14186 SendToProgram("exit\n", &second);
14187 second.analyzing = FALSE;
14189 if (second.pr == NoProc) StartChessProgram(&second);
14190 InitChessProgram(&second, FALSE);
14191 FeedMovesToProgram(&second, currentMove);
14193 SendToProgram("analyze\n", &second);
14194 second.analyzing = TRUE;
14198 /* Toggle ShowThinking */
14200 ToggleShowThinking()
14202 appData.showThinking = !appData.showThinking;
14203 ShowThinkingEvent();
14207 AnalyzeModeEvent ()
14211 if (!first.analysisSupport) {
14212 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14213 DisplayError(buf, 0);
14216 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14217 if (appData.icsActive) {
14218 if (gameMode != IcsObserving) {
14219 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14220 DisplayError(buf, 0);
14222 if (appData.icsEngineAnalyze) {
14223 if (appData.debugMode)
14224 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14230 /* if enable, user wants to disable icsEngineAnalyze */
14231 if (appData.icsEngineAnalyze) {
14236 appData.icsEngineAnalyze = TRUE;
14237 if (appData.debugMode)
14238 fprintf(debugFP, "ICS engine analyze starting... \n");
14241 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14242 if (appData.noChessProgram || gameMode == AnalyzeMode)
14245 if (gameMode != AnalyzeFile) {
14246 if (!appData.icsEngineAnalyze) {
14248 if (gameMode != EditGame) return 0;
14250 if (!appData.showThinking) ToggleShowThinking();
14251 ResurrectChessProgram();
14252 SendToProgram("analyze\n", &first);
14253 first.analyzing = TRUE;
14254 /*first.maybeThinking = TRUE;*/
14255 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14256 EngineOutputPopUp();
14258 if (!appData.icsEngineAnalyze) {
14259 gameMode = AnalyzeMode;
14260 ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14266 StartAnalysisClock();
14267 GetTimeMark(&lastNodeCountTime);
14273 AnalyzeFileEvent ()
14275 if (appData.noChessProgram || gameMode == AnalyzeFile)
14278 if (!first.analysisSupport) {
14280 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14281 DisplayError(buf, 0);
14285 if (gameMode != AnalyzeMode) {
14286 keepInfo = 1; // mere annotating should not alter PGN tags
14289 if (gameMode != EditGame) return;
14290 if (!appData.showThinking) ToggleShowThinking();
14291 ResurrectChessProgram();
14292 SendToProgram("analyze\n", &first);
14293 first.analyzing = TRUE;
14294 /*first.maybeThinking = TRUE;*/
14295 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14296 EngineOutputPopUp();
14298 gameMode = AnalyzeFile;
14302 StartAnalysisClock();
14303 GetTimeMark(&lastNodeCountTime);
14305 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14306 AnalysisPeriodicEvent(1);
14310 MachineWhiteEvent ()
14313 char *bookHit = NULL;
14315 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14319 if (gameMode == PlayFromGameFile ||
14320 gameMode == TwoMachinesPlay ||
14321 gameMode == Training ||
14322 gameMode == AnalyzeMode ||
14323 gameMode == EndOfGame)
14326 if (gameMode == EditPosition)
14327 EditPositionDone(TRUE);
14329 if (!WhiteOnMove(currentMove)) {
14330 DisplayError(_("It is not White's turn"), 0);
14334 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14337 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14338 gameMode == AnalyzeFile)
14341 ResurrectChessProgram(); /* in case it isn't running */
14342 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14343 gameMode = MachinePlaysWhite;
14346 gameMode = MachinePlaysWhite;
14350 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14352 if (first.sendName) {
14353 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14354 SendToProgram(buf, &first);
14356 if (first.sendTime) {
14357 if (first.useColors) {
14358 SendToProgram("black\n", &first); /*gnu kludge*/
14360 SendTimeRemaining(&first, TRUE);
14362 if (first.useColors) {
14363 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14365 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14366 SetMachineThinkingEnables();
14367 first.maybeThinking = TRUE;
14371 if (appData.autoFlipView && !flipView) {
14372 flipView = !flipView;
14373 DrawPosition(FALSE, NULL);
14374 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14377 if(bookHit) { // [HGM] book: simulate book reply
14378 static char bookMove[MSG_SIZ]; // a bit generous?
14380 programStats.nodes = programStats.depth = programStats.time =
14381 programStats.score = programStats.got_only_move = 0;
14382 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14384 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14385 strcat(bookMove, bookHit);
14386 HandleMachineMove(bookMove, &first);
14391 MachineBlackEvent ()
14394 char *bookHit = NULL;
14396 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14400 if (gameMode == PlayFromGameFile ||
14401 gameMode == TwoMachinesPlay ||
14402 gameMode == Training ||
14403 gameMode == AnalyzeMode ||
14404 gameMode == EndOfGame)
14407 if (gameMode == EditPosition)
14408 EditPositionDone(TRUE);
14410 if (WhiteOnMove(currentMove)) {
14411 DisplayError(_("It is not Black's turn"), 0);
14415 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14418 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14419 gameMode == AnalyzeFile)
14422 ResurrectChessProgram(); /* in case it isn't running */
14423 gameMode = MachinePlaysBlack;
14427 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14429 if (first.sendName) {
14430 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14431 SendToProgram(buf, &first);
14433 if (first.sendTime) {
14434 if (first.useColors) {
14435 SendToProgram("white\n", &first); /*gnu kludge*/
14437 SendTimeRemaining(&first, FALSE);
14439 if (first.useColors) {
14440 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14442 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14443 SetMachineThinkingEnables();
14444 first.maybeThinking = TRUE;
14447 if (appData.autoFlipView && flipView) {
14448 flipView = !flipView;
14449 DrawPosition(FALSE, NULL);
14450 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14452 if(bookHit) { // [HGM] book: simulate book reply
14453 static char bookMove[MSG_SIZ]; // a bit generous?
14455 programStats.nodes = programStats.depth = programStats.time =
14456 programStats.score = programStats.got_only_move = 0;
14457 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14459 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14460 strcat(bookMove, bookHit);
14461 HandleMachineMove(bookMove, &first);
14467 DisplayTwoMachinesTitle ()
14470 if (appData.matchGames > 0) {
14471 if(appData.tourneyFile[0]) {
14472 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14473 gameInfo.white, _("vs."), gameInfo.black,
14474 nextGame+1, appData.matchGames+1,
14475 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14477 if (first.twoMachinesColor[0] == 'w') {
14478 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14479 gameInfo.white, _("vs."), gameInfo.black,
14480 first.matchWins, second.matchWins,
14481 matchGame - 1 - (first.matchWins + second.matchWins));
14483 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14484 gameInfo.white, _("vs."), gameInfo.black,
14485 second.matchWins, first.matchWins,
14486 matchGame - 1 - (first.matchWins + second.matchWins));
14489 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14495 SettingsMenuIfReady ()
14497 if (second.lastPing != second.lastPong) {
14498 DisplayMessage("", _("Waiting for second chess program"));
14499 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14503 DisplayMessage("", "");
14504 SettingsPopUp(&second);
14508 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14511 if (cps->pr == NoProc) {
14512 StartChessProgram(cps);
14513 if (cps->protocolVersion == 1) {
14515 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14517 /* kludge: allow timeout for initial "feature" command */
14518 if(retry != TwoMachinesEventIfReady) FreezeUI();
14519 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14520 DisplayMessage("", buf);
14521 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14529 TwoMachinesEvent P((void))
14533 ChessProgramState *onmove;
14534 char *bookHit = NULL;
14535 static int stalling = 0;
14539 if (appData.noChessProgram) return;
14541 switch (gameMode) {
14542 case TwoMachinesPlay:
14544 case MachinePlaysWhite:
14545 case MachinePlaysBlack:
14546 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14547 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14551 case BeginningOfGame:
14552 case PlayFromGameFile:
14555 if (gameMode != EditGame) return;
14558 EditPositionDone(TRUE);
14569 // forwardMostMove = currentMove;
14570 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14571 startingEngine = TRUE;
14573 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14575 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14576 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14577 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14580 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14582 if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14583 gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14584 startingEngine = FALSE;
14585 DisplayError("second engine does not play this", 0);
14590 InitChessProgram(&second, FALSE); // unbalances ping of second engine
14591 SendToProgram("force\n", &second);
14593 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14596 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14597 if(appData.matchPause>10000 || appData.matchPause<10)
14598 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14599 wait = SubtractTimeMarks(&now, &pauseStart);
14600 if(wait < appData.matchPause) {
14601 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14604 // we are now committed to starting the game
14606 DisplayMessage("", "");
14607 if (startedFromSetupPosition) {
14608 SendBoard(&second, backwardMostMove);
14609 if (appData.debugMode) {
14610 fprintf(debugFP, "Two Machines\n");
14613 for (i = backwardMostMove; i < forwardMostMove; i++) {
14614 SendMoveToProgram(i, &second);
14617 gameMode = TwoMachinesPlay;
14618 pausing = startingEngine = FALSE;
14619 ModeHighlight(); // [HGM] logo: this triggers display update of logos
14621 DisplayTwoMachinesTitle();
14623 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14628 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14629 SendToProgram(first.computerString, &first);
14630 if (first.sendName) {
14631 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14632 SendToProgram(buf, &first);
14634 SendToProgram(second.computerString, &second);
14635 if (second.sendName) {
14636 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14637 SendToProgram(buf, &second);
14641 if (!first.sendTime || !second.sendTime) {
14642 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14643 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14645 if (onmove->sendTime) {
14646 if (onmove->useColors) {
14647 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14649 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14651 if (onmove->useColors) {
14652 SendToProgram(onmove->twoMachinesColor, onmove);
14654 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14655 // SendToProgram("go\n", onmove);
14656 onmove->maybeThinking = TRUE;
14657 SetMachineThinkingEnables();
14661 if(bookHit) { // [HGM] book: simulate book reply
14662 static char bookMove[MSG_SIZ]; // a bit generous?
14664 programStats.nodes = programStats.depth = programStats.time =
14665 programStats.score = programStats.got_only_move = 0;
14666 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14668 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14669 strcat(bookMove, bookHit);
14670 savedMessage = bookMove; // args for deferred call
14671 savedState = onmove;
14672 ScheduleDelayedEvent(DeferredBookMove, 1);
14679 if (gameMode == Training) {
14680 SetTrainingModeOff();
14681 gameMode = PlayFromGameFile;
14682 DisplayMessage("", _("Training mode off"));
14684 gameMode = Training;
14685 animateTraining = appData.animate;
14687 /* make sure we are not already at the end of the game */
14688 if (currentMove < forwardMostMove) {
14689 SetTrainingModeOn();
14690 DisplayMessage("", _("Training mode on"));
14692 gameMode = PlayFromGameFile;
14693 DisplayError(_("Already at end of game"), 0);
14702 if (!appData.icsActive) return;
14703 switch (gameMode) {
14704 case IcsPlayingWhite:
14705 case IcsPlayingBlack:
14708 case BeginningOfGame:
14716 EditPositionDone(TRUE);
14729 gameMode = IcsIdle;
14739 switch (gameMode) {
14741 SetTrainingModeOff();
14743 case MachinePlaysWhite:
14744 case MachinePlaysBlack:
14745 case BeginningOfGame:
14746 SendToProgram("force\n", &first);
14747 SetUserThinkingEnables();
14749 case PlayFromGameFile:
14750 (void) StopLoadGameTimer();
14751 if (gameFileFP != NULL) {
14756 EditPositionDone(TRUE);
14761 SendToProgram("force\n", &first);
14763 case TwoMachinesPlay:
14764 GameEnds(EndOfFile, NULL, GE_PLAYER);
14765 ResurrectChessProgram();
14766 SetUserThinkingEnables();
14769 ResurrectChessProgram();
14771 case IcsPlayingBlack:
14772 case IcsPlayingWhite:
14773 DisplayError(_("Warning: You are still playing a game"), 0);
14776 DisplayError(_("Warning: You are still observing a game"), 0);
14779 DisplayError(_("Warning: You are still examining a game"), 0);
14790 first.offeredDraw = second.offeredDraw = 0;
14792 if (gameMode == PlayFromGameFile) {
14793 whiteTimeRemaining = timeRemaining[0][currentMove];
14794 blackTimeRemaining = timeRemaining[1][currentMove];
14798 if (gameMode == MachinePlaysWhite ||
14799 gameMode == MachinePlaysBlack ||
14800 gameMode == TwoMachinesPlay ||
14801 gameMode == EndOfGame) {
14802 i = forwardMostMove;
14803 while (i > currentMove) {
14804 SendToProgram("undo\n", &first);
14807 if(!adjustedClock) {
14808 whiteTimeRemaining = timeRemaining[0][currentMove];
14809 blackTimeRemaining = timeRemaining[1][currentMove];
14810 DisplayBothClocks();
14812 if (whiteFlag || blackFlag) {
14813 whiteFlag = blackFlag = 0;
14818 gameMode = EditGame;
14825 EditPositionEvent ()
14827 if (gameMode == EditPosition) {
14833 if (gameMode != EditGame) return;
14835 gameMode = EditPosition;
14838 if (currentMove > 0)
14839 CopyBoard(boards[0], boards[currentMove]);
14841 blackPlaysFirst = !WhiteOnMove(currentMove);
14843 currentMove = forwardMostMove = backwardMostMove = 0;
14844 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14846 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14852 /* [DM] icsEngineAnalyze - possible call from other functions */
14853 if (appData.icsEngineAnalyze) {
14854 appData.icsEngineAnalyze = FALSE;
14856 DisplayMessage("",_("Close ICS engine analyze..."));
14858 if (first.analysisSupport && first.analyzing) {
14859 SendToBoth("exit\n");
14860 first.analyzing = second.analyzing = FALSE;
14862 thinkOutput[0] = NULLCHAR;
14866 EditPositionDone (Boolean fakeRights)
14868 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14870 startedFromSetupPosition = TRUE;
14871 InitChessProgram(&first, FALSE);
14872 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14873 boards[0][EP_STATUS] = EP_NONE;
14874 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14875 if(boards[0][0][BOARD_WIDTH>>1] == king) {
14876 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14877 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14878 } else boards[0][CASTLING][2] = NoRights;
14879 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14880 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14881 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14882 } else boards[0][CASTLING][5] = NoRights;
14883 if(gameInfo.variant == VariantSChess) {
14885 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14886 boards[0][VIRGIN][i] = 0;
14887 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14888 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14892 SendToProgram("force\n", &first);
14893 if (blackPlaysFirst) {
14894 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14895 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14896 currentMove = forwardMostMove = backwardMostMove = 1;
14897 CopyBoard(boards[1], boards[0]);
14899 currentMove = forwardMostMove = backwardMostMove = 0;
14901 SendBoard(&first, forwardMostMove);
14902 if (appData.debugMode) {
14903 fprintf(debugFP, "EditPosDone\n");
14906 DisplayMessage("", "");
14907 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14908 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14909 gameMode = EditGame;
14911 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14912 ClearHighlights(); /* [AS] */
14915 /* Pause for `ms' milliseconds */
14916 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14918 TimeDelay (long ms)
14925 } while (SubtractTimeMarks(&m2, &m1) < ms);
14928 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14930 SendMultiLineToICS (char *buf)
14932 char temp[MSG_SIZ+1], *p;
14939 strncpy(temp, buf, len);
14944 if (*p == '\n' || *p == '\r')
14949 strcat(temp, "\n");
14951 SendToPlayer(temp, strlen(temp));
14955 SetWhiteToPlayEvent ()
14957 if (gameMode == EditPosition) {
14958 blackPlaysFirst = FALSE;
14959 DisplayBothClocks(); /* works because currentMove is 0 */
14960 } else if (gameMode == IcsExamining) {
14961 SendToICS(ics_prefix);
14962 SendToICS("tomove white\n");
14967 SetBlackToPlayEvent ()
14969 if (gameMode == EditPosition) {
14970 blackPlaysFirst = TRUE;
14971 currentMove = 1; /* kludge */
14972 DisplayBothClocks();
14974 } else if (gameMode == IcsExamining) {
14975 SendToICS(ics_prefix);
14976 SendToICS("tomove black\n");
14981 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14984 ChessSquare piece = boards[0][y][x];
14985 static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14986 static int lastVariant;
14988 if (gameMode != EditPosition && gameMode != IcsExamining) return;
14990 switch (selection) {
14992 CopyBoard(currentBoard, boards[0]);
14993 CopyBoard(menuBoard, initialPosition);
14994 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14995 SendToICS(ics_prefix);
14996 SendToICS("bsetup clear\n");
14997 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14998 SendToICS(ics_prefix);
14999 SendToICS("clearboard\n");
15002 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15003 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15004 for (y = 0; y < BOARD_HEIGHT; y++) {
15005 if (gameMode == IcsExamining) {
15006 if (boards[currentMove][y][x] != EmptySquare) {
15007 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15012 if(boards[0][y][x] != p) nonEmpty++;
15013 boards[0][y][x] = p;
15016 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
15018 if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15019 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
15020 ChessSquare p = menuBoard[0][x];
15021 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
15022 p = menuBoard[BOARD_HEIGHT-1][x];
15023 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
15025 DisplayMessage("Clicking clock again restores position", "");
15026 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15027 if(!nonEmpty) { // asked to clear an empty board
15028 CopyBoard(boards[0], menuBoard);
15030 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15031 CopyBoard(boards[0], initialPosition);
15033 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15034 && !CompareBoards(nullBoard, erasedBoard)) {
15035 CopyBoard(boards[0], erasedBoard);
15037 CopyBoard(erasedBoard, currentBoard);
15041 if (gameMode == EditPosition) {
15042 DrawPosition(FALSE, boards[0]);
15047 SetWhiteToPlayEvent();
15051 SetBlackToPlayEvent();
15055 if (gameMode == IcsExamining) {
15056 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15057 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15060 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15061 if(x == BOARD_LEFT-2) {
15062 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15063 boards[0][y][1] = 0;
15065 if(x == BOARD_RGHT+1) {
15066 if(y >= gameInfo.holdingsSize) break;
15067 boards[0][y][BOARD_WIDTH-2] = 0;
15070 boards[0][y][x] = EmptySquare;
15071 DrawPosition(FALSE, boards[0]);
15076 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15077 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
15078 selection = (ChessSquare) (PROMOTED piece);
15079 } else if(piece == EmptySquare) selection = WhiteSilver;
15080 else selection = (ChessSquare)((int)piece - 1);
15084 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15085 piece > (int)BlackMan && piece <= (int)BlackKing ) {
15086 selection = (ChessSquare) (DEMOTED piece);
15087 } else if(piece == EmptySquare) selection = BlackSilver;
15088 else selection = (ChessSquare)((int)piece + 1);
15093 if(gameInfo.variant == VariantShatranj ||
15094 gameInfo.variant == VariantXiangqi ||
15095 gameInfo.variant == VariantCourier ||
15096 gameInfo.variant == VariantASEAN ||
15097 gameInfo.variant == VariantMakruk )
15098 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15103 if(gameInfo.variant == VariantXiangqi)
15104 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15105 if(gameInfo.variant == VariantKnightmate)
15106 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15109 if (gameMode == IcsExamining) {
15110 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15111 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15112 PieceToChar(selection), AAA + x, ONE + y);
15115 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15117 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15118 n = PieceToNumber(selection - BlackPawn);
15119 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15120 boards[0][BOARD_HEIGHT-1-n][0] = selection;
15121 boards[0][BOARD_HEIGHT-1-n][1]++;
15123 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15124 n = PieceToNumber(selection);
15125 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15126 boards[0][n][BOARD_WIDTH-1] = selection;
15127 boards[0][n][BOARD_WIDTH-2]++;
15130 boards[0][y][x] = selection;
15131 DrawPosition(TRUE, boards[0]);
15133 fromX = fromY = -1;
15141 DropMenuEvent (ChessSquare selection, int x, int y)
15143 ChessMove moveType;
15145 switch (gameMode) {
15146 case IcsPlayingWhite:
15147 case MachinePlaysBlack:
15148 if (!WhiteOnMove(currentMove)) {
15149 DisplayMoveError(_("It is Black's turn"));
15152 moveType = WhiteDrop;
15154 case IcsPlayingBlack:
15155 case MachinePlaysWhite:
15156 if (WhiteOnMove(currentMove)) {
15157 DisplayMoveError(_("It is White's turn"));
15160 moveType = BlackDrop;
15163 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15169 if (moveType == BlackDrop && selection < BlackPawn) {
15170 selection = (ChessSquare) ((int) selection
15171 + (int) BlackPawn - (int) WhitePawn);
15173 if (boards[currentMove][y][x] != EmptySquare) {
15174 DisplayMoveError(_("That square is occupied"));
15178 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15184 /* Accept a pending offer of any kind from opponent */
15186 if (appData.icsActive) {
15187 SendToICS(ics_prefix);
15188 SendToICS("accept\n");
15189 } else if (cmailMsgLoaded) {
15190 if (currentMove == cmailOldMove &&
15191 commentList[cmailOldMove] != NULL &&
15192 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15193 "Black offers a draw" : "White offers a draw")) {
15195 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15196 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15198 DisplayError(_("There is no pending offer on this move"), 0);
15199 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15202 /* Not used for offers from chess program */
15209 /* Decline a pending offer of any kind from opponent */
15211 if (appData.icsActive) {
15212 SendToICS(ics_prefix);
15213 SendToICS("decline\n");
15214 } else if (cmailMsgLoaded) {
15215 if (currentMove == cmailOldMove &&
15216 commentList[cmailOldMove] != NULL &&
15217 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15218 "Black offers a draw" : "White offers a draw")) {
15220 AppendComment(cmailOldMove, "Draw declined", TRUE);
15221 DisplayComment(cmailOldMove - 1, "Draw declined");
15224 DisplayError(_("There is no pending offer on this move"), 0);
15227 /* Not used for offers from chess program */
15234 /* Issue ICS rematch command */
15235 if (appData.icsActive) {
15236 SendToICS(ics_prefix);
15237 SendToICS("rematch\n");
15244 /* Call your opponent's flag (claim a win on time) */
15245 if (appData.icsActive) {
15246 SendToICS(ics_prefix);
15247 SendToICS("flag\n");
15249 switch (gameMode) {
15252 case MachinePlaysWhite:
15255 GameEnds(GameIsDrawn, "Both players ran out of time",
15258 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15260 DisplayError(_("Your opponent is not out of time"), 0);
15263 case MachinePlaysBlack:
15266 GameEnds(GameIsDrawn, "Both players ran out of time",
15269 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15271 DisplayError(_("Your opponent is not out of time"), 0);
15279 ClockClick (int which)
15280 { // [HGM] code moved to back-end from winboard.c
15281 if(which) { // black clock
15282 if (gameMode == EditPosition || gameMode == IcsExamining) {
15283 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15284 SetBlackToPlayEvent();
15285 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15286 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15287 } else if (shiftKey) {
15288 AdjustClock(which, -1);
15289 } else if (gameMode == IcsPlayingWhite ||
15290 gameMode == MachinePlaysBlack) {
15293 } else { // white clock
15294 if (gameMode == EditPosition || gameMode == IcsExamining) {
15295 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15296 SetWhiteToPlayEvent();
15297 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15298 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15299 } else if (shiftKey) {
15300 AdjustClock(which, -1);
15301 } else if (gameMode == IcsPlayingBlack ||
15302 gameMode == MachinePlaysWhite) {
15311 /* Offer draw or accept pending draw offer from opponent */
15313 if (appData.icsActive) {
15314 /* Note: tournament rules require draw offers to be
15315 made after you make your move but before you punch
15316 your clock. Currently ICS doesn't let you do that;
15317 instead, you immediately punch your clock after making
15318 a move, but you can offer a draw at any time. */
15320 SendToICS(ics_prefix);
15321 SendToICS("draw\n");
15322 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15323 } else if (cmailMsgLoaded) {
15324 if (currentMove == cmailOldMove &&
15325 commentList[cmailOldMove] != NULL &&
15326 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15327 "Black offers a draw" : "White offers a draw")) {
15328 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15329 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15330 } else if (currentMove == cmailOldMove + 1) {
15331 char *offer = WhiteOnMove(cmailOldMove) ?
15332 "White offers a draw" : "Black offers a draw";
15333 AppendComment(currentMove, offer, TRUE);
15334 DisplayComment(currentMove - 1, offer);
15335 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15337 DisplayError(_("You must make your move before offering a draw"), 0);
15338 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15340 } else if (first.offeredDraw) {
15341 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15343 if (first.sendDrawOffers) {
15344 SendToProgram("draw\n", &first);
15345 userOfferedDraw = TRUE;
15353 /* Offer Adjourn or accept pending Adjourn offer from opponent */
15355 if (appData.icsActive) {
15356 SendToICS(ics_prefix);
15357 SendToICS("adjourn\n");
15359 /* Currently GNU Chess doesn't offer or accept Adjourns */
15367 /* Offer Abort or accept pending Abort offer from opponent */
15369 if (appData.icsActive) {
15370 SendToICS(ics_prefix);
15371 SendToICS("abort\n");
15373 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15380 /* Resign. You can do this even if it's not your turn. */
15382 if (appData.icsActive) {
15383 SendToICS(ics_prefix);
15384 SendToICS("resign\n");
15386 switch (gameMode) {
15387 case MachinePlaysWhite:
15388 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15390 case MachinePlaysBlack:
15391 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15394 if (cmailMsgLoaded) {
15396 if (WhiteOnMove(cmailOldMove)) {
15397 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15399 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15401 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15412 StopObservingEvent ()
15414 /* Stop observing current games */
15415 SendToICS(ics_prefix);
15416 SendToICS("unobserve\n");
15420 StopExaminingEvent ()
15422 /* Stop observing current game */
15423 SendToICS(ics_prefix);
15424 SendToICS("unexamine\n");
15428 ForwardInner (int target)
15430 int limit; int oldSeekGraphUp = seekGraphUp;
15432 if (appData.debugMode)
15433 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15434 target, currentMove, forwardMostMove);
15436 if (gameMode == EditPosition)
15439 seekGraphUp = FALSE;
15440 MarkTargetSquares(1);
15442 if (gameMode == PlayFromGameFile && !pausing)
15445 if (gameMode == IcsExamining && pausing)
15446 limit = pauseExamForwardMostMove;
15448 limit = forwardMostMove;
15450 if (target > limit) target = limit;
15452 if (target > 0 && moveList[target - 1][0]) {
15453 int fromX, fromY, toX, toY;
15454 toX = moveList[target - 1][2] - AAA;
15455 toY = moveList[target - 1][3] - ONE;
15456 if (moveList[target - 1][1] == '@') {
15457 if (appData.highlightLastMove) {
15458 SetHighlights(-1, -1, toX, toY);
15461 fromX = moveList[target - 1][0] - AAA;
15462 fromY = moveList[target - 1][1] - ONE;
15463 if (target == currentMove + 1) {
15464 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15466 if (appData.highlightLastMove) {
15467 SetHighlights(fromX, fromY, toX, toY);
15471 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15472 gameMode == Training || gameMode == PlayFromGameFile ||
15473 gameMode == AnalyzeFile) {
15474 while (currentMove < target) {
15475 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15476 SendMoveToProgram(currentMove++, &first);
15479 currentMove = target;
15482 if (gameMode == EditGame || gameMode == EndOfGame) {
15483 whiteTimeRemaining = timeRemaining[0][currentMove];
15484 blackTimeRemaining = timeRemaining[1][currentMove];
15486 DisplayBothClocks();
15487 DisplayMove(currentMove - 1);
15488 DrawPosition(oldSeekGraphUp, boards[currentMove]);
15489 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15490 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15491 DisplayComment(currentMove - 1, commentList[currentMove]);
15493 ClearMap(); // [HGM] exclude: invalidate map
15500 if (gameMode == IcsExamining && !pausing) {
15501 SendToICS(ics_prefix);
15502 SendToICS("forward\n");
15504 ForwardInner(currentMove + 1);
15511 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15512 /* to optimze, we temporarily turn off analysis mode while we feed
15513 * the remaining moves to the engine. Otherwise we get analysis output
15516 if (first.analysisSupport) {
15517 SendToProgram("exit\nforce\n", &first);
15518 first.analyzing = FALSE;
15522 if (gameMode == IcsExamining && !pausing) {
15523 SendToICS(ics_prefix);
15524 SendToICS("forward 999999\n");
15526 ForwardInner(forwardMostMove);
15529 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15530 /* we have fed all the moves, so reactivate analysis mode */
15531 SendToProgram("analyze\n", &first);
15532 first.analyzing = TRUE;
15533 /*first.maybeThinking = TRUE;*/
15534 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15539 BackwardInner (int target)
15541 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15543 if (appData.debugMode)
15544 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15545 target, currentMove, forwardMostMove);
15547 if (gameMode == EditPosition) return;
15548 seekGraphUp = FALSE;
15549 MarkTargetSquares(1);
15550 if (currentMove <= backwardMostMove) {
15552 DrawPosition(full_redraw, boards[currentMove]);
15555 if (gameMode == PlayFromGameFile && !pausing)
15558 if (moveList[target][0]) {
15559 int fromX, fromY, toX, toY;
15560 toX = moveList[target][2] - AAA;
15561 toY = moveList[target][3] - ONE;
15562 if (moveList[target][1] == '@') {
15563 if (appData.highlightLastMove) {
15564 SetHighlights(-1, -1, toX, toY);
15567 fromX = moveList[target][0] - AAA;
15568 fromY = moveList[target][1] - ONE;
15569 if (target == currentMove - 1) {
15570 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15572 if (appData.highlightLastMove) {
15573 SetHighlights(fromX, fromY, toX, toY);
15577 if (gameMode == EditGame || gameMode==AnalyzeMode ||
15578 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15579 while (currentMove > target) {
15580 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15581 // null move cannot be undone. Reload program with move history before it.
15583 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15584 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15586 SendBoard(&first, i);
15587 if(second.analyzing) SendBoard(&second, i);
15588 for(currentMove=i; currentMove<target; currentMove++) {
15589 SendMoveToProgram(currentMove, &first);
15590 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15594 SendToBoth("undo\n");
15598 currentMove = target;
15601 if (gameMode == EditGame || gameMode == EndOfGame) {
15602 whiteTimeRemaining = timeRemaining[0][currentMove];
15603 blackTimeRemaining = timeRemaining[1][currentMove];
15605 DisplayBothClocks();
15606 DisplayMove(currentMove - 1);
15607 DrawPosition(full_redraw, boards[currentMove]);
15608 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15609 // [HGM] PV info: routine tests if comment empty
15610 DisplayComment(currentMove - 1, commentList[currentMove]);
15611 ClearMap(); // [HGM] exclude: invalidate map
15617 if (gameMode == IcsExamining && !pausing) {
15618 SendToICS(ics_prefix);
15619 SendToICS("backward\n");
15621 BackwardInner(currentMove - 1);
15628 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15629 /* to optimize, we temporarily turn off analysis mode while we undo
15630 * all the moves. Otherwise we get analysis output after each undo.
15632 if (first.analysisSupport) {
15633 SendToProgram("exit\nforce\n", &first);
15634 first.analyzing = FALSE;
15638 if (gameMode == IcsExamining && !pausing) {
15639 SendToICS(ics_prefix);
15640 SendToICS("backward 999999\n");
15642 BackwardInner(backwardMostMove);
15645 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15646 /* we have fed all the moves, so reactivate analysis mode */
15647 SendToProgram("analyze\n", &first);
15648 first.analyzing = TRUE;
15649 /*first.maybeThinking = TRUE;*/
15650 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15657 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15658 if (to >= forwardMostMove) to = forwardMostMove;
15659 if (to <= backwardMostMove) to = backwardMostMove;
15660 if (to < currentMove) {
15668 RevertEvent (Boolean annotate)
15670 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15673 if (gameMode != IcsExamining) {
15674 DisplayError(_("You are not examining a game"), 0);
15678 DisplayError(_("You can't revert while pausing"), 0);
15681 SendToICS(ics_prefix);
15682 SendToICS("revert\n");
15686 RetractMoveEvent ()
15688 switch (gameMode) {
15689 case MachinePlaysWhite:
15690 case MachinePlaysBlack:
15691 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15692 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15695 if (forwardMostMove < 2) return;
15696 currentMove = forwardMostMove = forwardMostMove - 2;
15697 whiteTimeRemaining = timeRemaining[0][currentMove];
15698 blackTimeRemaining = timeRemaining[1][currentMove];
15699 DisplayBothClocks();
15700 DisplayMove(currentMove - 1);
15701 ClearHighlights();/*!! could figure this out*/
15702 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15703 SendToProgram("remove\n", &first);
15704 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15707 case BeginningOfGame:
15711 case IcsPlayingWhite:
15712 case IcsPlayingBlack:
15713 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15714 SendToICS(ics_prefix);
15715 SendToICS("takeback 2\n");
15717 SendToICS(ics_prefix);
15718 SendToICS("takeback 1\n");
15727 ChessProgramState *cps;
15729 switch (gameMode) {
15730 case MachinePlaysWhite:
15731 if (!WhiteOnMove(forwardMostMove)) {
15732 DisplayError(_("It is your turn"), 0);
15737 case MachinePlaysBlack:
15738 if (WhiteOnMove(forwardMostMove)) {
15739 DisplayError(_("It is your turn"), 0);
15744 case TwoMachinesPlay:
15745 if (WhiteOnMove(forwardMostMove) ==
15746 (first.twoMachinesColor[0] == 'w')) {
15752 case BeginningOfGame:
15756 SendToProgram("?\n", cps);
15760 TruncateGameEvent ()
15763 if (gameMode != EditGame) return;
15770 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15771 if (forwardMostMove > currentMove) {
15772 if (gameInfo.resultDetails != NULL) {
15773 free(gameInfo.resultDetails);
15774 gameInfo.resultDetails = NULL;
15775 gameInfo.result = GameUnfinished;
15777 forwardMostMove = currentMove;
15778 HistorySet(parseList, backwardMostMove, forwardMostMove,
15786 if (appData.noChessProgram) return;
15787 switch (gameMode) {
15788 case MachinePlaysWhite:
15789 if (WhiteOnMove(forwardMostMove)) {
15790 DisplayError(_("Wait until your turn."), 0);
15794 case BeginningOfGame:
15795 case MachinePlaysBlack:
15796 if (!WhiteOnMove(forwardMostMove)) {
15797 DisplayError(_("Wait until your turn."), 0);
15802 DisplayError(_("No hint available"), 0);
15805 SendToProgram("hint\n", &first);
15806 hintRequested = TRUE;
15812 ListGame * lg = (ListGame *) gameList.head;
15815 static int secondTime = FALSE;
15817 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15818 DisplayError(_("Game list not loaded or empty"), 0);
15822 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15825 DisplayNote(_("Book file exists! Try again for overwrite."));
15829 creatingBook = TRUE;
15830 secondTime = FALSE;
15832 /* Get list size */
15833 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15834 LoadGame(f, nItem, "", TRUE);
15835 AddGameToBook(TRUE);
15836 lg = (ListGame *) lg->node.succ;
15839 creatingBook = FALSE;
15846 if (appData.noChessProgram) return;
15847 switch (gameMode) {
15848 case MachinePlaysWhite:
15849 if (WhiteOnMove(forwardMostMove)) {
15850 DisplayError(_("Wait until your turn."), 0);
15854 case BeginningOfGame:
15855 case MachinePlaysBlack:
15856 if (!WhiteOnMove(forwardMostMove)) {
15857 DisplayError(_("Wait until your turn."), 0);
15862 EditPositionDone(TRUE);
15864 case TwoMachinesPlay:
15869 SendToProgram("bk\n", &first);
15870 bookOutput[0] = NULLCHAR;
15871 bookRequested = TRUE;
15877 char *tags = PGNTags(&gameInfo);
15878 TagsPopUp(tags, CmailMsg());
15882 /* end button procedures */
15885 PrintPosition (FILE *fp, int move)
15889 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15890 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15891 char c = PieceToChar(boards[move][i][j]);
15892 fputc(c == 'x' ? '.' : c, fp);
15893 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15896 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15897 fprintf(fp, "white to play\n");
15899 fprintf(fp, "black to play\n");
15903 PrintOpponents (FILE *fp)
15905 if (gameInfo.white != NULL) {
15906 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15912 /* Find last component of program's own name, using some heuristics */
15914 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15917 int local = (strcmp(host, "localhost") == 0);
15918 while (!local && (p = strchr(prog, ';')) != NULL) {
15920 while (*p == ' ') p++;
15923 if (*prog == '"' || *prog == '\'') {
15924 q = strchr(prog + 1, *prog);
15926 q = strchr(prog, ' ');
15928 if (q == NULL) q = prog + strlen(prog);
15930 while (p >= prog && *p != '/' && *p != '\\') p--;
15932 if(p == prog && *p == '"') p++;
15934 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15935 memcpy(buf, p, q - p);
15936 buf[q - p] = NULLCHAR;
15944 TimeControlTagValue ()
15947 if (!appData.clockMode) {
15948 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15949 } else if (movesPerSession > 0) {
15950 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15951 } else if (timeIncrement == 0) {
15952 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15954 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15956 return StrSave(buf);
15962 /* This routine is used only for certain modes */
15963 VariantClass v = gameInfo.variant;
15964 ChessMove r = GameUnfinished;
15967 if(keepInfo) return;
15969 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15970 r = gameInfo.result;
15971 p = gameInfo.resultDetails;
15972 gameInfo.resultDetails = NULL;
15974 ClearGameInfo(&gameInfo);
15975 gameInfo.variant = v;
15977 switch (gameMode) {
15978 case MachinePlaysWhite:
15979 gameInfo.event = StrSave( appData.pgnEventHeader );
15980 gameInfo.site = StrSave(HostName());
15981 gameInfo.date = PGNDate();
15982 gameInfo.round = StrSave("-");
15983 gameInfo.white = StrSave(first.tidy);
15984 gameInfo.black = StrSave(UserName());
15985 gameInfo.timeControl = TimeControlTagValue();
15988 case MachinePlaysBlack:
15989 gameInfo.event = StrSave( appData.pgnEventHeader );
15990 gameInfo.site = StrSave(HostName());
15991 gameInfo.date = PGNDate();
15992 gameInfo.round = StrSave("-");
15993 gameInfo.white = StrSave(UserName());
15994 gameInfo.black = StrSave(first.tidy);
15995 gameInfo.timeControl = TimeControlTagValue();
15998 case TwoMachinesPlay:
15999 gameInfo.event = StrSave( appData.pgnEventHeader );
16000 gameInfo.site = StrSave(HostName());
16001 gameInfo.date = PGNDate();
16004 snprintf(buf, MSG_SIZ, "%d", roundNr);
16005 gameInfo.round = StrSave(buf);
16007 gameInfo.round = StrSave("-");
16009 if (first.twoMachinesColor[0] == 'w') {
16010 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16011 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16013 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16014 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16016 gameInfo.timeControl = TimeControlTagValue();
16020 gameInfo.event = StrSave("Edited game");
16021 gameInfo.site = StrSave(HostName());
16022 gameInfo.date = PGNDate();
16023 gameInfo.round = StrSave("-");
16024 gameInfo.white = StrSave("-");
16025 gameInfo.black = StrSave("-");
16026 gameInfo.result = r;
16027 gameInfo.resultDetails = p;
16031 gameInfo.event = StrSave("Edited position");
16032 gameInfo.site = StrSave(HostName());
16033 gameInfo.date = PGNDate();
16034 gameInfo.round = StrSave("-");
16035 gameInfo.white = StrSave("-");
16036 gameInfo.black = StrSave("-");
16039 case IcsPlayingWhite:
16040 case IcsPlayingBlack:
16045 case PlayFromGameFile:
16046 gameInfo.event = StrSave("Game from non-PGN file");
16047 gameInfo.site = StrSave(HostName());
16048 gameInfo.date = PGNDate();
16049 gameInfo.round = StrSave("-");
16050 gameInfo.white = StrSave("?");
16051 gameInfo.black = StrSave("?");
16060 ReplaceComment (int index, char *text)
16066 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16067 pvInfoList[index-1].depth == len &&
16068 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16069 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16070 while (*text == '\n') text++;
16071 len = strlen(text);
16072 while (len > 0 && text[len - 1] == '\n') len--;
16074 if (commentList[index] != NULL)
16075 free(commentList[index]);
16078 commentList[index] = NULL;
16081 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16082 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16083 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16084 commentList[index] = (char *) malloc(len + 2);
16085 strncpy(commentList[index], text, len);
16086 commentList[index][len] = '\n';
16087 commentList[index][len + 1] = NULLCHAR;
16089 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16091 commentList[index] = (char *) malloc(len + 7);
16092 safeStrCpy(commentList[index], "{\n", 3);
16093 safeStrCpy(commentList[index]+2, text, len+1);
16094 commentList[index][len+2] = NULLCHAR;
16095 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16096 strcat(commentList[index], "\n}\n");
16101 CrushCRs (char *text)
16109 if (ch == '\r') continue;
16111 } while (ch != '\0');
16115 AppendComment (int index, char *text, Boolean addBraces)
16116 /* addBraces tells if we should add {} */
16121 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16122 if(addBraces == 3) addBraces = 0; else // force appending literally
16123 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16126 while (*text == '\n') text++;
16127 len = strlen(text);
16128 while (len > 0 && text[len - 1] == '\n') len--;
16129 text[len] = NULLCHAR;
16131 if (len == 0) return;
16133 if (commentList[index] != NULL) {
16134 Boolean addClosingBrace = addBraces;
16135 old = commentList[index];
16136 oldlen = strlen(old);
16137 while(commentList[index][oldlen-1] == '\n')
16138 commentList[index][--oldlen] = NULLCHAR;
16139 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16140 safeStrCpy(commentList[index], old, oldlen + len + 6);
16142 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16143 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16144 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16145 while (*text == '\n') { text++; len--; }
16146 commentList[index][--oldlen] = NULLCHAR;
16148 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16149 else strcat(commentList[index], "\n");
16150 strcat(commentList[index], text);
16151 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16152 else strcat(commentList[index], "\n");
16154 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16156 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16157 else commentList[index][0] = NULLCHAR;
16158 strcat(commentList[index], text);
16159 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16160 if(addBraces == TRUE) strcat(commentList[index], "}\n");
16165 FindStr (char * text, char * sub_text)
16167 char * result = strstr( text, sub_text );
16169 if( result != NULL ) {
16170 result += strlen( sub_text );
16176 /* [AS] Try to extract PV info from PGN comment */
16177 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16179 GetInfoFromComment (int index, char * text)
16181 char * sep = text, *p;
16183 if( text != NULL && index > 0 ) {
16186 int time = -1, sec = 0, deci;
16187 char * s_eval = FindStr( text, "[%eval " );
16188 char * s_emt = FindStr( text, "[%emt " );
16190 if( s_eval != NULL || s_emt != NULL ) {
16192 if(0) { // [HGM] this code is not finished, and could actually be detrimental
16197 if( s_eval != NULL ) {
16198 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16202 if( delim != ']' ) {
16207 if( s_emt != NULL ) {
16212 /* We expect something like: [+|-]nnn.nn/dd */
16215 if(*text != '{') return text; // [HGM] braces: must be normal comment
16217 sep = strchr( text, '/' );
16218 if( sep == NULL || sep < (text+4) ) {
16223 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16224 if(p[1] == '(') { // comment starts with PV
16225 p = strchr(p, ')'); // locate end of PV
16226 if(p == NULL || sep < p+5) return text;
16227 // at this point we have something like "{(.*) +0.23/6 ..."
16228 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16229 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16230 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16232 time = -1; sec = -1; deci = -1;
16233 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16234 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16235 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16236 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
16240 if( score_lo < 0 || score_lo >= 100 ) {
16244 if(sec >= 0) time = 600*time + 10*sec; else
16245 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16247 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16249 /* [HGM] PV time: now locate end of PV info */
16250 while( *++sep >= '0' && *sep <= '9'); // strip depth
16252 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16254 while( *++sep >= '0' && *sep <= '9'); // strip seconds
16256 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16257 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16268 pvInfoList[index-1].depth = depth;
16269 pvInfoList[index-1].score = score;
16270 pvInfoList[index-1].time = 10*time; // centi-sec
16271 if(*sep == '}') *sep = 0; else *--sep = '{';
16272 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16278 SendToProgram (char *message, ChessProgramState *cps)
16280 int count, outCount, error;
16283 if (cps->pr == NoProc) return;
16286 if (appData.debugMode) {
16289 fprintf(debugFP, "%ld >%-6s: %s",
16290 SubtractTimeMarks(&now, &programStartTime),
16291 cps->which, message);
16293 fprintf(serverFP, "%ld >%-6s: %s",
16294 SubtractTimeMarks(&now, &programStartTime),
16295 cps->which, message), fflush(serverFP);
16298 count = strlen(message);
16299 outCount = OutputToProcess(cps->pr, message, count, &error);
16300 if (outCount < count && !exiting
16301 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16302 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16303 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16304 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16305 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16306 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16307 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16308 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16310 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16311 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16312 gameInfo.result = res;
16314 gameInfo.resultDetails = StrSave(buf);
16316 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16317 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16322 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16326 ChessProgramState *cps = (ChessProgramState *)closure;
16328 if (isr != cps->isr) return; /* Killed intentionally */
16331 RemoveInputSource(cps->isr);
16332 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16333 _(cps->which), cps->program);
16334 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16335 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16336 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16337 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16338 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16339 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16341 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16342 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16343 gameInfo.result = res;
16345 gameInfo.resultDetails = StrSave(buf);
16347 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16348 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16350 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16351 _(cps->which), cps->program);
16352 RemoveInputSource(cps->isr);
16354 /* [AS] Program is misbehaving badly... kill it */
16355 if( count == -2 ) {
16356 DestroyChildProcess( cps->pr, 9 );
16360 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16365 if ((end_str = strchr(message, '\r')) != NULL)
16366 *end_str = NULLCHAR;
16367 if ((end_str = strchr(message, '\n')) != NULL)
16368 *end_str = NULLCHAR;
16370 if (appData.debugMode) {
16371 TimeMark now; int print = 1;
16372 char *quote = ""; char c; int i;
16374 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16375 char start = message[0];
16376 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16377 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16378 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
16379 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16380 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16381 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
16382 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16383 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
16384 sscanf(message, "hint: %c", &c)!=1 &&
16385 sscanf(message, "pong %c", &c)!=1 && start != '#') {
16386 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16387 print = (appData.engineComments >= 2);
16389 message[0] = start; // restore original message
16393 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16394 SubtractTimeMarks(&now, &programStartTime), cps->which,
16398 fprintf(serverFP, "%ld <%-6s: %s%s\n",
16399 SubtractTimeMarks(&now, &programStartTime), cps->which,
16401 message), fflush(serverFP);
16405 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16406 if (appData.icsEngineAnalyze) {
16407 if (strstr(message, "whisper") != NULL ||
16408 strstr(message, "kibitz") != NULL ||
16409 strstr(message, "tellics") != NULL) return;
16412 HandleMachineMove(message, cps);
16417 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16422 if( timeControl_2 > 0 ) {
16423 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16424 tc = timeControl_2;
16427 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16428 inc /= cps->timeOdds;
16429 st /= cps->timeOdds;
16431 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16434 /* Set exact time per move, normally using st command */
16435 if (cps->stKludge) {
16436 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16438 if (seconds == 0) {
16439 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16441 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16444 snprintf(buf, MSG_SIZ, "st %d\n", st);
16447 /* Set conventional or incremental time control, using level command */
16448 if (seconds == 0) {
16449 /* Note old gnuchess bug -- minutes:seconds used to not work.
16450 Fixed in later versions, but still avoid :seconds
16451 when seconds is 0. */
16452 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16454 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16455 seconds, inc/1000.);
16458 SendToProgram(buf, cps);
16460 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16461 /* Orthogonally, limit search to given depth */
16463 if (cps->sdKludge) {
16464 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16466 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16468 SendToProgram(buf, cps);
16471 if(cps->nps >= 0) { /* [HGM] nps */
16472 if(cps->supportsNPS == FALSE)
16473 cps->nps = -1; // don't use if engine explicitly says not supported!
16475 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16476 SendToProgram(buf, cps);
16481 ChessProgramState *
16483 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16485 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16486 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16492 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16494 char message[MSG_SIZ];
16497 /* Note: this routine must be called when the clocks are stopped
16498 or when they have *just* been set or switched; otherwise
16499 it will be off by the time since the current tick started.
16501 if (machineWhite) {
16502 time = whiteTimeRemaining / 10;
16503 otime = blackTimeRemaining / 10;
16505 time = blackTimeRemaining / 10;
16506 otime = whiteTimeRemaining / 10;
16508 /* [HGM] translate opponent's time by time-odds factor */
16509 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16511 if (time <= 0) time = 1;
16512 if (otime <= 0) otime = 1;
16514 snprintf(message, MSG_SIZ, "time %ld\n", time);
16515 SendToProgram(message, cps);
16517 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16518 SendToProgram(message, cps);
16522 EngineDefinedVariant (ChessProgramState *cps, int n)
16523 { // return name of n-th unknown variant that engine supports
16524 static char buf[MSG_SIZ];
16525 char *p, *s = cps->variants;
16526 if(!s) return NULL;
16527 do { // parse string from variants feature
16529 p = strchr(s, ',');
16530 if(p) *p = NULLCHAR;
16531 v = StringToVariant(s);
16532 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16533 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16534 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16537 if(n < 0) return buf;
16543 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16546 int len = strlen(name);
16549 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16551 sscanf(*p, "%d", &val);
16553 while (**p && **p != ' ')
16555 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16556 SendToProgram(buf, cps);
16563 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16566 int len = strlen(name);
16567 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16569 sscanf(*p, "%d", loc);
16570 while (**p && **p != ' ') (*p)++;
16571 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16572 SendToProgram(buf, cps);
16579 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16582 int len = strlen(name);
16583 if (strncmp((*p), name, len) == 0
16584 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16586 ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16587 sscanf(*p, "%[^\"]", *loc);
16588 while (**p && **p != '\"') (*p)++;
16589 if (**p == '\"') (*p)++;
16590 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16591 SendToProgram(buf, cps);
16598 ParseOption (Option *opt, ChessProgramState *cps)
16599 // [HGM] options: process the string that defines an engine option, and determine
16600 // name, type, default value, and allowed value range
16602 char *p, *q, buf[MSG_SIZ];
16603 int n, min = (-1)<<31, max = 1<<31, def;
16605 if(p = strstr(opt->name, " -spin ")) {
16606 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16607 if(max < min) max = min; // enforce consistency
16608 if(def < min) def = min;
16609 if(def > max) def = max;
16614 } else if((p = strstr(opt->name, " -slider "))) {
16615 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16616 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16617 if(max < min) max = min; // enforce consistency
16618 if(def < min) def = min;
16619 if(def > max) def = max;
16623 opt->type = Spin; // Slider;
16624 } else if((p = strstr(opt->name, " -string "))) {
16625 opt->textValue = p+9;
16626 opt->type = TextBox;
16627 } else if((p = strstr(opt->name, " -file "))) {
16628 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16629 opt->textValue = p+7;
16630 opt->type = FileName; // FileName;
16631 } else if((p = strstr(opt->name, " -path "))) {
16632 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16633 opt->textValue = p+7;
16634 opt->type = PathName; // PathName;
16635 } else if(p = strstr(opt->name, " -check ")) {
16636 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16637 opt->value = (def != 0);
16638 opt->type = CheckBox;
16639 } else if(p = strstr(opt->name, " -combo ")) {
16640 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16641 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16642 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16643 opt->value = n = 0;
16644 while(q = StrStr(q, " /// ")) {
16645 n++; *q = 0; // count choices, and null-terminate each of them
16647 if(*q == '*') { // remember default, which is marked with * prefix
16651 cps->comboList[cps->comboCnt++] = q;
16653 cps->comboList[cps->comboCnt++] = NULL;
16655 opt->type = ComboBox;
16656 } else if(p = strstr(opt->name, " -button")) {
16657 opt->type = Button;
16658 } else if(p = strstr(opt->name, " -save")) {
16659 opt->type = SaveButton;
16660 } else return FALSE;
16661 *p = 0; // terminate option name
16662 // now look if the command-line options define a setting for this engine option.
16663 if(cps->optionSettings && cps->optionSettings[0])
16664 p = strstr(cps->optionSettings, opt->name); else p = NULL;
16665 if(p && (p == cps->optionSettings || p[-1] == ',')) {
16666 snprintf(buf, MSG_SIZ, "option %s", p);
16667 if(p = strstr(buf, ",")) *p = 0;
16668 if(q = strchr(buf, '=')) switch(opt->type) {
16670 for(n=0; n<opt->max; n++)
16671 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16674 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16678 opt->value = atoi(q+1);
16683 SendToProgram(buf, cps);
16689 FeatureDone (ChessProgramState *cps, int val)
16691 DelayedEventCallback cb = GetDelayedEvent();
16692 if ((cb == InitBackEnd3 && cps == &first) ||
16693 (cb == SettingsMenuIfReady && cps == &second) ||
16694 (cb == LoadEngine) ||
16695 (cb == TwoMachinesEventIfReady)) {
16696 CancelDelayedEvent();
16697 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16699 cps->initDone = val;
16700 if(val) cps->reload = FALSE;
16703 /* Parse feature command from engine */
16705 ParseFeatures (char *args, ChessProgramState *cps)
16713 while (*p == ' ') p++;
16714 if (*p == NULLCHAR) return;
16716 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16717 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16718 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16719 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16720 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16721 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16722 if (BoolFeature(&p, "reuse", &val, cps)) {
16723 /* Engine can disable reuse, but can't enable it if user said no */
16724 if (!val) cps->reuse = FALSE;
16727 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16728 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16729 if (gameMode == TwoMachinesPlay) {
16730 DisplayTwoMachinesTitle();
16736 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16737 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16738 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16739 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16740 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16741 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16742 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16743 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16744 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16745 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16746 if (IntFeature(&p, "done", &val, cps)) {
16747 FeatureDone(cps, val);
16750 /* Added by Tord: */
16751 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16752 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16753 /* End of additions by Tord */
16755 /* [HGM] added features: */
16756 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16757 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16758 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16759 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16760 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16761 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16762 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16763 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16764 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16765 FREE(cps->option[cps->nrOptions].name);
16766 cps->option[cps->nrOptions].name = q; q = NULL;
16767 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16768 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16769 SendToProgram(buf, cps);
16772 if(cps->nrOptions >= MAX_OPTIONS) {
16774 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16775 DisplayError(buf, 0);
16779 /* End of additions by HGM */
16781 /* unknown feature: complain and skip */
16783 while (*q && *q != '=') q++;
16784 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16785 SendToProgram(buf, cps);
16791 while (*p && *p != '\"') p++;
16792 if (*p == '\"') p++;
16794 while (*p && *p != ' ') p++;
16802 PeriodicUpdatesEvent (int newState)
16804 if (newState == appData.periodicUpdates)
16807 appData.periodicUpdates=newState;
16809 /* Display type changes, so update it now */
16810 // DisplayAnalysis();
16812 /* Get the ball rolling again... */
16814 AnalysisPeriodicEvent(1);
16815 StartAnalysisClock();
16820 PonderNextMoveEvent (int newState)
16822 if (newState == appData.ponderNextMove) return;
16823 if (gameMode == EditPosition) EditPositionDone(TRUE);
16825 SendToProgram("hard\n", &first);
16826 if (gameMode == TwoMachinesPlay) {
16827 SendToProgram("hard\n", &second);
16830 SendToProgram("easy\n", &first);
16831 thinkOutput[0] = NULLCHAR;
16832 if (gameMode == TwoMachinesPlay) {
16833 SendToProgram("easy\n", &second);
16836 appData.ponderNextMove = newState;
16840 NewSettingEvent (int option, int *feature, char *command, int value)
16844 if (gameMode == EditPosition) EditPositionDone(TRUE);
16845 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16846 if(feature == NULL || *feature) SendToProgram(buf, &first);
16847 if (gameMode == TwoMachinesPlay) {
16848 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16853 ShowThinkingEvent ()
16854 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16856 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16857 int newState = appData.showThinking
16858 // [HGM] thinking: other features now need thinking output as well
16859 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16861 if (oldState == newState) return;
16862 oldState = newState;
16863 if (gameMode == EditPosition) EditPositionDone(TRUE);
16865 SendToProgram("post\n", &first);
16866 if (gameMode == TwoMachinesPlay) {
16867 SendToProgram("post\n", &second);
16870 SendToProgram("nopost\n", &first);
16871 thinkOutput[0] = NULLCHAR;
16872 if (gameMode == TwoMachinesPlay) {
16873 SendToProgram("nopost\n", &second);
16876 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16880 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16882 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16883 if (pr == NoProc) return;
16884 AskQuestion(title, question, replyPrefix, pr);
16888 TypeInEvent (char firstChar)
16890 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16891 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16892 gameMode == AnalyzeMode || gameMode == EditGame ||
16893 gameMode == EditPosition || gameMode == IcsExamining ||
16894 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16895 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16896 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16897 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
16898 gameMode == Training) PopUpMoveDialog(firstChar);
16902 TypeInDoneEvent (char *move)
16905 int n, fromX, fromY, toX, toY;
16907 ChessMove moveType;
16910 if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16911 EditPositionPasteFEN(move);
16914 // [HGM] movenum: allow move number to be typed in any mode
16915 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16919 // undocumented kludge: allow command-line option to be typed in!
16920 // (potentially fatal, and does not implement the effect of the option.)
16921 // should only be used for options that are values on which future decisions will be made,
16922 // and definitely not on options that would be used during initialization.
16923 if(strstr(move, "!!! -") == move) {
16924 ParseArgsFromString(move+4);
16928 if (gameMode != EditGame && currentMove != forwardMostMove &&
16929 gameMode != Training) {
16930 DisplayMoveError(_("Displayed move is not current"));
16932 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16933 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16934 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16935 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16936 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16937 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16939 DisplayMoveError(_("Could not parse move"));
16945 DisplayMove (int moveNumber)
16947 char message[MSG_SIZ];
16949 char cpThinkOutput[MSG_SIZ];
16951 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16953 if (moveNumber == forwardMostMove - 1 ||
16954 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16956 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16958 if (strchr(cpThinkOutput, '\n')) {
16959 *strchr(cpThinkOutput, '\n') = NULLCHAR;
16962 *cpThinkOutput = NULLCHAR;
16965 /* [AS] Hide thinking from human user */
16966 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16967 *cpThinkOutput = NULLCHAR;
16968 if( thinkOutput[0] != NULLCHAR ) {
16971 for( i=0; i<=hiddenThinkOutputState; i++ ) {
16972 cpThinkOutput[i] = '.';
16974 cpThinkOutput[i] = NULLCHAR;
16975 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16979 if (moveNumber == forwardMostMove - 1 &&
16980 gameInfo.resultDetails != NULL) {
16981 if (gameInfo.resultDetails[0] == NULLCHAR) {
16982 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16984 snprintf(res, MSG_SIZ, " {%s} %s",
16985 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16991 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16992 DisplayMessage(res, cpThinkOutput);
16994 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16995 WhiteOnMove(moveNumber) ? " " : ".. ",
16996 parseList[moveNumber], res);
16997 DisplayMessage(message, cpThinkOutput);
17002 DisplayComment (int moveNumber, char *text)
17004 char title[MSG_SIZ];
17006 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17007 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17009 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17010 WhiteOnMove(moveNumber) ? " " : ".. ",
17011 parseList[moveNumber]);
17013 if (text != NULL && (appData.autoDisplayComment || commentUp))
17014 CommentPopUp(title, text);
17017 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17018 * might be busy thinking or pondering. It can be omitted if your
17019 * gnuchess is configured to stop thinking immediately on any user
17020 * input. However, that gnuchess feature depends on the FIONREAD
17021 * ioctl, which does not work properly on some flavors of Unix.
17024 Attention (ChessProgramState *cps)
17027 if (!cps->useSigint) return;
17028 if (appData.noChessProgram || (cps->pr == NoProc)) return;
17029 switch (gameMode) {
17030 case MachinePlaysWhite:
17031 case MachinePlaysBlack:
17032 case TwoMachinesPlay:
17033 case IcsPlayingWhite:
17034 case IcsPlayingBlack:
17037 /* Skip if we know it isn't thinking */
17038 if (!cps->maybeThinking) return;
17039 if (appData.debugMode)
17040 fprintf(debugFP, "Interrupting %s\n", cps->which);
17041 InterruptChildProcess(cps->pr);
17042 cps->maybeThinking = FALSE;
17047 #endif /*ATTENTION*/
17053 if (whiteTimeRemaining <= 0) {
17056 if (appData.icsActive) {
17057 if (appData.autoCallFlag &&
17058 gameMode == IcsPlayingBlack && !blackFlag) {
17059 SendToICS(ics_prefix);
17060 SendToICS("flag\n");
17064 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17066 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17067 if (appData.autoCallFlag) {
17068 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17075 if (blackTimeRemaining <= 0) {
17078 if (appData.icsActive) {
17079 if (appData.autoCallFlag &&
17080 gameMode == IcsPlayingWhite && !whiteFlag) {
17081 SendToICS(ics_prefix);
17082 SendToICS("flag\n");
17086 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17088 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17089 if (appData.autoCallFlag) {
17090 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17101 CheckTimeControl ()
17103 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17104 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17107 * add time to clocks when time control is achieved ([HGM] now also used for increment)
17109 if ( !WhiteOnMove(forwardMostMove) ) {
17110 /* White made time control */
17111 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17112 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17113 /* [HGM] time odds: correct new time quota for time odds! */
17114 / WhitePlayer()->timeOdds;
17115 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17117 lastBlack -= blackTimeRemaining;
17118 /* Black made time control */
17119 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17120 / WhitePlayer()->other->timeOdds;
17121 lastWhite = whiteTimeRemaining;
17126 DisplayBothClocks ()
17128 int wom = gameMode == EditPosition ?
17129 !blackPlaysFirst : WhiteOnMove(currentMove);
17130 DisplayWhiteClock(whiteTimeRemaining, wom);
17131 DisplayBlackClock(blackTimeRemaining, !wom);
17135 /* Timekeeping seems to be a portability nightmare. I think everyone
17136 has ftime(), but I'm really not sure, so I'm including some ifdefs
17137 to use other calls if you don't. Clocks will be less accurate if
17138 you have neither ftime nor gettimeofday.
17141 /* VS 2008 requires the #include outside of the function */
17142 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17143 #include <sys/timeb.h>
17146 /* Get the current time as a TimeMark */
17148 GetTimeMark (TimeMark *tm)
17150 #if HAVE_GETTIMEOFDAY
17152 struct timeval timeVal;
17153 struct timezone timeZone;
17155 gettimeofday(&timeVal, &timeZone);
17156 tm->sec = (long) timeVal.tv_sec;
17157 tm->ms = (int) (timeVal.tv_usec / 1000L);
17159 #else /*!HAVE_GETTIMEOFDAY*/
17162 // include <sys/timeb.h> / moved to just above start of function
17163 struct timeb timeB;
17166 tm->sec = (long) timeB.time;
17167 tm->ms = (int) timeB.millitm;
17169 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17170 tm->sec = (long) time(NULL);
17176 /* Return the difference in milliseconds between two
17177 time marks. We assume the difference will fit in a long!
17180 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17182 return 1000L*(tm2->sec - tm1->sec) +
17183 (long) (tm2->ms - tm1->ms);
17188 * Code to manage the game clocks.
17190 * In tournament play, black starts the clock and then white makes a move.
17191 * We give the human user a slight advantage if he is playing white---the
17192 * clocks don't run until he makes his first move, so it takes zero time.
17193 * Also, we don't account for network lag, so we could get out of sync
17194 * with GNU Chess's clock -- but then, referees are always right.
17197 static TimeMark tickStartTM;
17198 static long intendedTickLength;
17201 NextTickLength (long timeRemaining)
17203 long nominalTickLength, nextTickLength;
17205 if (timeRemaining > 0L && timeRemaining <= 10000L)
17206 nominalTickLength = 100L;
17208 nominalTickLength = 1000L;
17209 nextTickLength = timeRemaining % nominalTickLength;
17210 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17212 return nextTickLength;
17215 /* Adjust clock one minute up or down */
17217 AdjustClock (Boolean which, int dir)
17219 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17220 if(which) blackTimeRemaining += 60000*dir;
17221 else whiteTimeRemaining += 60000*dir;
17222 DisplayBothClocks();
17223 adjustedClock = TRUE;
17226 /* Stop clocks and reset to a fresh time control */
17230 (void) StopClockTimer();
17231 if (appData.icsActive) {
17232 whiteTimeRemaining = blackTimeRemaining = 0;
17233 } else if (searchTime) {
17234 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17235 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17236 } else { /* [HGM] correct new time quote for time odds */
17237 whiteTC = blackTC = fullTimeControlString;
17238 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17239 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17241 if (whiteFlag || blackFlag) {
17243 whiteFlag = blackFlag = FALSE;
17245 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17246 DisplayBothClocks();
17247 adjustedClock = FALSE;
17250 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17252 /* Decrement running clock by amount of time that has passed */
17256 long timeRemaining;
17257 long lastTickLength, fudge;
17260 if (!appData.clockMode) return;
17261 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17265 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17267 /* Fudge if we woke up a little too soon */
17268 fudge = intendedTickLength - lastTickLength;
17269 if (fudge < 0 || fudge > FUDGE) fudge = 0;
17271 if (WhiteOnMove(forwardMostMove)) {
17272 if(whiteNPS >= 0) lastTickLength = 0;
17273 timeRemaining = whiteTimeRemaining -= lastTickLength;
17274 if(timeRemaining < 0 && !appData.icsActive) {
17275 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17276 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17277 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17278 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17281 DisplayWhiteClock(whiteTimeRemaining - fudge,
17282 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17284 if(blackNPS >= 0) lastTickLength = 0;
17285 timeRemaining = blackTimeRemaining -= lastTickLength;
17286 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17287 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17289 blackStartMove = forwardMostMove;
17290 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17293 DisplayBlackClock(blackTimeRemaining - fudge,
17294 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17296 if (CheckFlags()) return;
17298 if(twoBoards) { // count down secondary board's clocks as well
17299 activePartnerTime -= lastTickLength;
17301 if(activePartner == 'W')
17302 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17304 DisplayBlackClock(activePartnerTime, TRUE);
17309 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17310 StartClockTimer(intendedTickLength);
17312 /* if the time remaining has fallen below the alarm threshold, sound the
17313 * alarm. if the alarm has sounded and (due to a takeback or time control
17314 * with increment) the time remaining has increased to a level above the
17315 * threshold, reset the alarm so it can sound again.
17318 if (appData.icsActive && appData.icsAlarm) {
17320 /* make sure we are dealing with the user's clock */
17321 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17322 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17325 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17326 alarmSounded = FALSE;
17327 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17329 alarmSounded = TRUE;
17335 /* A player has just moved, so stop the previously running
17336 clock and (if in clock mode) start the other one.
17337 We redisplay both clocks in case we're in ICS mode, because
17338 ICS gives us an update to both clocks after every move.
17339 Note that this routine is called *after* forwardMostMove
17340 is updated, so the last fractional tick must be subtracted
17341 from the color that is *not* on move now.
17344 SwitchClocks (int newMoveNr)
17346 long lastTickLength;
17348 int flagged = FALSE;
17352 if (StopClockTimer() && appData.clockMode) {
17353 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17354 if (!WhiteOnMove(forwardMostMove)) {
17355 if(blackNPS >= 0) lastTickLength = 0;
17356 blackTimeRemaining -= lastTickLength;
17357 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17358 // if(pvInfoList[forwardMostMove].time == -1)
17359 pvInfoList[forwardMostMove].time = // use GUI time
17360 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17362 if(whiteNPS >= 0) lastTickLength = 0;
17363 whiteTimeRemaining -= lastTickLength;
17364 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17365 // if(pvInfoList[forwardMostMove].time == -1)
17366 pvInfoList[forwardMostMove].time =
17367 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17369 flagged = CheckFlags();
17371 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17372 CheckTimeControl();
17374 if (flagged || !appData.clockMode) return;
17376 switch (gameMode) {
17377 case MachinePlaysBlack:
17378 case MachinePlaysWhite:
17379 case BeginningOfGame:
17380 if (pausing) return;
17384 case PlayFromGameFile:
17392 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17393 if(WhiteOnMove(forwardMostMove))
17394 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17395 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17399 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17400 whiteTimeRemaining : blackTimeRemaining);
17401 StartClockTimer(intendedTickLength);
17405 /* Stop both clocks */
17409 long lastTickLength;
17412 if (!StopClockTimer()) return;
17413 if (!appData.clockMode) return;
17417 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17418 if (WhiteOnMove(forwardMostMove)) {
17419 if(whiteNPS >= 0) lastTickLength = 0;
17420 whiteTimeRemaining -= lastTickLength;
17421 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17423 if(blackNPS >= 0) lastTickLength = 0;
17424 blackTimeRemaining -= lastTickLength;
17425 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17430 /* Start clock of player on move. Time may have been reset, so
17431 if clock is already running, stop and restart it. */
17435 (void) StopClockTimer(); /* in case it was running already */
17436 DisplayBothClocks();
17437 if (CheckFlags()) return;
17439 if (!appData.clockMode) return;
17440 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17442 GetTimeMark(&tickStartTM);
17443 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17444 whiteTimeRemaining : blackTimeRemaining);
17446 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17447 whiteNPS = blackNPS = -1;
17448 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17449 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17450 whiteNPS = first.nps;
17451 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17452 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17453 blackNPS = first.nps;
17454 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17455 whiteNPS = second.nps;
17456 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17457 blackNPS = second.nps;
17458 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17460 StartClockTimer(intendedTickLength);
17464 TimeString (long ms)
17466 long second, minute, hour, day;
17468 static char buf[32];
17470 if (ms > 0 && ms <= 9900) {
17471 /* convert milliseconds to tenths, rounding up */
17472 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17474 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17478 /* convert milliseconds to seconds, rounding up */
17479 /* use floating point to avoid strangeness of integer division
17480 with negative dividends on many machines */
17481 second = (long) floor(((double) (ms + 999L)) / 1000.0);
17488 day = second / (60 * 60 * 24);
17489 second = second % (60 * 60 * 24);
17490 hour = second / (60 * 60);
17491 second = second % (60 * 60);
17492 minute = second / 60;
17493 second = second % 60;
17496 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17497 sign, day, hour, minute, second);
17499 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17501 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17508 * This is necessary because some C libraries aren't ANSI C compliant yet.
17511 StrStr (char *string, char *match)
17515 length = strlen(match);
17517 for (i = strlen(string) - length; i >= 0; i--, string++)
17518 if (!strncmp(match, string, length))
17525 StrCaseStr (char *string, char *match)
17529 length = strlen(match);
17531 for (i = strlen(string) - length; i >= 0; i--, string++) {
17532 for (j = 0; j < length; j++) {
17533 if (ToLower(match[j]) != ToLower(string[j]))
17536 if (j == length) return string;
17544 StrCaseCmp (char *s1, char *s2)
17549 c1 = ToLower(*s1++);
17550 c2 = ToLower(*s2++);
17551 if (c1 > c2) return 1;
17552 if (c1 < c2) return -1;
17553 if (c1 == NULLCHAR) return 0;
17561 return isupper(c) ? tolower(c) : c;
17568 return islower(c) ? toupper(c) : c;
17570 #endif /* !_amigados */
17577 if ((ret = (char *) malloc(strlen(s) + 1)))
17579 safeStrCpy(ret, s, strlen(s)+1);
17585 StrSavePtr (char *s, char **savePtr)
17590 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17591 safeStrCpy(*savePtr, s, strlen(s)+1);
17603 clock = time((time_t *)NULL);
17604 tm = localtime(&clock);
17605 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17606 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17607 return StrSave(buf);
17612 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17614 int i, j, fromX, fromY, toX, toY;
17621 whiteToPlay = (gameMode == EditPosition) ?
17622 !blackPlaysFirst : (move % 2 == 0);
17625 /* Piece placement data */
17626 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17627 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17629 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17630 if (boards[move][i][j] == EmptySquare) {
17632 } else { ChessSquare piece = boards[move][i][j];
17633 if (emptycount > 0) {
17634 if(emptycount<10) /* [HGM] can be >= 10 */
17635 *p++ = '0' + emptycount;
17636 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17639 if(PieceToChar(piece) == '+') {
17640 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17642 piece = (ChessSquare)(CHUDEMOTED piece);
17644 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17646 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17647 p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17652 if (emptycount > 0) {
17653 if(emptycount<10) /* [HGM] can be >= 10 */
17654 *p++ = '0' + emptycount;
17655 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17662 /* [HGM] print Crazyhouse or Shogi holdings */
17663 if( gameInfo.holdingsWidth ) {
17664 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17666 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17667 piece = boards[move][i][BOARD_WIDTH-1];
17668 if( piece != EmptySquare )
17669 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17670 *p++ = PieceToChar(piece);
17672 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17673 piece = boards[move][BOARD_HEIGHT-i-1][0];
17674 if( piece != EmptySquare )
17675 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17676 *p++ = PieceToChar(piece);
17679 if( q == p ) *p++ = '-';
17685 *p++ = whiteToPlay ? 'w' : 'b';
17688 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17689 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17691 if(nrCastlingRights) {
17693 if(appData.fischerCastling) {
17694 /* [HGM] write directly from rights */
17695 if(boards[move][CASTLING][2] != NoRights &&
17696 boards[move][CASTLING][0] != NoRights )
17697 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17698 if(boards[move][CASTLING][2] != NoRights &&
17699 boards[move][CASTLING][1] != NoRights )
17700 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17701 if(boards[move][CASTLING][5] != NoRights &&
17702 boards[move][CASTLING][3] != NoRights )
17703 *p++ = boards[move][CASTLING][3] + AAA;
17704 if(boards[move][CASTLING][5] != NoRights &&
17705 boards[move][CASTLING][4] != NoRights )
17706 *p++ = boards[move][CASTLING][4] + AAA;
17709 /* [HGM] write true castling rights */
17710 if( nrCastlingRights == 6 ) {
17712 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17713 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
17714 q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17715 boards[move][CASTLING][2] != NoRights );
17716 if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17717 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17718 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17719 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17720 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17724 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17725 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
17726 q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17727 boards[move][CASTLING][5] != NoRights );
17728 if(gameInfo.variant == VariantSChess) {
17729 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17730 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17731 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17732 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17737 if (q == p) *p++ = '-'; /* No castling rights */
17741 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17742 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17743 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17744 /* En passant target square */
17745 if (move > backwardMostMove) {
17746 fromX = moveList[move - 1][0] - AAA;
17747 fromY = moveList[move - 1][1] - ONE;
17748 toX = moveList[move - 1][2] - AAA;
17749 toY = moveList[move - 1][3] - ONE;
17750 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17751 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17752 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17754 /* 2-square pawn move just happened */
17756 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17760 } else if(move == backwardMostMove) {
17761 // [HGM] perhaps we should always do it like this, and forget the above?
17762 if((signed char)boards[move][EP_STATUS] >= 0) {
17763 *p++ = boards[move][EP_STATUS] + AAA;
17764 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17776 { int i = 0, j=move;
17778 /* [HGM] find reversible plies */
17779 if (appData.debugMode) { int k;
17780 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17781 for(k=backwardMostMove; k<=forwardMostMove; k++)
17782 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17786 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17787 if( j == backwardMostMove ) i += initialRulePlies;
17788 sprintf(p, "%d ", i);
17789 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17791 /* Fullmove number */
17792 sprintf(p, "%d", (move / 2) + 1);
17793 } else *--p = NULLCHAR;
17795 return StrSave(buf);
17799 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17801 int i, j, k, w=0, subst=0, shuffle=0;
17803 int emptycount, virgin[BOARD_FILES];
17808 /* Piece placement data */
17809 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17812 if (*p == '/' || *p == ' ' || *p == '[' ) {
17814 emptycount = gameInfo.boardWidth - j;
17815 while (emptycount--)
17816 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17817 if (*p == '/') p++;
17818 else if(autoSize) { // we stumbled unexpectedly into end of board
17819 for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17820 for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17822 appData.NrRanks = gameInfo.boardHeight - i; i=0;
17825 #if(BOARD_FILES >= 10)*0
17826 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17827 p++; emptycount=10;
17828 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17829 while (emptycount--)
17830 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17832 } else if (*p == '*') {
17833 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17834 } else if (isdigit(*p)) {
17835 emptycount = *p++ - '0';
17836 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17837 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17838 while (emptycount--)
17839 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17840 } else if (*p == '<') {
17841 if(i == BOARD_HEIGHT-1) shuffle = 1;
17842 else if (i != 0 || !shuffle) return FALSE;
17844 } else if (shuffle && *p == '>') {
17845 p++; // for now ignore closing shuffle range, and assume rank-end
17846 } else if (*p == '?') {
17847 if (j >= gameInfo.boardWidth) return FALSE;
17848 if (i != 0 && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
17849 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
17850 } else if (*p == '+' || isalpha(*p)) {
17851 if (j >= gameInfo.boardWidth) return FALSE;
17853 piece = CharToPiece(*++p);
17854 if(piece == EmptySquare) return FALSE; /* unknown piece */
17855 piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17856 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17857 } else piece = CharToPiece(*p++);
17859 if(piece==EmptySquare) return FALSE; /* unknown piece */
17860 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17861 piece = (ChessSquare) (PROMOTED piece);
17862 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17865 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17871 while (*p == '/' || *p == ' ') p++;
17873 if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17875 /* [HGM] by default clear Crazyhouse holdings, if present */
17876 if(gameInfo.holdingsWidth) {
17877 for(i=0; i<BOARD_HEIGHT; i++) {
17878 board[i][0] = EmptySquare; /* black holdings */
17879 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17880 board[i][1] = (ChessSquare) 0; /* black counts */
17881 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17885 /* [HGM] look for Crazyhouse holdings here */
17886 while(*p==' ') p++;
17887 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17888 int swap=0, wcnt=0, bcnt=0;
17890 if(*p == '<') swap++, p++;
17891 if(*p == '-' ) p++; /* empty holdings */ else {
17892 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17893 /* if we would allow FEN reading to set board size, we would */
17894 /* have to add holdings and shift the board read so far here */
17895 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17897 if((int) piece >= (int) BlackPawn ) {
17898 i = (int)piece - (int)BlackPawn;
17899 i = PieceToNumber((ChessSquare)i);
17900 if( i >= gameInfo.holdingsSize ) return FALSE;
17901 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17902 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
17905 i = (int)piece - (int)WhitePawn;
17906 i = PieceToNumber((ChessSquare)i);
17907 if( i >= gameInfo.holdingsSize ) return FALSE;
17908 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
17909 board[i][BOARD_WIDTH-2]++; /* black holdings */
17913 if(subst) { // substitute back-rank question marks by holdings pieces
17914 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
17915 int k, m, n = bcnt + 1;
17916 if(board[0][j] == ClearBoard) {
17917 if(!wcnt) return FALSE;
17919 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
17920 board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
17921 if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
17925 if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
17926 if(!bcnt) return FALSE;
17927 if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
17928 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
17929 board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
17930 if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
17941 if(subst) return FALSE; // substitution requested, but no holdings
17943 while(*p == ' ') p++;
17947 if(appData.colorNickNames) {
17948 if( c == appData.colorNickNames[0] ) c = 'w'; else
17949 if( c == appData.colorNickNames[1] ) c = 'b';
17953 *blackPlaysFirst = FALSE;
17956 *blackPlaysFirst = TRUE;
17962 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17963 /* return the extra info in global variiables */
17965 /* set defaults in case FEN is incomplete */
17966 board[EP_STATUS] = EP_UNKNOWN;
17967 for(i=0; i<nrCastlingRights; i++ ) {
17968 board[CASTLING][i] =
17969 appData.fischerCastling ? NoRights : initialRights[i];
17970 } /* assume possible unless obviously impossible */
17971 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17972 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17973 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17974 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17975 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17976 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17977 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17978 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17981 while(*p==' ') p++;
17982 if(nrCastlingRights) {
17984 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17985 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17986 /* castling indicator present, so default becomes no castlings */
17987 for(i=0; i<nrCastlingRights; i++ ) {
17988 board[CASTLING][i] = NoRights;
17991 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17992 (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
17993 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17994 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
17995 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17997 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17998 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17999 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
18001 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18002 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18003 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18004 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18005 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18006 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18009 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
18010 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18011 board[CASTLING][2] = whiteKingFile;
18012 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18013 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18014 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18017 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
18018 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18019 board[CASTLING][2] = whiteKingFile;
18020 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18021 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18022 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18025 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
18026 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18027 board[CASTLING][5] = blackKingFile;
18028 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18029 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18030 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18033 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
18034 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18035 board[CASTLING][5] = blackKingFile;
18036 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18037 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18038 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18041 default: /* FRC castlings */
18042 if(c >= 'a') { /* black rights */
18043 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18044 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18045 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18046 if(i == BOARD_RGHT) break;
18047 board[CASTLING][5] = i;
18049 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
18050 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
18052 board[CASTLING][3] = c;
18054 board[CASTLING][4] = c;
18055 } else { /* white rights */
18056 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18057 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18058 if(board[0][i] == WhiteKing) break;
18059 if(i == BOARD_RGHT) break;
18060 board[CASTLING][2] = i;
18061 c -= AAA - 'a' + 'A';
18062 if(board[0][c] >= WhiteKing) break;
18064 board[CASTLING][0] = c;
18066 board[CASTLING][1] = c;
18070 for(i=0; i<nrCastlingRights; i++)
18071 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18072 if(gameInfo.variant == VariantSChess)
18073 for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18074 if(fischer && shuffle) appData.fischerCastling = TRUE;
18075 if (appData.debugMode) {
18076 fprintf(debugFP, "FEN castling rights:");
18077 for(i=0; i<nrCastlingRights; i++)
18078 fprintf(debugFP, " %d", board[CASTLING][i]);
18079 fprintf(debugFP, "\n");
18082 while(*p==' ') p++;
18085 if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18087 /* read e.p. field in games that know e.p. capture */
18088 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
18089 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18090 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18092 p++; board[EP_STATUS] = EP_NONE;
18094 char c = *p++ - AAA;
18096 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18097 if(*p >= '0' && *p <='9') p++;
18098 board[EP_STATUS] = c;
18103 if(sscanf(p, "%d", &i) == 1) {
18104 FENrulePlies = i; /* 50-move ply counter */
18105 /* (The move number is still ignored) */
18112 EditPositionPasteFEN (char *fen)
18115 Board initial_position;
18117 if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18118 DisplayError(_("Bad FEN position in clipboard"), 0);
18121 int savedBlackPlaysFirst = blackPlaysFirst;
18122 EditPositionEvent();
18123 blackPlaysFirst = savedBlackPlaysFirst;
18124 CopyBoard(boards[0], initial_position);
18125 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18126 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18127 DisplayBothClocks();
18128 DrawPosition(FALSE, boards[currentMove]);
18133 static char cseq[12] = "\\ ";
18136 set_cont_sequence (char *new_seq)
18141 // handle bad attempts to set the sequence
18143 return 0; // acceptable error - no debug
18145 len = strlen(new_seq);
18146 ret = (len > 0) && (len < sizeof(cseq));
18148 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18149 else if (appData.debugMode)
18150 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18155 reformat a source message so words don't cross the width boundary. internal
18156 newlines are not removed. returns the wrapped size (no null character unless
18157 included in source message). If dest is NULL, only calculate the size required
18158 for the dest buffer. lp argument indicats line position upon entry, and it's
18159 passed back upon exit.
18162 wrap (char *dest, char *src, int count, int width, int *lp)
18164 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18166 cseq_len = strlen(cseq);
18167 old_line = line = *lp;
18168 ansi = len = clen = 0;
18170 for (i=0; i < count; i++)
18172 if (src[i] == '\033')
18175 // if we hit the width, back up
18176 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18178 // store i & len in case the word is too long
18179 old_i = i, old_len = len;
18181 // find the end of the last word
18182 while (i && src[i] != ' ' && src[i] != '\n')
18188 // word too long? restore i & len before splitting it
18189 if ((old_i-i+clen) >= width)
18196 if (i && src[i-1] == ' ')
18199 if (src[i] != ' ' && src[i] != '\n')
18206 // now append the newline and continuation sequence
18211 strncpy(dest+len, cseq, cseq_len);
18219 dest[len] = src[i];
18223 if (src[i] == '\n')
18228 if (dest && appData.debugMode)
18230 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18231 count, width, line, len, *lp);
18232 show_bytes(debugFP, src, count);
18233 fprintf(debugFP, "\ndest: ");
18234 show_bytes(debugFP, dest, len);
18235 fprintf(debugFP, "\n");
18237 *lp = dest ? line : old_line;
18242 // [HGM] vari: routines for shelving variations
18243 Boolean modeRestore = FALSE;
18246 PushInner (int firstMove, int lastMove)
18248 int i, j, nrMoves = lastMove - firstMove;
18250 // push current tail of game on stack
18251 savedResult[storedGames] = gameInfo.result;
18252 savedDetails[storedGames] = gameInfo.resultDetails;
18253 gameInfo.resultDetails = NULL;
18254 savedFirst[storedGames] = firstMove;
18255 savedLast [storedGames] = lastMove;
18256 savedFramePtr[storedGames] = framePtr;
18257 framePtr -= nrMoves; // reserve space for the boards
18258 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18259 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18260 for(j=0; j<MOVE_LEN; j++)
18261 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18262 for(j=0; j<2*MOVE_LEN; j++)
18263 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18264 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18265 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18266 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18267 pvInfoList[firstMove+i-1].depth = 0;
18268 commentList[framePtr+i] = commentList[firstMove+i];
18269 commentList[firstMove+i] = NULL;
18273 forwardMostMove = firstMove; // truncate game so we can start variation
18277 PushTail (int firstMove, int lastMove)
18279 if(appData.icsActive) { // only in local mode
18280 forwardMostMove = currentMove; // mimic old ICS behavior
18283 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18285 PushInner(firstMove, lastMove);
18286 if(storedGames == 1) GreyRevert(FALSE);
18287 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18291 PopInner (Boolean annotate)
18294 char buf[8000], moveBuf[20];
18296 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18297 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18298 nrMoves = savedLast[storedGames] - currentMove;
18301 if(!WhiteOnMove(currentMove))
18302 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18303 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18304 for(i=currentMove; i<forwardMostMove; i++) {
18306 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18307 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18308 strcat(buf, moveBuf);
18309 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18310 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18314 for(i=1; i<=nrMoves; i++) { // copy last variation back
18315 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18316 for(j=0; j<MOVE_LEN; j++)
18317 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18318 for(j=0; j<2*MOVE_LEN; j++)
18319 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18320 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18321 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18322 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18323 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18324 commentList[currentMove+i] = commentList[framePtr+i];
18325 commentList[framePtr+i] = NULL;
18327 if(annotate) AppendComment(currentMove+1, buf, FALSE);
18328 framePtr = savedFramePtr[storedGames];
18329 gameInfo.result = savedResult[storedGames];
18330 if(gameInfo.resultDetails != NULL) {
18331 free(gameInfo.resultDetails);
18333 gameInfo.resultDetails = savedDetails[storedGames];
18334 forwardMostMove = currentMove + nrMoves;
18338 PopTail (Boolean annotate)
18340 if(appData.icsActive) return FALSE; // only in local mode
18341 if(!storedGames) return FALSE; // sanity
18342 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18344 PopInner(annotate);
18345 if(currentMove < forwardMostMove) ForwardEvent(); else
18346 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18348 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18354 { // remove all shelved variations
18356 for(i=0; i<storedGames; i++) {
18357 if(savedDetails[i])
18358 free(savedDetails[i]);
18359 savedDetails[i] = NULL;
18361 for(i=framePtr; i<MAX_MOVES; i++) {
18362 if(commentList[i]) free(commentList[i]);
18363 commentList[i] = NULL;
18365 framePtr = MAX_MOVES-1;
18370 LoadVariation (int index, char *text)
18371 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18372 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18373 int level = 0, move;
18375 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18376 // first find outermost bracketing variation
18377 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18378 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18379 if(*p == '{') wait = '}'; else
18380 if(*p == '[') wait = ']'; else
18381 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18382 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18384 if(*p == wait) wait = NULLCHAR; // closing ]} found
18387 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18388 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18389 end[1] = NULLCHAR; // clip off comment beyond variation
18390 ToNrEvent(currentMove-1);
18391 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18392 // kludge: use ParsePV() to append variation to game
18393 move = currentMove;
18394 ParsePV(start, TRUE, TRUE);
18395 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18396 ClearPremoveHighlights();
18398 ToNrEvent(currentMove+1);
18404 char *p, *q, buf[MSG_SIZ];
18405 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18406 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18407 ParseArgsFromString(buf);
18408 ActivateTheme(TRUE); // also redo colors
18412 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18415 q = appData.themeNames;
18416 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18417 if(appData.useBitmaps) {
18418 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18419 appData.liteBackTextureFile, appData.darkBackTextureFile,
18420 appData.liteBackTextureMode,
18421 appData.darkBackTextureMode );
18423 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18424 Col2Text(2), // lightSquareColor
18425 Col2Text(3) ); // darkSquareColor
18427 if(appData.useBorder) {
18428 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18431 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18433 if(appData.useFont) {
18434 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18435 appData.renderPiecesWithFont,
18436 appData.fontToPieceTable,
18437 Col2Text(9), // appData.fontBackColorWhite
18438 Col2Text(10) ); // appData.fontForeColorBlack
18440 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18441 appData.pieceDirectory);
18442 if(!appData.pieceDirectory[0])
18443 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18444 Col2Text(0), // whitePieceColor
18445 Col2Text(1) ); // blackPieceColor
18447 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18448 Col2Text(4), // highlightSquareColor
18449 Col2Text(5) ); // premoveHighlightColor
18450 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18451 if(insert != q) insert[-1] = NULLCHAR;
18452 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18455 ActivateTheme(FALSE);