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 if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
6944 ChessSquare q, p = boards[0][rf][ff];
6945 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
6946 if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
6947 else p = CHUDEMOTED (q = boards[0][rf][ff]);
6948 if(PieceToChar(q) == '+') gatingPiece = p;
6950 boards[0][toY][toX] = boards[0][fromY][fromX];
6951 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6952 if(boards[0][fromY][0] != EmptySquare) {
6953 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6954 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6957 if(fromX == BOARD_RGHT+1) {
6958 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6959 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6960 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6963 boards[0][fromY][fromX] = gatingPiece;
6964 DrawPosition(FALSE, boards[currentMove]);
6970 if(toX < 0 || toY < 0) return;
6971 pup = boards[currentMove][toY][toX];
6973 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6974 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6975 if( pup != EmptySquare ) return;
6976 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6977 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6978 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6979 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6980 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6981 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6982 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6986 /* [HGM] always test for legality, to get promotion info */
6987 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6988 fromY, fromX, toY, toX, promoChar);
6990 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6992 /* [HGM] but possibly ignore an IllegalMove result */
6993 if (appData.testLegality) {
6994 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6995 DisplayMoveError(_("Illegal move"));
7000 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7001 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7002 ClearPremoveHighlights(); // was included
7003 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
7007 if(addToBookFlag) { // adding moves to book
7008 char buf[MSG_SIZ], move[MSG_SIZ];
7009 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7010 snprintf(buf, MSG_SIZ, " 0.0%% 1 %s\n", move);
7012 addToBookFlag = FALSE;
7017 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7020 /* Common tail of UserMoveEvent and DropMenuEvent */
7022 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7026 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7027 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7028 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7029 if(WhiteOnMove(currentMove)) {
7030 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7032 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7036 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7037 move type in caller when we know the move is a legal promotion */
7038 if(moveType == NormalMove && promoChar)
7039 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7041 /* [HGM] <popupFix> The following if has been moved here from
7042 UserMoveEvent(). Because it seemed to belong here (why not allow
7043 piece drops in training games?), and because it can only be
7044 performed after it is known to what we promote. */
7045 if (gameMode == Training) {
7046 /* compare the move played on the board to the next move in the
7047 * game. If they match, display the move and the opponent's response.
7048 * If they don't match, display an error message.
7052 CopyBoard(testBoard, boards[currentMove]);
7053 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7055 if (CompareBoards(testBoard, boards[currentMove+1])) {
7056 ForwardInner(currentMove+1);
7058 /* Autoplay the opponent's response.
7059 * if appData.animate was TRUE when Training mode was entered,
7060 * the response will be animated.
7062 saveAnimate = appData.animate;
7063 appData.animate = animateTraining;
7064 ForwardInner(currentMove+1);
7065 appData.animate = saveAnimate;
7067 /* check for the end of the game */
7068 if (currentMove >= forwardMostMove) {
7069 gameMode = PlayFromGameFile;
7071 SetTrainingModeOff();
7072 DisplayInformation(_("End of game"));
7075 DisplayError(_("Incorrect move"), 0);
7080 /* Ok, now we know that the move is good, so we can kill
7081 the previous line in Analysis Mode */
7082 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7083 && currentMove < forwardMostMove) {
7084 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7085 else forwardMostMove = currentMove;
7090 /* If we need the chess program but it's dead, restart it */
7091 ResurrectChessProgram();
7093 /* A user move restarts a paused game*/
7097 thinkOutput[0] = NULLCHAR;
7099 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7101 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7102 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7106 if (gameMode == BeginningOfGame) {
7107 if (appData.noChessProgram) {
7108 gameMode = EditGame;
7112 gameMode = MachinePlaysBlack;
7115 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7117 if (first.sendName) {
7118 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7119 SendToProgram(buf, &first);
7126 /* Relay move to ICS or chess engine */
7127 if (appData.icsActive) {
7128 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7129 gameMode == IcsExamining) {
7130 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7131 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7133 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7135 // also send plain move, in case ICS does not understand atomic claims
7136 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7140 if (first.sendTime && (gameMode == BeginningOfGame ||
7141 gameMode == MachinePlaysWhite ||
7142 gameMode == MachinePlaysBlack)) {
7143 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7145 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7146 // [HGM] book: if program might be playing, let it use book
7147 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7148 first.maybeThinking = TRUE;
7149 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7150 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7151 SendBoard(&first, currentMove+1);
7152 if(second.analyzing) {
7153 if(!second.useSetboard) SendToProgram("undo\n", &second);
7154 SendBoard(&second, currentMove+1);
7157 SendMoveToProgram(forwardMostMove-1, &first);
7158 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7160 if (currentMove == cmailOldMove + 1) {
7161 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7165 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7169 if(appData.testLegality)
7170 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7176 if (WhiteOnMove(currentMove)) {
7177 GameEnds(BlackWins, "Black mates", GE_PLAYER);
7179 GameEnds(WhiteWins, "White mates", GE_PLAYER);
7183 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7188 case MachinePlaysBlack:
7189 case MachinePlaysWhite:
7190 /* disable certain menu options while machine is thinking */
7191 SetMachineThinkingEnables();
7198 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7199 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7201 if(bookHit) { // [HGM] book: simulate book reply
7202 static char bookMove[MSG_SIZ]; // a bit generous?
7204 programStats.nodes = programStats.depth = programStats.time =
7205 programStats.score = programStats.got_only_move = 0;
7206 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7208 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7209 strcat(bookMove, bookHit);
7210 HandleMachineMove(bookMove, &first);
7216 MarkByFEN(char *fen)
7219 if(!appData.markers || !appData.highlightDragging) return;
7220 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7221 r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7225 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7226 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7227 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7228 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7229 if(*fen == 'T') marker[r][f++] = 0; else
7230 if(*fen == 'Y') marker[r][f++] = 1; else
7231 if(*fen == 'G') marker[r][f++] = 3; else
7232 if(*fen == 'B') marker[r][f++] = 4; else
7233 if(*fen == 'C') marker[r][f++] = 5; else
7234 if(*fen == 'M') marker[r][f++] = 6; else
7235 if(*fen == 'W') marker[r][f++] = 7; else
7236 if(*fen == 'D') marker[r][f++] = 8; else
7237 if(*fen == 'R') marker[r][f++] = 2; else {
7238 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7241 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7245 DrawPosition(TRUE, NULL);
7248 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7251 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7253 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7254 Markers *m = (Markers *) closure;
7255 if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7256 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7257 || kind == WhiteCapturesEnPassant
7258 || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7259 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7262 static int hoverSavedValid;
7265 MarkTargetSquares (int clear)
7268 if(clear) { // no reason to ever suppress clearing
7269 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7270 hoverSavedValid = 0;
7271 if(!sum) return; // nothing was cleared,no redraw needed
7274 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7275 !appData.testLegality || gameMode == EditPosition) return;
7276 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7277 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7278 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7280 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7283 DrawPosition(FALSE, NULL);
7287 Explode (Board board, int fromX, int fromY, int toX, int toY)
7289 if(gameInfo.variant == VariantAtomic &&
7290 (board[toY][toX] != EmptySquare || // capture?
7291 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7292 board[fromY][fromX] == BlackPawn )
7294 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7300 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7303 CanPromote (ChessSquare piece, int y)
7305 int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7306 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7307 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7308 if(IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantXiangqi ||
7309 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7310 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7311 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) return FALSE;
7312 return (piece == BlackPawn && y <= zone ||
7313 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7314 piece == BlackLance && y == 1 ||
7315 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7319 HoverEvent (int xPix, int yPix, int x, int y)
7321 static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7323 if(!first.highlight) return;
7324 if(fromX != oldFromX || fromY != oldFromY) oldX = oldY = -1; // kludge to fake entry on from-click
7325 if(x == oldX && y == oldY) return; // only do something if we enter new square
7326 oldFromX = fromX; oldFromY = fromY;
7327 if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7328 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7329 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7330 hoverSavedValid = 1;
7331 } else if(oldX != x || oldY != y) {
7332 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7333 if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7334 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7335 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7336 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7338 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7339 SendToProgram(buf, &first);
7342 // SetHighlights(fromX, fromY, x, y);
7346 void ReportClick(char *action, int x, int y)
7348 char buf[MSG_SIZ]; // Inform engine of what user does
7350 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7351 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7352 if(!first.highlight || gameMode == EditPosition) return;
7353 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7354 SendToProgram(buf, &first);
7358 LeftClick (ClickType clickType, int xPix, int yPix)
7361 Boolean saveAnimate;
7362 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7363 char promoChoice = NULLCHAR;
7365 static TimeMark lastClickTime, prevClickTime;
7367 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7369 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7371 if (clickType == Press) ErrorPopDown();
7372 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7374 x = EventToSquare(xPix, BOARD_WIDTH);
7375 y = EventToSquare(yPix, BOARD_HEIGHT);
7376 if (!flipView && y >= 0) {
7377 y = BOARD_HEIGHT - 1 - y;
7379 if (flipView && x >= 0) {
7380 x = BOARD_WIDTH - 1 - x;
7383 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7384 defaultPromoChoice = promoSweep;
7385 promoSweep = EmptySquare; // terminate sweep
7386 promoDefaultAltered = TRUE;
7387 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7390 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7391 if(clickType == Release) return; // ignore upclick of click-click destination
7392 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7393 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7394 if(gameInfo.holdingsWidth &&
7395 (WhiteOnMove(currentMove)
7396 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7397 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7398 // click in right holdings, for determining promotion piece
7399 ChessSquare p = boards[currentMove][y][x];
7400 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7401 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7402 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7403 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7408 DrawPosition(FALSE, boards[currentMove]);
7412 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7413 if(clickType == Press
7414 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7415 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7416 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7419 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7420 // could be static click on premove from-square: abort premove
7422 ClearPremoveHighlights();
7425 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7426 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7428 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7429 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7430 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7431 defaultPromoChoice = DefaultPromoChoice(side);
7434 autoQueen = appData.alwaysPromoteToQueen;
7438 gatingPiece = EmptySquare;
7439 if (clickType != Press) {
7440 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7441 DragPieceEnd(xPix, yPix); dragging = 0;
7442 DrawPosition(FALSE, NULL);
7446 doubleClick = FALSE;
7447 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7448 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7450 fromX = x; fromY = y; toX = toY = killX = killY = -1;
7451 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7452 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7453 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7455 if (OKToStartUserMove(fromX, fromY)) {
7457 ReportClick("lift", x, y);
7458 MarkTargetSquares(0);
7459 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7460 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7461 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7462 promoSweep = defaultPromoChoice;
7463 selectFlag = 0; lastX = xPix; lastY = yPix;
7464 Sweep(0); // Pawn that is going to promote: preview promotion piece
7465 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7467 if (appData.highlightDragging) {
7468 SetHighlights(fromX, fromY, -1, -1);
7472 } else fromX = fromY = -1;
7478 if (clickType == Press && gameMode != EditPosition) {
7483 // ignore off-board to clicks
7484 if(y < 0 || x < 0) return;
7486 /* Check if clicking again on the same color piece */
7487 fromP = boards[currentMove][fromY][fromX];
7488 toP = boards[currentMove][y][x];
7489 frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7490 if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7491 ((WhitePawn <= fromP && fromP <= WhiteKing &&
7492 WhitePawn <= toP && toP <= WhiteKing &&
7493 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7494 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7495 (BlackPawn <= fromP && fromP <= BlackKing &&
7496 BlackPawn <= toP && toP <= BlackKing &&
7497 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7498 !(fromP == BlackKing && toP == BlackRook && frc)))) {
7499 /* Clicked again on same color piece -- changed his mind */
7500 second = (x == fromX && y == fromY);
7502 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7503 second = FALSE; // first double-click rather than scond click
7504 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7506 promoDefaultAltered = FALSE;
7507 MarkTargetSquares(1);
7508 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7509 if (appData.highlightDragging) {
7510 SetHighlights(x, y, -1, -1);
7514 if (OKToStartUserMove(x, y)) {
7515 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7516 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7517 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7518 gatingPiece = boards[currentMove][fromY][fromX];
7519 else gatingPiece = doubleClick ? fromP : EmptySquare;
7521 fromY = y; dragging = 1;
7522 ReportClick("lift", x, y);
7523 MarkTargetSquares(0);
7524 DragPieceBegin(xPix, yPix, FALSE);
7525 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7526 promoSweep = defaultPromoChoice;
7527 selectFlag = 0; lastX = xPix; lastY = yPix;
7528 Sweep(0); // Pawn that is going to promote: preview promotion piece
7532 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7535 // ignore clicks on holdings
7536 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7539 if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7540 DragPieceEnd(xPix, yPix); dragging = 0;
7542 // a deferred attempt to click-click move an empty square on top of a piece
7543 boards[currentMove][y][x] = EmptySquare;
7545 DrawPosition(FALSE, boards[currentMove]);
7546 fromX = fromY = -1; clearFlag = 0;
7549 if (appData.animateDragging) {
7550 /* Undo animation damage if any */
7551 DrawPosition(FALSE, NULL);
7553 if (second || sweepSelecting) {
7554 /* Second up/down in same square; just abort move */
7555 if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7556 second = sweepSelecting = 0;
7558 gatingPiece = EmptySquare;
7559 MarkTargetSquares(1);
7562 ClearPremoveHighlights();
7564 /* First upclick in same square; start click-click mode */
7565 SetHighlights(x, y, -1, -1);
7572 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7573 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7574 DisplayMessage(_("only marked squares are legal"),"");
7575 DrawPosition(TRUE, NULL);
7576 return; // ignore to-click
7579 /* we now have a different from- and (possibly off-board) to-square */
7580 /* Completed move */
7581 if(!sweepSelecting) {
7586 piece = boards[currentMove][fromY][fromX];
7588 saveAnimate = appData.animate;
7589 if (clickType == Press) {
7590 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7591 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7592 // must be Edit Position mode with empty-square selected
7593 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7594 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7597 if(dragging == 2) { // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7600 if(x == killX && y == killY) { // second click on this square, which was selected as first-leg target
7601 killX = killY = -1; // this informs us no second leg is coming, so treat as to-click without intermediate
7603 if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7604 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7605 if(appData.sweepSelect) {
7606 promoSweep = defaultPromoChoice;
7607 if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7608 selectFlag = 0; lastX = xPix; lastY = yPix;
7609 Sweep(0); // Pawn that is going to promote: preview promotion piece
7611 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7612 MarkTargetSquares(1);
7614 return; // promo popup appears on up-click
7616 /* Finish clickclick move */
7617 if (appData.animate || appData.highlightLastMove) {
7618 SetHighlights(fromX, fromY, toX, toY);
7622 } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7623 sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7624 if (appData.animate || appData.highlightLastMove) {
7625 SetHighlights(fromX, fromY, toX, toY);
7631 // [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
7632 /* Finish drag move */
7633 if (appData.highlightLastMove) {
7634 SetHighlights(fromX, fromY, toX, toY);
7639 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7640 if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7641 dragging *= 2; // flag button-less dragging if we are dragging
7642 MarkTargetSquares(1);
7643 if(x == killX && y == killY) killX = killY = -1; else {
7644 killX = x; killY = y; //remeber this square as intermediate
7645 ReportClick("put", x, y); // and inform engine
7646 ReportClick("lift", x, y);
7647 MarkTargetSquares(0);
7651 DragPieceEnd(xPix, yPix); dragging = 0;
7652 /* Don't animate move and drag both */
7653 appData.animate = FALSE;
7656 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7657 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7658 ChessSquare piece = boards[currentMove][fromY][fromX];
7659 if(gameMode == EditPosition && piece != EmptySquare &&
7660 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7663 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7664 n = PieceToNumber(piece - (int)BlackPawn);
7665 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7666 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7667 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7669 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7670 n = PieceToNumber(piece);
7671 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7672 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7673 boards[currentMove][n][BOARD_WIDTH-2]++;
7675 boards[currentMove][fromY][fromX] = EmptySquare;
7679 MarkTargetSquares(1);
7680 DrawPosition(TRUE, boards[currentMove]);
7684 // off-board moves should not be highlighted
7685 if(x < 0 || y < 0) ClearHighlights();
7686 else ReportClick("put", x, y);
7688 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7690 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7691 SetHighlights(fromX, fromY, toX, toY);
7692 MarkTargetSquares(1);
7693 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7694 // [HGM] super: promotion to captured piece selected from holdings
7695 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7696 promotionChoice = TRUE;
7697 // kludge follows to temporarily execute move on display, without promoting yet
7698 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7699 boards[currentMove][toY][toX] = p;
7700 DrawPosition(FALSE, boards[currentMove]);
7701 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7702 boards[currentMove][toY][toX] = q;
7703 DisplayMessage("Click in holdings to choose piece", "");
7706 PromotionPopUp(promoChoice);
7708 int oldMove = currentMove;
7709 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7710 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7711 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7712 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7713 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7714 DrawPosition(TRUE, boards[currentMove]);
7715 MarkTargetSquares(1);
7718 appData.animate = saveAnimate;
7719 if (appData.animate || appData.animateDragging) {
7720 /* Undo animation damage if needed */
7721 DrawPosition(FALSE, NULL);
7726 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7727 { // front-end-free part taken out of PieceMenuPopup
7728 int whichMenu; int xSqr, ySqr;
7730 if(seekGraphUp) { // [HGM] seekgraph
7731 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7732 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7736 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7737 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7738 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7739 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7740 if(action == Press) {
7741 originalFlip = flipView;
7742 flipView = !flipView; // temporarily flip board to see game from partners perspective
7743 DrawPosition(TRUE, partnerBoard);
7744 DisplayMessage(partnerStatus, "");
7746 } else if(action == Release) {
7747 flipView = originalFlip;
7748 DrawPosition(TRUE, boards[currentMove]);
7754 xSqr = EventToSquare(x, BOARD_WIDTH);
7755 ySqr = EventToSquare(y, BOARD_HEIGHT);
7756 if (action == Release) {
7757 if(pieceSweep != EmptySquare) {
7758 EditPositionMenuEvent(pieceSweep, toX, toY);
7759 pieceSweep = EmptySquare;
7760 } else UnLoadPV(); // [HGM] pv
7762 if (action != Press) return -2; // return code to be ignored
7765 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7767 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7768 if (xSqr < 0 || ySqr < 0) return -1;
7769 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7770 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7771 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7772 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7776 if(!appData.icsEngineAnalyze) return -1;
7777 case IcsPlayingWhite:
7778 case IcsPlayingBlack:
7779 if(!appData.zippyPlay) goto noZip;
7782 case MachinePlaysWhite:
7783 case MachinePlaysBlack:
7784 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7785 if (!appData.dropMenu) {
7787 return 2; // flag front-end to grab mouse events
7789 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7790 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7793 if (xSqr < 0 || ySqr < 0) return -1;
7794 if (!appData.dropMenu || appData.testLegality &&
7795 gameInfo.variant != VariantBughouse &&
7796 gameInfo.variant != VariantCrazyhouse) return -1;
7797 whichMenu = 1; // drop menu
7803 if (((*fromX = xSqr) < 0) ||
7804 ((*fromY = ySqr) < 0)) {
7805 *fromX = *fromY = -1;
7809 *fromX = BOARD_WIDTH - 1 - *fromX;
7811 *fromY = BOARD_HEIGHT - 1 - *fromY;
7817 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7819 // char * hint = lastHint;
7820 FrontEndProgramStats stats;
7822 stats.which = cps == &first ? 0 : 1;
7823 stats.depth = cpstats->depth;
7824 stats.nodes = cpstats->nodes;
7825 stats.score = cpstats->score;
7826 stats.time = cpstats->time;
7827 stats.pv = cpstats->movelist;
7828 stats.hint = lastHint;
7829 stats.an_move_index = 0;
7830 stats.an_move_count = 0;
7832 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7833 stats.hint = cpstats->move_name;
7834 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7835 stats.an_move_count = cpstats->nr_moves;
7838 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
7840 SetProgramStats( &stats );
7844 ClearEngineOutputPane (int which)
7846 static FrontEndProgramStats dummyStats;
7847 dummyStats.which = which;
7848 dummyStats.pv = "#";
7849 SetProgramStats( &dummyStats );
7852 #define MAXPLAYERS 500
7855 TourneyStandings (int display)
7857 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7858 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7859 char result, *p, *names[MAXPLAYERS];
7861 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7862 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7863 names[0] = p = strdup(appData.participants);
7864 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7866 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7868 while(result = appData.results[nr]) {
7869 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7870 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7871 wScore = bScore = 0;
7873 case '+': wScore = 2; break;
7874 case '-': bScore = 2; break;
7875 case '=': wScore = bScore = 1; break;
7877 case '*': return strdup("busy"); // tourney not finished
7885 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7886 for(w=0; w<nPlayers; w++) {
7888 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7889 ranking[w] = b; points[w] = bScore; score[b] = -2;
7891 p = malloc(nPlayers*34+1);
7892 for(w=0; w<nPlayers && w<display; w++)
7893 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7899 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7900 { // count all piece types
7902 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7903 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7904 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7907 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7908 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7909 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7910 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7911 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7912 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7917 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7919 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7920 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7922 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7923 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7924 if(myPawns == 2 && nMine == 3) // KPP
7925 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7926 if(myPawns == 1 && nMine == 2) // KP
7927 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7928 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7929 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7930 if(myPawns) return FALSE;
7931 if(pCnt[WhiteRook+side])
7932 return pCnt[BlackRook-side] ||
7933 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7934 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7935 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7936 if(pCnt[WhiteCannon+side]) {
7937 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7938 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7940 if(pCnt[WhiteKnight+side])
7941 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7946 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7948 VariantClass v = gameInfo.variant;
7950 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7951 if(v == VariantShatranj) return TRUE; // always winnable through baring
7952 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7953 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7955 if(v == VariantXiangqi) {
7956 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7958 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7959 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7960 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7961 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7962 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7963 if(stale) // we have at least one last-rank P plus perhaps C
7964 return majors // KPKX
7965 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7967 return pCnt[WhiteFerz+side] // KCAK
7968 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7969 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7970 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7972 } else if(v == VariantKnightmate) {
7973 if(nMine == 1) return FALSE;
7974 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7975 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7976 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7978 if(nMine == 1) return FALSE; // bare King
7979 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
7980 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7981 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7982 // by now we have King + 1 piece (or multiple Bishops on the same color)
7983 if(pCnt[WhiteKnight+side])
7984 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7985 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7986 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7988 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7989 if(pCnt[WhiteAlfil+side])
7990 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7991 if(pCnt[WhiteWazir+side])
7992 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7999 CompareWithRights (Board b1, Board b2)
8002 if(!CompareBoards(b1, b2)) return FALSE;
8003 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8004 /* compare castling rights */
8005 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8006 rights++; /* King lost rights, while rook still had them */
8007 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8008 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8009 rights++; /* but at least one rook lost them */
8011 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8013 if( b1[CASTLING][5] != NoRights ) {
8014 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8021 Adjudicate (ChessProgramState *cps)
8022 { // [HGM] some adjudications useful with buggy engines
8023 // [HGM] adjudicate: made into separate routine, which now can be called after every move
8024 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8025 // Actually ending the game is now based on the additional internal condition canAdjudicate.
8026 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8027 int k, drop, count = 0; static int bare = 1;
8028 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8029 Boolean canAdjudicate = !appData.icsActive;
8031 // most tests only when we understand the game, i.e. legality-checking on
8032 if( appData.testLegality )
8033 { /* [HGM] Some more adjudications for obstinate engines */
8034 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8035 static int moveCount = 6;
8037 char *reason = NULL;
8039 /* Count what is on board. */
8040 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8042 /* Some material-based adjudications that have to be made before stalemate test */
8043 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8044 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8045 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8046 if(canAdjudicate && appData.checkMates) {
8048 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8049 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8050 "Xboard adjudication: King destroyed", GE_XBOARD );
8055 /* Bare King in Shatranj (loses) or Losers (wins) */
8056 if( nrW == 1 || nrB == 1) {
8057 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8058 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
8059 if(canAdjudicate && appData.checkMates) {
8061 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8062 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8063 "Xboard adjudication: Bare king", GE_XBOARD );
8067 if( gameInfo.variant == VariantShatranj && --bare < 0)
8069 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8070 if(canAdjudicate && appData.checkMates) {
8071 /* but only adjudicate if adjudication enabled */
8073 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8074 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8075 "Xboard adjudication: Bare king", GE_XBOARD );
8082 // don't wait for engine to announce game end if we can judge ourselves
8083 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8085 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8086 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
8087 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8088 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8091 reason = "Xboard adjudication: 3rd check";
8092 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8103 reason = "Xboard adjudication: Stalemate";
8104 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8105 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
8106 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8107 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
8108 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8109 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8110 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8111 EP_CHECKMATE : EP_WINS);
8112 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8113 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8117 reason = "Xboard adjudication: Checkmate";
8118 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8119 if(gameInfo.variant == VariantShogi) {
8120 if(forwardMostMove > backwardMostMove
8121 && moveList[forwardMostMove-1][1] == '@'
8122 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8123 reason = "XBoard adjudication: pawn-drop mate";
8124 boards[forwardMostMove][EP_STATUS] = EP_WINS;
8130 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8132 result = GameIsDrawn; break;
8134 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8136 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8140 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8142 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8143 GameEnds( result, reason, GE_XBOARD );
8147 /* Next absolutely insufficient mating material. */
8148 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8149 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8150 { /* includes KBK, KNK, KK of KBKB with like Bishops */
8152 /* always flag draws, for judging claims */
8153 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8155 if(canAdjudicate && appData.materialDraws) {
8156 /* but only adjudicate them if adjudication enabled */
8157 if(engineOpponent) {
8158 SendToProgram("force\n", engineOpponent); // suppress reply
8159 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8161 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8166 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8167 if(gameInfo.variant == VariantXiangqi ?
8168 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8170 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8171 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
8172 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
8173 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8175 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8176 { /* if the first 3 moves do not show a tactical win, declare draw */
8177 if(engineOpponent) {
8178 SendToProgram("force\n", engineOpponent); // suppress reply
8179 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8181 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8184 } else moveCount = 6;
8187 // Repetition draws and 50-move rule can be applied independently of legality testing
8189 /* Check for rep-draws */
8191 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8192 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8193 for(k = forwardMostMove-2;
8194 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8195 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8196 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8199 if(CompareBoards(boards[k], boards[forwardMostMove])) {
8200 /* compare castling rights */
8201 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8202 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8203 rights++; /* King lost rights, while rook still had them */
8204 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8205 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8206 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8207 rights++; /* but at least one rook lost them */
8209 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8210 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8212 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8213 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8214 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8217 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8218 && appData.drawRepeats > 1) {
8219 /* adjudicate after user-specified nr of repeats */
8220 int result = GameIsDrawn;
8221 char *details = "XBoard adjudication: repetition draw";
8222 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8223 // [HGM] xiangqi: check for forbidden perpetuals
8224 int m, ourPerpetual = 1, hisPerpetual = 1;
8225 for(m=forwardMostMove; m>k; m-=2) {
8226 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8227 ourPerpetual = 0; // the current mover did not always check
8228 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8229 hisPerpetual = 0; // the opponent did not always check
8231 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8232 ourPerpetual, hisPerpetual);
8233 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8234 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8235 details = "Xboard adjudication: perpetual checking";
8237 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8238 break; // (or we would have caught him before). Abort repetition-checking loop.
8240 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8241 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8243 details = "Xboard adjudication: repetition";
8245 } else // it must be XQ
8246 // Now check for perpetual chases
8247 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8248 hisPerpetual = PerpetualChase(k, forwardMostMove);
8249 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8250 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8251 static char resdet[MSG_SIZ];
8252 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8254 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8256 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8257 break; // Abort repetition-checking loop.
8259 // if neither of us is checking or chasing all the time, or both are, it is draw
8261 if(engineOpponent) {
8262 SendToProgram("force\n", engineOpponent); // suppress reply
8263 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8265 GameEnds( result, details, GE_XBOARD );
8268 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8269 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8273 /* Now we test for 50-move draws. Determine ply count */
8274 count = forwardMostMove;
8275 /* look for last irreversble move */
8276 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8278 /* if we hit starting position, add initial plies */
8279 if( count == backwardMostMove )
8280 count -= initialRulePlies;
8281 count = forwardMostMove - count;
8282 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8283 // adjust reversible move counter for checks in Xiangqi
8284 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8285 if(i < backwardMostMove) i = backwardMostMove;
8286 while(i <= forwardMostMove) {
8287 lastCheck = inCheck; // check evasion does not count
8288 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8289 if(inCheck || lastCheck) count--; // check does not count
8294 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8295 /* this is used to judge if draw claims are legal */
8296 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8297 if(engineOpponent) {
8298 SendToProgram("force\n", engineOpponent); // suppress reply
8299 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8301 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8305 /* if draw offer is pending, treat it as a draw claim
8306 * when draw condition present, to allow engines a way to
8307 * claim draws before making their move to avoid a race
8308 * condition occurring after their move
8310 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8312 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8313 p = "Draw claim: 50-move rule";
8314 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8315 p = "Draw claim: 3-fold repetition";
8316 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8317 p = "Draw claim: insufficient mating material";
8318 if( p != NULL && canAdjudicate) {
8319 if(engineOpponent) {
8320 SendToProgram("force\n", engineOpponent); // suppress reply
8321 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8323 GameEnds( GameIsDrawn, p, GE_XBOARD );
8328 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8329 if(engineOpponent) {
8330 SendToProgram("force\n", engineOpponent); // suppress reply
8331 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8333 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8339 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8340 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8341 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8346 int pieces[10], squares[10], cnt=0, r, f, res;
8348 static PPROBE_EGBB probeBB;
8349 if(!appData.testLegality) return 10;
8350 if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8351 if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8352 if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8353 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8354 ChessSquare piece = boards[forwardMostMove][r][f];
8355 int black = (piece >= BlackPawn);
8356 int type = piece - black*BlackPawn;
8357 if(piece == EmptySquare) continue;
8358 if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8359 if(type == WhiteKing) type = WhiteQueen + 1;
8360 type = egbbCode[type];
8361 squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8362 pieces[cnt] = type + black*6;
8363 if(++cnt > 5) return 11;
8365 pieces[cnt] = squares[cnt] = 0;
8367 if(loaded == 2) return 13; // loading failed before
8369 loaded = 2; // prepare for failure
8370 char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8373 if(!path) return 13; // no egbb installed
8374 strncpy(buf, path + 8, MSG_SIZ);
8375 if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8376 snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8377 lib = LoadLibrary(buf);
8378 if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8379 loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8380 probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8381 if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8382 p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8383 loaded = 1; // success!
8385 res = probeBB(forwardMostMove & 1, pieces, squares);
8386 return res > 0 ? 1 : res < 0 ? -1 : 0;
8390 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8391 { // [HGM] book: this routine intercepts moves to simulate book replies
8392 char *bookHit = NULL;
8394 if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8396 snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8397 SendToProgram(buf, cps);
8399 //first determine if the incoming move brings opponent into his book
8400 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8401 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8402 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8403 if(bookHit != NULL && !cps->bookSuspend) {
8404 // make sure opponent is not going to reply after receiving move to book position
8405 SendToProgram("force\n", cps);
8406 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8408 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8409 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8410 // now arrange restart after book miss
8412 // after a book hit we never send 'go', and the code after the call to this routine
8413 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8414 char buf[MSG_SIZ], *move = bookHit;
8416 int fromX, fromY, toX, toY;
8420 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8421 &fromX, &fromY, &toX, &toY, &promoChar)) {
8422 (void) CoordsToAlgebraic(boards[forwardMostMove],
8423 PosFlags(forwardMostMove),
8424 fromY, fromX, toY, toX, promoChar, move);
8426 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8430 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8431 SendToProgram(buf, cps);
8432 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8433 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8434 SendToProgram("go\n", cps);
8435 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8436 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8437 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8438 SendToProgram("go\n", cps);
8439 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8441 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8445 LoadError (char *errmess, ChessProgramState *cps)
8446 { // unloads engine and switches back to -ncp mode if it was first
8447 if(cps->initDone) return FALSE;
8448 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8449 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8452 appData.noChessProgram = TRUE;
8453 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8454 gameMode = BeginningOfGame; ModeHighlight();
8457 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8458 DisplayMessage("", ""); // erase waiting message
8459 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8464 ChessProgramState *savedState;
8466 DeferredBookMove (void)
8468 if(savedState->lastPing != savedState->lastPong)
8469 ScheduleDelayedEvent(DeferredBookMove, 10);
8471 HandleMachineMove(savedMessage, savedState);
8474 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8475 static ChessProgramState *stalledEngine;
8476 static char stashedInputMove[MSG_SIZ];
8479 HandleMachineMove (char *message, ChessProgramState *cps)
8481 static char firstLeg[20];
8482 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8483 char realname[MSG_SIZ];
8484 int fromX, fromY, toX, toY;
8486 char promoChar, roar;
8488 int machineWhite, oldError;
8491 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8492 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8493 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8494 DisplayError(_("Invalid pairing from pairing engine"), 0);
8497 pairingReceived = 1;
8499 return; // Skim the pairing messages here.
8502 oldError = cps->userError; cps->userError = 0;
8504 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8506 * Kludge to ignore BEL characters
8508 while (*message == '\007') message++;
8511 * [HGM] engine debug message: ignore lines starting with '#' character
8513 if(cps->debug && *message == '#') return;
8516 * Look for book output
8518 if (cps == &first && bookRequested) {
8519 if (message[0] == '\t' || message[0] == ' ') {
8520 /* Part of the book output is here; append it */
8521 strcat(bookOutput, message);
8522 strcat(bookOutput, " \n");
8524 } else if (bookOutput[0] != NULLCHAR) {
8525 /* All of book output has arrived; display it */
8526 char *p = bookOutput;
8527 while (*p != NULLCHAR) {
8528 if (*p == '\t') *p = ' ';
8531 DisplayInformation(bookOutput);
8532 bookRequested = FALSE;
8533 /* Fall through to parse the current output */
8538 * Look for machine move.
8540 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8541 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8543 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8544 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8545 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8546 stalledEngine = cps;
8547 if(appData.ponderNextMove) { // bring opponent out of ponder
8548 if(gameMode == TwoMachinesPlay) {
8549 if(cps->other->pause)
8550 PauseEngine(cps->other);
8552 SendToProgram("easy\n", cps->other);
8559 /* This method is only useful on engines that support ping */
8560 if (cps->lastPing != cps->lastPong) {
8561 if (gameMode == BeginningOfGame) {
8562 /* Extra move from before last new; ignore */
8563 if (appData.debugMode) {
8564 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8567 if (appData.debugMode) {
8568 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8569 cps->which, gameMode);
8572 SendToProgram("undo\n", cps);
8578 case BeginningOfGame:
8579 /* Extra move from before last reset; ignore */
8580 if (appData.debugMode) {
8581 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8588 /* Extra move after we tried to stop. The mode test is
8589 not a reliable way of detecting this problem, but it's
8590 the best we can do on engines that don't support ping.
8592 if (appData.debugMode) {
8593 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8594 cps->which, gameMode);
8596 SendToProgram("undo\n", cps);
8599 case MachinePlaysWhite:
8600 case IcsPlayingWhite:
8601 machineWhite = TRUE;
8604 case MachinePlaysBlack:
8605 case IcsPlayingBlack:
8606 machineWhite = FALSE;
8609 case TwoMachinesPlay:
8610 machineWhite = (cps->twoMachinesColor[0] == 'w');
8613 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8614 if (appData.debugMode) {
8616 "Ignoring move out of turn by %s, gameMode %d"
8617 ", forwardMost %d\n",
8618 cps->which, gameMode, forwardMostMove);
8623 if(cps->alphaRank) AlphaRank(machineMove, 4);
8625 // [HGM] lion: (some very limited) support for Alien protocol
8627 if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8628 safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8630 } else if(firstLeg[0]) { // there was a previous leg;
8631 // only support case where same piece makes two step (and don't even test that!)
8632 char buf[20], *p = machineMove+1, *q = buf+1, f;
8633 safeStrCpy(buf, machineMove, 20);
8634 while(isdigit(*q)) q++; // find start of to-square
8635 safeStrCpy(machineMove, firstLeg, 20);
8636 while(isdigit(*p)) p++;
8637 safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8638 sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8639 firstLeg[0] = NULLCHAR;
8642 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8643 &fromX, &fromY, &toX, &toY, &promoChar)) {
8644 /* Machine move could not be parsed; ignore it. */
8645 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8646 machineMove, _(cps->which));
8647 DisplayMoveError(buf1);
8648 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8649 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8650 if (gameMode == TwoMachinesPlay) {
8651 GameEnds(machineWhite ? BlackWins : WhiteWins,
8657 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8658 /* So we have to redo legality test with true e.p. status here, */
8659 /* to make sure an illegal e.p. capture does not slip through, */
8660 /* to cause a forfeit on a justified illegal-move complaint */
8661 /* of the opponent. */
8662 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8664 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8665 fromY, fromX, toY, toX, promoChar);
8666 if(moveType == IllegalMove) {
8667 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8668 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8669 GameEnds(machineWhite ? BlackWins : WhiteWins,
8672 } else if(!appData.fischerCastling)
8673 /* [HGM] Kludge to handle engines that send FRC-style castling
8674 when they shouldn't (like TSCP-Gothic) */
8676 case WhiteASideCastleFR:
8677 case BlackASideCastleFR:
8679 currentMoveString[2]++;
8681 case WhiteHSideCastleFR:
8682 case BlackHSideCastleFR:
8684 currentMoveString[2]--;
8686 default: ; // nothing to do, but suppresses warning of pedantic compilers
8689 hintRequested = FALSE;
8690 lastHint[0] = NULLCHAR;
8691 bookRequested = FALSE;
8692 /* Program may be pondering now */
8693 cps->maybeThinking = TRUE;
8694 if (cps->sendTime == 2) cps->sendTime = 1;
8695 if (cps->offeredDraw) cps->offeredDraw--;
8697 /* [AS] Save move info*/
8698 pvInfoList[ forwardMostMove ].score = programStats.score;
8699 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8700 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8702 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8704 /* Test suites abort the 'game' after one move */
8705 if(*appData.finger) {
8707 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8708 if(!f) f = fopen(appData.finger, "w");
8709 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8710 else { DisplayFatalError("Bad output file", errno, 0); return; }
8712 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8715 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8716 if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8719 while( count < adjudicateLossPlies ) {
8720 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8723 score = -score; /* Flip score for winning side */
8725 printf("score=%d count=%d\n",score,count);
8726 if( score > appData.adjudicateLossThreshold ) {
8733 if( count >= adjudicateLossPlies ) {
8734 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8736 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8737 "Xboard adjudication",
8744 if(Adjudicate(cps)) {
8745 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8746 return; // [HGM] adjudicate: for all automatic game ends
8750 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8752 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8753 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8755 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8757 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8759 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8760 char buf[3*MSG_SIZ];
8762 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8763 programStats.score / 100.,
8765 programStats.time / 100.,
8766 (unsigned int)programStats.nodes,
8767 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8768 programStats.movelist);
8774 /* [AS] Clear stats for next move */
8775 ClearProgramStats();
8776 thinkOutput[0] = NULLCHAR;
8777 hiddenThinkOutputState = 0;
8780 if (gameMode == TwoMachinesPlay) {
8781 /* [HGM] relaying draw offers moved to after reception of move */
8782 /* and interpreting offer as claim if it brings draw condition */
8783 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8784 SendToProgram("draw\n", cps->other);
8786 if (cps->other->sendTime) {
8787 SendTimeRemaining(cps->other,
8788 cps->other->twoMachinesColor[0] == 'w');
8790 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8791 if (firstMove && !bookHit) {
8793 if (cps->other->useColors) {
8794 SendToProgram(cps->other->twoMachinesColor, cps->other);
8796 SendToProgram("go\n", cps->other);
8798 cps->other->maybeThinking = TRUE;
8801 roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8803 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8805 if (!pausing && appData.ringBellAfterMoves) {
8806 if(!roar) RingBell();
8810 * Reenable menu items that were disabled while
8811 * machine was thinking
8813 if (gameMode != TwoMachinesPlay)
8814 SetUserThinkingEnables();
8816 // [HGM] book: after book hit opponent has received move and is now in force mode
8817 // force the book reply into it, and then fake that it outputted this move by jumping
8818 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8820 static char bookMove[MSG_SIZ]; // a bit generous?
8822 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8823 strcat(bookMove, bookHit);
8826 programStats.nodes = programStats.depth = programStats.time =
8827 programStats.score = programStats.got_only_move = 0;
8828 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8830 if(cps->lastPing != cps->lastPong) {
8831 savedMessage = message; // args for deferred call
8833 ScheduleDelayedEvent(DeferredBookMove, 10);
8842 /* Set special modes for chess engines. Later something general
8843 * could be added here; for now there is just one kludge feature,
8844 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8845 * when "xboard" is given as an interactive command.
8847 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8848 cps->useSigint = FALSE;
8849 cps->useSigterm = FALSE;
8851 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8852 ParseFeatures(message+8, cps);
8853 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8856 if (!strncmp(message, "setup ", 6) &&
8857 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8858 NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8859 ) { // [HGM] allow first engine to define opening position
8860 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8861 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8863 if(sscanf(message, "setup (%s", buf) == 1) {
8864 s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8865 ASSIGN(appData.pieceToCharTable, buf);
8867 if(startedFromSetupPosition) return;
8868 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8870 while(message[s] && message[s++] != ' ');
8871 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8872 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8873 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8874 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
8875 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8876 if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8879 ParseFEN(boards[0], &dummy, message+s, FALSE);
8880 DrawPosition(TRUE, boards[0]);
8881 startedFromSetupPosition = TRUE;
8884 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8885 * want this, I was asked to put it in, and obliged.
8887 if (!strncmp(message, "setboard ", 9)) {
8888 Board initial_position;
8890 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8892 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8893 DisplayError(_("Bad FEN received from engine"), 0);
8897 CopyBoard(boards[0], initial_position);
8898 initialRulePlies = FENrulePlies;
8899 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8900 else gameMode = MachinePlaysBlack;
8901 DrawPosition(FALSE, boards[currentMove]);
8907 * Look for communication commands
8909 if (!strncmp(message, "telluser ", 9)) {
8910 if(message[9] == '\\' && message[10] == '\\')
8911 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8913 DisplayNote(message + 9);
8916 if (!strncmp(message, "tellusererror ", 14)) {
8918 if(message[14] == '\\' && message[15] == '\\')
8919 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8921 DisplayError(message + 14, 0);
8924 if (!strncmp(message, "tellopponent ", 13)) {
8925 if (appData.icsActive) {
8927 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8931 DisplayNote(message + 13);
8935 if (!strncmp(message, "tellothers ", 11)) {
8936 if (appData.icsActive) {
8938 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8941 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8944 if (!strncmp(message, "tellall ", 8)) {
8945 if (appData.icsActive) {
8947 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8951 DisplayNote(message + 8);
8955 if (strncmp(message, "warning", 7) == 0) {
8956 /* Undocumented feature, use tellusererror in new code */
8957 DisplayError(message, 0);
8960 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8961 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8962 strcat(realname, " query");
8963 AskQuestion(realname, buf2, buf1, cps->pr);
8966 /* Commands from the engine directly to ICS. We don't allow these to be
8967 * sent until we are logged on. Crafty kibitzes have been known to
8968 * interfere with the login process.
8971 if (!strncmp(message, "tellics ", 8)) {
8972 SendToICS(message + 8);
8976 if (!strncmp(message, "tellicsnoalias ", 15)) {
8977 SendToICS(ics_prefix);
8978 SendToICS(message + 15);
8982 /* The following are for backward compatibility only */
8983 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8984 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8985 SendToICS(ics_prefix);
8991 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8992 if(initPing == cps->lastPong) {
8993 if(gameInfo.variant == VariantUnknown) {
8994 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8995 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8996 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9002 if(!strncmp(message, "highlight ", 10)) {
9003 if(appData.testLegality && appData.markers) return;
9004 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9007 if(!strncmp(message, "click ", 6)) {
9008 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9009 if(appData.testLegality || !appData.oneClick) return;
9010 sscanf(message+6, "%c%d%c", &f, &y, &c);
9011 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9012 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9013 x = x*squareSize + (x+1)*lineGap + squareSize/2;
9014 y = y*squareSize + (y+1)*lineGap + squareSize/2;
9015 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9016 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9017 LeftClick(Release, lastLeftX, lastLeftY);
9018 controlKey = (c == ',');
9019 LeftClick(Press, x, y);
9020 LeftClick(Release, x, y);
9021 first.highlight = f;
9025 * If the move is illegal, cancel it and redraw the board.
9026 * Also deal with other error cases. Matching is rather loose
9027 * here to accommodate engines written before the spec.
9029 if (strncmp(message + 1, "llegal move", 11) == 0 ||
9030 strncmp(message, "Error", 5) == 0) {
9031 if (StrStr(message, "name") ||
9032 StrStr(message, "rating") || StrStr(message, "?") ||
9033 StrStr(message, "result") || StrStr(message, "board") ||
9034 StrStr(message, "bk") || StrStr(message, "computer") ||
9035 StrStr(message, "variant") || StrStr(message, "hint") ||
9036 StrStr(message, "random") || StrStr(message, "depth") ||
9037 StrStr(message, "accepted")) {
9040 if (StrStr(message, "protover")) {
9041 /* Program is responding to input, so it's apparently done
9042 initializing, and this error message indicates it is
9043 protocol version 1. So we don't need to wait any longer
9044 for it to initialize and send feature commands. */
9045 FeatureDone(cps, 1);
9046 cps->protocolVersion = 1;
9049 cps->maybeThinking = FALSE;
9051 if (StrStr(message, "draw")) {
9052 /* Program doesn't have "draw" command */
9053 cps->sendDrawOffers = 0;
9056 if (cps->sendTime != 1 &&
9057 (StrStr(message, "time") || StrStr(message, "otim"))) {
9058 /* Program apparently doesn't have "time" or "otim" command */
9062 if (StrStr(message, "analyze")) {
9063 cps->analysisSupport = FALSE;
9064 cps->analyzing = FALSE;
9065 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9066 EditGameEvent(); // [HGM] try to preserve loaded game
9067 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9068 DisplayError(buf2, 0);
9071 if (StrStr(message, "(no matching move)st")) {
9072 /* Special kludge for GNU Chess 4 only */
9073 cps->stKludge = TRUE;
9074 SendTimeControl(cps, movesPerSession, timeControl,
9075 timeIncrement, appData.searchDepth,
9079 if (StrStr(message, "(no matching move)sd")) {
9080 /* Special kludge for GNU Chess 4 only */
9081 cps->sdKludge = TRUE;
9082 SendTimeControl(cps, movesPerSession, timeControl,
9083 timeIncrement, appData.searchDepth,
9087 if (!StrStr(message, "llegal")) {
9090 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9091 gameMode == IcsIdle) return;
9092 if (forwardMostMove <= backwardMostMove) return;
9093 if (pausing) PauseEvent();
9094 if(appData.forceIllegal) {
9095 // [HGM] illegal: machine refused move; force position after move into it
9096 SendToProgram("force\n", cps);
9097 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9098 // we have a real problem now, as SendBoard will use the a2a3 kludge
9099 // when black is to move, while there might be nothing on a2 or black
9100 // might already have the move. So send the board as if white has the move.
9101 // But first we must change the stm of the engine, as it refused the last move
9102 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9103 if(WhiteOnMove(forwardMostMove)) {
9104 SendToProgram("a7a6\n", cps); // for the engine black still had the move
9105 SendBoard(cps, forwardMostMove); // kludgeless board
9107 SendToProgram("a2a3\n", cps); // for the engine white still had the move
9108 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9109 SendBoard(cps, forwardMostMove+1); // kludgeless board
9111 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9112 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9113 gameMode == TwoMachinesPlay)
9114 SendToProgram("go\n", cps);
9117 if (gameMode == PlayFromGameFile) {
9118 /* Stop reading this game file */
9119 gameMode = EditGame;
9122 /* [HGM] illegal-move claim should forfeit game when Xboard */
9123 /* only passes fully legal moves */
9124 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9125 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9126 "False illegal-move claim", GE_XBOARD );
9127 return; // do not take back move we tested as valid
9129 currentMove = forwardMostMove-1;
9130 DisplayMove(currentMove-1); /* before DisplayMoveError */
9131 SwitchClocks(forwardMostMove-1); // [HGM] race
9132 DisplayBothClocks();
9133 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9134 parseList[currentMove], _(cps->which));
9135 DisplayMoveError(buf1);
9136 DrawPosition(FALSE, boards[currentMove]);
9138 SetUserThinkingEnables();
9141 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9142 /* Program has a broken "time" command that
9143 outputs a string not ending in newline.
9147 if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9148 if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9149 sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1 ) return;
9153 * If chess program startup fails, exit with an error message.
9154 * Attempts to recover here are futile. [HGM] Well, we try anyway
9156 if ((StrStr(message, "unknown host") != NULL)
9157 || (StrStr(message, "No remote directory") != NULL)
9158 || (StrStr(message, "not found") != NULL)
9159 || (StrStr(message, "No such file") != NULL)
9160 || (StrStr(message, "can't alloc") != NULL)
9161 || (StrStr(message, "Permission denied") != NULL)) {
9163 cps->maybeThinking = FALSE;
9164 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9165 _(cps->which), cps->program, cps->host, message);
9166 RemoveInputSource(cps->isr);
9167 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9168 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9169 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9175 * Look for hint output
9177 if (sscanf(message, "Hint: %s", buf1) == 1) {
9178 if (cps == &first && hintRequested) {
9179 hintRequested = FALSE;
9180 if (ParseOneMove(buf1, forwardMostMove, &moveType,
9181 &fromX, &fromY, &toX, &toY, &promoChar)) {
9182 (void) CoordsToAlgebraic(boards[forwardMostMove],
9183 PosFlags(forwardMostMove),
9184 fromY, fromX, toY, toX, promoChar, buf1);
9185 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9186 DisplayInformation(buf2);
9188 /* Hint move could not be parsed!? */
9189 snprintf(buf2, sizeof(buf2),
9190 _("Illegal hint move \"%s\"\nfrom %s chess program"),
9191 buf1, _(cps->which));
9192 DisplayError(buf2, 0);
9195 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9201 * Ignore other messages if game is not in progress
9203 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9204 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9207 * look for win, lose, draw, or draw offer
9209 if (strncmp(message, "1-0", 3) == 0) {
9210 char *p, *q, *r = "";
9211 p = strchr(message, '{');
9219 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9221 } else if (strncmp(message, "0-1", 3) == 0) {
9222 char *p, *q, *r = "";
9223 p = strchr(message, '{');
9231 /* Kludge for Arasan 4.1 bug */
9232 if (strcmp(r, "Black resigns") == 0) {
9233 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9236 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9238 } else if (strncmp(message, "1/2", 3) == 0) {
9239 char *p, *q, *r = "";
9240 p = strchr(message, '{');
9249 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9252 } else if (strncmp(message, "White resign", 12) == 0) {
9253 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9255 } else if (strncmp(message, "Black resign", 12) == 0) {
9256 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9258 } else if (strncmp(message, "White matches", 13) == 0 ||
9259 strncmp(message, "Black matches", 13) == 0 ) {
9260 /* [HGM] ignore GNUShogi noises */
9262 } else if (strncmp(message, "White", 5) == 0 &&
9263 message[5] != '(' &&
9264 StrStr(message, "Black") == NULL) {
9265 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9267 } else if (strncmp(message, "Black", 5) == 0 &&
9268 message[5] != '(') {
9269 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9271 } else if (strcmp(message, "resign") == 0 ||
9272 strcmp(message, "computer resigns") == 0) {
9274 case MachinePlaysBlack:
9275 case IcsPlayingBlack:
9276 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9278 case MachinePlaysWhite:
9279 case IcsPlayingWhite:
9280 GameEnds(BlackWins, "White resigns", GE_ENGINE);
9282 case TwoMachinesPlay:
9283 if (cps->twoMachinesColor[0] == 'w')
9284 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9286 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9293 } else if (strncmp(message, "opponent mates", 14) == 0) {
9295 case MachinePlaysBlack:
9296 case IcsPlayingBlack:
9297 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9299 case MachinePlaysWhite:
9300 case IcsPlayingWhite:
9301 GameEnds(BlackWins, "Black mates", GE_ENGINE);
9303 case TwoMachinesPlay:
9304 if (cps->twoMachinesColor[0] == 'w')
9305 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9307 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9314 } else if (strncmp(message, "computer mates", 14) == 0) {
9316 case MachinePlaysBlack:
9317 case IcsPlayingBlack:
9318 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9320 case MachinePlaysWhite:
9321 case IcsPlayingWhite:
9322 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9324 case TwoMachinesPlay:
9325 if (cps->twoMachinesColor[0] == 'w')
9326 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9328 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9335 } else if (strncmp(message, "checkmate", 9) == 0) {
9336 if (WhiteOnMove(forwardMostMove)) {
9337 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9339 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9342 } else if (strstr(message, "Draw") != NULL ||
9343 strstr(message, "game is a draw") != NULL) {
9344 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9346 } else if (strstr(message, "offer") != NULL &&
9347 strstr(message, "draw") != NULL) {
9349 if (appData.zippyPlay && first.initDone) {
9350 /* Relay offer to ICS */
9351 SendToICS(ics_prefix);
9352 SendToICS("draw\n");
9355 cps->offeredDraw = 2; /* valid until this engine moves twice */
9356 if (gameMode == TwoMachinesPlay) {
9357 if (cps->other->offeredDraw) {
9358 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9359 /* [HGM] in two-machine mode we delay relaying draw offer */
9360 /* until after we also have move, to see if it is really claim */
9362 } else if (gameMode == MachinePlaysWhite ||
9363 gameMode == MachinePlaysBlack) {
9364 if (userOfferedDraw) {
9365 DisplayInformation(_("Machine accepts your draw offer"));
9366 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9368 DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9375 * Look for thinking output
9377 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9378 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9380 int plylev, mvleft, mvtot, curscore, time;
9381 char mvname[MOVE_LEN];
9385 int prefixHint = FALSE;
9386 mvname[0] = NULLCHAR;
9389 case MachinePlaysBlack:
9390 case IcsPlayingBlack:
9391 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9393 case MachinePlaysWhite:
9394 case IcsPlayingWhite:
9395 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9400 case IcsObserving: /* [DM] icsEngineAnalyze */
9401 if (!appData.icsEngineAnalyze) ignore = TRUE;
9403 case TwoMachinesPlay:
9404 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9414 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9416 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9417 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9419 if (plyext != ' ' && plyext != '\t') {
9423 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9424 if( cps->scoreIsAbsolute &&
9425 ( gameMode == MachinePlaysBlack ||
9426 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9427 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9428 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9429 !WhiteOnMove(currentMove)
9432 curscore = -curscore;
9435 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9437 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9440 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9441 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9442 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9443 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9444 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9445 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9449 /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9450 DisplayError(_("failed writing PV"), 0);
9453 tempStats.depth = plylev;
9454 tempStats.nodes = nodes;
9455 tempStats.time = time;
9456 tempStats.score = curscore;
9457 tempStats.got_only_move = 0;
9459 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9462 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9463 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9464 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9465 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9466 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9467 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9468 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9469 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9472 /* Buffer overflow protection */
9473 if (pv[0] != NULLCHAR) {
9474 if (strlen(pv) >= sizeof(tempStats.movelist)
9475 && appData.debugMode) {
9477 "PV is too long; using the first %u bytes.\n",
9478 (unsigned) sizeof(tempStats.movelist) - 1);
9481 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9483 sprintf(tempStats.movelist, " no PV\n");
9486 if (tempStats.seen_stat) {
9487 tempStats.ok_to_send = 1;
9490 if (strchr(tempStats.movelist, '(') != NULL) {
9491 tempStats.line_is_book = 1;
9492 tempStats.nr_moves = 0;
9493 tempStats.moves_left = 0;
9495 tempStats.line_is_book = 0;
9498 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9499 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9501 SendProgramStatsToFrontend( cps, &tempStats );
9504 [AS] Protect the thinkOutput buffer from overflow... this
9505 is only useful if buf1 hasn't overflowed first!
9507 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9509 (gameMode == TwoMachinesPlay ?
9510 ToUpper(cps->twoMachinesColor[0]) : ' '),
9511 ((double) curscore) / 100.0,
9512 prefixHint ? lastHint : "",
9513 prefixHint ? " " : "" );
9515 if( buf1[0] != NULLCHAR ) {
9516 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9518 if( strlen(pv) > max_len ) {
9519 if( appData.debugMode) {
9520 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9522 pv[max_len+1] = '\0';
9525 strcat( thinkOutput, pv);
9528 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9529 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9530 DisplayMove(currentMove - 1);
9534 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9535 /* crafty (9.25+) says "(only move) <move>"
9536 * if there is only 1 legal move
9538 sscanf(p, "(only move) %s", buf1);
9539 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9540 sprintf(programStats.movelist, "%s (only move)", buf1);
9541 programStats.depth = 1;
9542 programStats.nr_moves = 1;
9543 programStats.moves_left = 1;
9544 programStats.nodes = 1;
9545 programStats.time = 1;
9546 programStats.got_only_move = 1;
9548 /* Not really, but we also use this member to
9549 mean "line isn't going to change" (Crafty
9550 isn't searching, so stats won't change) */
9551 programStats.line_is_book = 1;
9553 SendProgramStatsToFrontend( cps, &programStats );
9555 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9556 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9557 DisplayMove(currentMove - 1);
9560 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9561 &time, &nodes, &plylev, &mvleft,
9562 &mvtot, mvname) >= 5) {
9563 /* The stat01: line is from Crafty (9.29+) in response
9564 to the "." command */
9565 programStats.seen_stat = 1;
9566 cps->maybeThinking = TRUE;
9568 if (programStats.got_only_move || !appData.periodicUpdates)
9571 programStats.depth = plylev;
9572 programStats.time = time;
9573 programStats.nodes = nodes;
9574 programStats.moves_left = mvleft;
9575 programStats.nr_moves = mvtot;
9576 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9577 programStats.ok_to_send = 1;
9578 programStats.movelist[0] = '\0';
9580 SendProgramStatsToFrontend( cps, &programStats );
9584 } else if (strncmp(message,"++",2) == 0) {
9585 /* Crafty 9.29+ outputs this */
9586 programStats.got_fail = 2;
9589 } else if (strncmp(message,"--",2) == 0) {
9590 /* Crafty 9.29+ outputs this */
9591 programStats.got_fail = 1;
9594 } else if (thinkOutput[0] != NULLCHAR &&
9595 strncmp(message, " ", 4) == 0) {
9596 unsigned message_len;
9599 while (*p && *p == ' ') p++;
9601 message_len = strlen( p );
9603 /* [AS] Avoid buffer overflow */
9604 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9605 strcat(thinkOutput, " ");
9606 strcat(thinkOutput, p);
9609 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9610 strcat(programStats.movelist, " ");
9611 strcat(programStats.movelist, p);
9614 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9615 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9616 DisplayMove(currentMove - 1);
9624 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9625 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9627 ChessProgramStats cpstats;
9629 if (plyext != ' ' && plyext != '\t') {
9633 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9634 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9635 curscore = -curscore;
9638 cpstats.depth = plylev;
9639 cpstats.nodes = nodes;
9640 cpstats.time = time;
9641 cpstats.score = curscore;
9642 cpstats.got_only_move = 0;
9643 cpstats.movelist[0] = '\0';
9645 if (buf1[0] != NULLCHAR) {
9646 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9649 cpstats.ok_to_send = 0;
9650 cpstats.line_is_book = 0;
9651 cpstats.nr_moves = 0;
9652 cpstats.moves_left = 0;
9654 SendProgramStatsToFrontend( cps, &cpstats );
9661 /* Parse a game score from the character string "game", and
9662 record it as the history of the current game. The game
9663 score is NOT assumed to start from the standard position.
9664 The display is not updated in any way.
9667 ParseGameHistory (char *game)
9670 int fromX, fromY, toX, toY, boardIndex;
9675 if (appData.debugMode)
9676 fprintf(debugFP, "Parsing game history: %s\n", game);
9678 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9679 gameInfo.site = StrSave(appData.icsHost);
9680 gameInfo.date = PGNDate();
9681 gameInfo.round = StrSave("-");
9683 /* Parse out names of players */
9684 while (*game == ' ') game++;
9686 while (*game != ' ') *p++ = *game++;
9688 gameInfo.white = StrSave(buf);
9689 while (*game == ' ') game++;
9691 while (*game != ' ' && *game != '\n') *p++ = *game++;
9693 gameInfo.black = StrSave(buf);
9696 boardIndex = blackPlaysFirst ? 1 : 0;
9699 yyboardindex = boardIndex;
9700 moveType = (ChessMove) Myylex();
9702 case IllegalMove: /* maybe suicide chess, etc. */
9703 if (appData.debugMode) {
9704 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9705 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9706 setbuf(debugFP, NULL);
9708 case WhitePromotion:
9709 case BlackPromotion:
9710 case WhiteNonPromotion:
9711 case BlackNonPromotion:
9714 case WhiteCapturesEnPassant:
9715 case BlackCapturesEnPassant:
9716 case WhiteKingSideCastle:
9717 case WhiteQueenSideCastle:
9718 case BlackKingSideCastle:
9719 case BlackQueenSideCastle:
9720 case WhiteKingSideCastleWild:
9721 case WhiteQueenSideCastleWild:
9722 case BlackKingSideCastleWild:
9723 case BlackQueenSideCastleWild:
9725 case WhiteHSideCastleFR:
9726 case WhiteASideCastleFR:
9727 case BlackHSideCastleFR:
9728 case BlackASideCastleFR:
9730 fromX = currentMoveString[0] - AAA;
9731 fromY = currentMoveString[1] - ONE;
9732 toX = currentMoveString[2] - AAA;
9733 toY = currentMoveString[3] - ONE;
9734 promoChar = currentMoveString[4];
9738 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9739 fromX = moveType == WhiteDrop ?
9740 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9741 (int) CharToPiece(ToLower(currentMoveString[0]));
9743 toX = currentMoveString[2] - AAA;
9744 toY = currentMoveString[3] - ONE;
9745 promoChar = NULLCHAR;
9749 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9750 if (appData.debugMode) {
9751 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9752 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9753 setbuf(debugFP, NULL);
9755 DisplayError(buf, 0);
9757 case ImpossibleMove:
9759 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9760 if (appData.debugMode) {
9761 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9762 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9763 setbuf(debugFP, NULL);
9765 DisplayError(buf, 0);
9768 if (boardIndex < backwardMostMove) {
9769 /* Oops, gap. How did that happen? */
9770 DisplayError(_("Gap in move list"), 0);
9773 backwardMostMove = blackPlaysFirst ? 1 : 0;
9774 if (boardIndex > forwardMostMove) {
9775 forwardMostMove = boardIndex;
9779 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9780 strcat(parseList[boardIndex-1], " ");
9781 strcat(parseList[boardIndex-1], yy_text);
9793 case GameUnfinished:
9794 if (gameMode == IcsExamining) {
9795 if (boardIndex < backwardMostMove) {
9796 /* Oops, gap. How did that happen? */
9799 backwardMostMove = blackPlaysFirst ? 1 : 0;
9802 gameInfo.result = moveType;
9803 p = strchr(yy_text, '{');
9804 if (p == NULL) p = strchr(yy_text, '(');
9807 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9809 q = strchr(p, *p == '{' ? '}' : ')');
9810 if (q != NULL) *q = NULLCHAR;
9813 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9814 gameInfo.resultDetails = StrSave(p);
9817 if (boardIndex >= forwardMostMove &&
9818 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9819 backwardMostMove = blackPlaysFirst ? 1 : 0;
9822 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9823 fromY, fromX, toY, toX, promoChar,
9824 parseList[boardIndex]);
9825 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9826 /* currentMoveString is set as a side-effect of yylex */
9827 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9828 strcat(moveList[boardIndex], "\n");
9830 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9831 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9837 if(!IS_SHOGI(gameInfo.variant))
9838 strcat(parseList[boardIndex - 1], "+");
9842 strcat(parseList[boardIndex - 1], "#");
9849 /* Apply a move to the given board */
9851 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9853 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9854 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9856 /* [HGM] compute & store e.p. status and castling rights for new position */
9857 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9859 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9860 oldEP = (signed char)board[EP_STATUS];
9861 board[EP_STATUS] = EP_NONE;
9863 if (fromY == DROP_RANK) {
9865 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9866 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9869 piece = board[toY][toX] = (ChessSquare) fromX;
9871 // ChessSquare victim;
9874 if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9875 // victim = board[killY][killX],
9876 board[killY][killX] = EmptySquare,
9877 board[EP_STATUS] = EP_CAPTURE;
9879 if( board[toY][toX] != EmptySquare ) {
9880 board[EP_STATUS] = EP_CAPTURE;
9881 if( (fromX != toX || fromY != toY) && // not igui!
9882 (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9883 captured == BlackLion && board[fromY][fromX] != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules
9884 board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9888 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9889 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9890 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9892 if( board[fromY][fromX] == WhitePawn ) {
9893 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9894 board[EP_STATUS] = EP_PAWN_MOVE;
9896 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9897 gameInfo.variant != VariantBerolina || toX < fromX)
9898 board[EP_STATUS] = toX | berolina;
9899 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9900 gameInfo.variant != VariantBerolina || toX > fromX)
9901 board[EP_STATUS] = toX;
9904 if( board[fromY][fromX] == BlackPawn ) {
9905 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9906 board[EP_STATUS] = EP_PAWN_MOVE;
9907 if( toY-fromY== -2) {
9908 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9909 gameInfo.variant != VariantBerolina || toX < fromX)
9910 board[EP_STATUS] = toX | berolina;
9911 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9912 gameInfo.variant != VariantBerolina || toX > fromX)
9913 board[EP_STATUS] = toX;
9917 for(i=0; i<nrCastlingRights; i++) {
9918 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9919 board[CASTLING][i] == toX && castlingRank[i] == toY
9920 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9923 if(gameInfo.variant == VariantSChess) { // update virginity
9924 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9925 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9926 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
9927 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
9930 if (fromX == toX && fromY == toY) return;
9932 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9933 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9934 if(gameInfo.variant == VariantKnightmate)
9935 king += (int) WhiteUnicorn - (int) WhiteKing;
9937 /* Code added by Tord: */
9938 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9939 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9940 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9941 board[fromY][fromX] = EmptySquare;
9942 board[toY][toX] = EmptySquare;
9943 if((toX > fromX) != (piece == WhiteRook)) {
9944 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9946 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9948 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9949 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9950 board[fromY][fromX] = EmptySquare;
9951 board[toY][toX] = EmptySquare;
9952 if((toX > fromX) != (piece == BlackRook)) {
9953 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9955 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9957 /* End of code added by Tord */
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_RGHT-1];
9965 board[fromY][BOARD_RGHT-1] = EmptySquare;
9966 } else if (board[fromY][fromX] == king
9967 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9968 && toY == fromY && toX < fromX-1) {
9969 board[fromY][fromX] = EmptySquare;
9970 board[toY][toX] = king;
9971 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9972 board[fromY][BOARD_LEFT] = EmptySquare;
9973 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9974 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9975 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9977 /* white pawn promotion */
9978 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9979 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9980 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9981 board[fromY][fromX] = EmptySquare;
9982 } else if ((fromY >= BOARD_HEIGHT>>1)
9983 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9985 && gameInfo.variant != VariantXiangqi
9986 && gameInfo.variant != VariantBerolina
9987 && (board[fromY][fromX] == WhitePawn)
9988 && (board[toY][toX] == EmptySquare)) {
9989 board[fromY][fromX] = EmptySquare;
9990 board[toY][toX] = WhitePawn;
9991 captured = board[toY - 1][toX];
9992 board[toY - 1][toX] = EmptySquare;
9993 } else if ((fromY == BOARD_HEIGHT-4)
9995 && gameInfo.variant == VariantBerolina
9996 && (board[fromY][fromX] == WhitePawn)
9997 && (board[toY][toX] == EmptySquare)) {
9998 board[fromY][fromX] = EmptySquare;
9999 board[toY][toX] = WhitePawn;
10000 if(oldEP & EP_BEROLIN_A) {
10001 captured = board[fromY][fromX-1];
10002 board[fromY][fromX-1] = EmptySquare;
10003 }else{ captured = board[fromY][fromX+1];
10004 board[fromY][fromX+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_RGHT-1];
10012 board[fromY][BOARD_RGHT-1] = EmptySquare;
10013 } else if (board[fromY][fromX] == king
10014 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10015 && toY == fromY && toX < fromX-1) {
10016 board[fromY][fromX] = EmptySquare;
10017 board[toY][toX] = king;
10018 board[toY][toX+1] = board[fromY][BOARD_LEFT];
10019 board[fromY][BOARD_LEFT] = EmptySquare;
10020 } else if (fromY == 7 && fromX == 3
10021 && board[fromY][fromX] == BlackKing
10022 && toY == 7 && toX == 5) {
10023 board[fromY][fromX] = EmptySquare;
10024 board[toY][toX] = BlackKing;
10025 board[fromY][7] = EmptySquare;
10026 board[toY][4] = BlackRook;
10027 } else if (fromY == 7 && fromX == 3
10028 && board[fromY][fromX] == BlackKing
10029 && toY == 7 && toX == 1) {
10030 board[fromY][fromX] = EmptySquare;
10031 board[toY][toX] = BlackKing;
10032 board[fromY][0] = EmptySquare;
10033 board[toY][2] = BlackRook;
10034 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10035 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10036 && toY < promoRank && promoChar
10038 /* black pawn promotion */
10039 board[toY][toX] = CharToPiece(ToLower(promoChar));
10040 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10041 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10042 board[fromY][fromX] = EmptySquare;
10043 } else if ((fromY < BOARD_HEIGHT>>1)
10044 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
10046 && gameInfo.variant != VariantXiangqi
10047 && gameInfo.variant != VariantBerolina
10048 && (board[fromY][fromX] == BlackPawn)
10049 && (board[toY][toX] == EmptySquare)) {
10050 board[fromY][fromX] = EmptySquare;
10051 board[toY][toX] = BlackPawn;
10052 captured = board[toY + 1][toX];
10053 board[toY + 1][toX] = EmptySquare;
10054 } else if ((fromY == 3)
10056 && gameInfo.variant == VariantBerolina
10057 && (board[fromY][fromX] == BlackPawn)
10058 && (board[toY][toX] == EmptySquare)) {
10059 board[fromY][fromX] = EmptySquare;
10060 board[toY][toX] = BlackPawn;
10061 if(oldEP & EP_BEROLIN_A) {
10062 captured = board[fromY][fromX-1];
10063 board[fromY][fromX-1] = EmptySquare;
10064 }else{ captured = board[fromY][fromX+1];
10065 board[fromY][fromX+1] = EmptySquare;
10068 ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10069 board[fromY][fromX] = EmptySquare;
10070 board[toY][toX] = piece;
10074 if (gameInfo.holdingsWidth != 0) {
10076 /* !!A lot more code needs to be written to support holdings */
10077 /* [HGM] OK, so I have written it. Holdings are stored in the */
10078 /* penultimate board files, so they are automaticlly stored */
10079 /* in the game history. */
10080 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10081 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10082 /* Delete from holdings, by decreasing count */
10083 /* and erasing image if necessary */
10084 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10085 if(p < (int) BlackPawn) { /* white drop */
10086 p -= (int)WhitePawn;
10087 p = PieceToNumber((ChessSquare)p);
10088 if(p >= gameInfo.holdingsSize) p = 0;
10089 if(--board[p][BOARD_WIDTH-2] <= 0)
10090 board[p][BOARD_WIDTH-1] = EmptySquare;
10091 if((int)board[p][BOARD_WIDTH-2] < 0)
10092 board[p][BOARD_WIDTH-2] = 0;
10093 } else { /* black drop */
10094 p -= (int)BlackPawn;
10095 p = PieceToNumber((ChessSquare)p);
10096 if(p >= gameInfo.holdingsSize) p = 0;
10097 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10098 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10099 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10100 board[BOARD_HEIGHT-1-p][1] = 0;
10103 if (captured != EmptySquare && gameInfo.holdingsSize > 0
10104 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
10105 /* [HGM] holdings: Add to holdings, if holdings exist */
10106 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10107 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10108 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10110 p = (int) captured;
10111 if (p >= (int) BlackPawn) {
10112 p -= (int)BlackPawn;
10113 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10114 /* in Shogi restore piece to its original first */
10115 captured = (ChessSquare) (DEMOTED captured);
10118 p = PieceToNumber((ChessSquare)p);
10119 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10120 board[p][BOARD_WIDTH-2]++;
10121 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10123 p -= (int)WhitePawn;
10124 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10125 captured = (ChessSquare) (DEMOTED captured);
10128 p = PieceToNumber((ChessSquare)p);
10129 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10130 board[BOARD_HEIGHT-1-p][1]++;
10131 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10134 } else if (gameInfo.variant == VariantAtomic) {
10135 if (captured != EmptySquare) {
10137 for (y = toY-1; y <= toY+1; y++) {
10138 for (x = toX-1; x <= toX+1; x++) {
10139 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10140 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10141 board[y][x] = EmptySquare;
10145 board[toY][toX] = EmptySquare;
10149 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10150 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10152 if(promoChar == '+') {
10153 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10154 board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10155 if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10156 board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10157 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10158 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10159 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10160 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10161 board[toY][toX] = newPiece;
10163 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10164 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10165 // [HGM] superchess: take promotion piece out of holdings
10166 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10167 if((int)piece < (int)BlackPawn) { // determine stm from piece color
10168 if(!--board[k][BOARD_WIDTH-2])
10169 board[k][BOARD_WIDTH-1] = EmptySquare;
10171 if(!--board[BOARD_HEIGHT-1-k][1])
10172 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10177 /* Updates forwardMostMove */
10179 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10181 int x = toX, y = toY;
10182 char *s = parseList[forwardMostMove];
10183 ChessSquare p = boards[forwardMostMove][toY][toX];
10184 // forwardMostMove++; // [HGM] bare: moved downstream
10186 if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10187 (void) CoordsToAlgebraic(boards[forwardMostMove],
10188 PosFlags(forwardMostMove),
10189 fromY, fromX, y, x, promoChar,
10191 if(killX >= 0 && killY >= 0)
10192 sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10194 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10195 int timeLeft; static int lastLoadFlag=0; int king, piece;
10196 piece = boards[forwardMostMove][fromY][fromX];
10197 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10198 if(gameInfo.variant == VariantKnightmate)
10199 king += (int) WhiteUnicorn - (int) WhiteKing;
10200 if(forwardMostMove == 0) {
10201 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10202 fprintf(serverMoves, "%s;", UserName());
10203 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10204 fprintf(serverMoves, "%s;", second.tidy);
10205 fprintf(serverMoves, "%s;", first.tidy);
10206 if(gameMode == MachinePlaysWhite)
10207 fprintf(serverMoves, "%s;", UserName());
10208 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10209 fprintf(serverMoves, "%s;", second.tidy);
10210 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10211 lastLoadFlag = loadFlag;
10213 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10214 // print castling suffix
10215 if( toY == fromY && piece == king ) {
10217 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10219 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10222 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10223 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
10224 boards[forwardMostMove][toY][toX] == EmptySquare
10225 && fromX != toX && fromY != toY)
10226 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10227 // promotion suffix
10228 if(promoChar != NULLCHAR) {
10229 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10230 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10231 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10232 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10235 char buf[MOVE_LEN*2], *p; int len;
10236 fprintf(serverMoves, "/%d/%d",
10237 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10238 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10239 else timeLeft = blackTimeRemaining/1000;
10240 fprintf(serverMoves, "/%d", timeLeft);
10241 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10242 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10243 if(p = strchr(buf, '=')) *p = NULLCHAR;
10244 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10245 fprintf(serverMoves, "/%s", buf);
10247 fflush(serverMoves);
10250 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10251 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10254 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10255 if (commentList[forwardMostMove+1] != NULL) {
10256 free(commentList[forwardMostMove+1]);
10257 commentList[forwardMostMove+1] = NULL;
10259 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10260 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10261 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10262 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10263 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10264 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10265 adjustedClock = FALSE;
10266 gameInfo.result = GameUnfinished;
10267 if (gameInfo.resultDetails != NULL) {
10268 free(gameInfo.resultDetails);
10269 gameInfo.resultDetails = NULL;
10271 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10272 moveList[forwardMostMove - 1]);
10273 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10279 if(!IS_SHOGI(gameInfo.variant))
10280 strcat(parseList[forwardMostMove - 1], "+");
10284 strcat(parseList[forwardMostMove - 1], "#");
10289 /* Updates currentMove if not pausing */
10291 ShowMove (int fromX, int fromY, int toX, int toY)
10293 int instant = (gameMode == PlayFromGameFile) ?
10294 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10295 if(appData.noGUI) return;
10296 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10298 if (forwardMostMove == currentMove + 1) {
10299 AnimateMove(boards[forwardMostMove - 1],
10300 fromX, fromY, toX, toY);
10303 currentMove = forwardMostMove;
10306 killX = killY = -1; // [HGM] lion: used up
10308 if (instant) return;
10310 DisplayMove(currentMove - 1);
10311 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10312 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10313 SetHighlights(fromX, fromY, toX, toY);
10316 DrawPosition(FALSE, boards[currentMove]);
10317 DisplayBothClocks();
10318 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10322 SendEgtPath (ChessProgramState *cps)
10323 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10324 char buf[MSG_SIZ], name[MSG_SIZ], *p;
10326 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10329 char c, *q = name+1, *r, *s;
10331 name[0] = ','; // extract next format name from feature and copy with prefixed ','
10332 while(*p && *p != ',') *q++ = *p++;
10333 *q++ = ':'; *q = 0;
10334 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10335 strcmp(name, ",nalimov:") == 0 ) {
10336 // take nalimov path from the menu-changeable option first, if it is defined
10337 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10338 SendToProgram(buf,cps); // send egtbpath command for nalimov
10340 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10341 (s = StrStr(appData.egtFormats, name)) != NULL) {
10342 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10343 s = r = StrStr(s, ":") + 1; // beginning of path info
10344 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10345 c = *r; *r = 0; // temporarily null-terminate path info
10346 *--q = 0; // strip of trailig ':' from name
10347 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10349 SendToProgram(buf,cps); // send egtbpath command for this format
10351 if(*p == ',') p++; // read away comma to position for next format name
10356 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10358 int width = 8, height = 8, holdings = 0; // most common sizes
10359 if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10360 // correct the deviations default for each variant
10361 if( v == VariantXiangqi ) width = 9, height = 10;
10362 if( v == VariantShogi ) width = 9, height = 9, holdings = 7;
10363 if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10364 if( v == VariantCapablanca || v == VariantCapaRandom ||
10365 v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10367 if( v == VariantCourier ) width = 12;
10368 if( v == VariantSuper ) holdings = 8;
10369 if( v == VariantGreat ) width = 10, holdings = 8;
10370 if( v == VariantSChess ) holdings = 7;
10371 if( v == VariantGrand ) width = 10, height = 10, holdings = 7;
10372 if( v == VariantChuChess) width = 10, height = 10;
10373 if( v == VariantChu ) width = 12, height = 12;
10374 return boardWidth >= 0 && boardWidth != width || // -1 is default,
10375 boardHeight >= 0 && boardHeight != height || // and thus by definition OK
10376 holdingsSize >= 0 && holdingsSize != holdings;
10379 char variantError[MSG_SIZ];
10382 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10383 { // returns error message (recognizable by upper-case) if engine does not support the variant
10384 char *p, *variant = VariantName(v);
10385 static char b[MSG_SIZ];
10386 if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10387 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10388 holdingsSize, variant); // cook up sized variant name
10389 /* [HGM] varsize: try first if this deviant size variant is specifically known */
10390 if(StrStr(list, b) == NULL) {
10391 // specific sized variant not known, check if general sizing allowed
10392 if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10393 snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10394 boardWidth, boardHeight, holdingsSize, engine);
10397 /* [HGM] here we really should compare with the maximum supported board size */
10399 } else snprintf(b, MSG_SIZ,"%s", variant);
10400 if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10401 p = StrStr(list, b);
10402 while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10404 // occurs not at all in list, or only as sub-string
10405 snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10406 if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10407 int l = strlen(variantError);
10409 while(p != list && p[-1] != ',') p--;
10410 q = strchr(p, ',');
10411 if(q) *q = NULLCHAR;
10412 snprintf(variantError + l, MSG_SIZ - l, _(", but %s is"), p);
10421 InitChessProgram (ChessProgramState *cps, int setup)
10422 /* setup needed to setup FRC opening position */
10424 char buf[MSG_SIZ], *b;
10425 if (appData.noChessProgram) return;
10426 hintRequested = FALSE;
10427 bookRequested = FALSE;
10429 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10430 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10431 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10432 if(cps->memSize) { /* [HGM] memory */
10433 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10434 SendToProgram(buf, cps);
10436 SendEgtPath(cps); /* [HGM] EGT */
10437 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10438 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10439 SendToProgram(buf, cps);
10442 setboardSpoiledMachineBlack = FALSE;
10443 SendToProgram(cps->initString, cps);
10444 if (gameInfo.variant != VariantNormal &&
10445 gameInfo.variant != VariantLoadable
10446 /* [HGM] also send variant if board size non-standard */
10447 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10449 b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10450 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10452 DisplayFatalError(variantError, 0, 1);
10456 snprintf(buf, MSG_SIZ, "variant %s\n", b);
10457 SendToProgram(buf, cps);
10459 currentlyInitializedVariant = gameInfo.variant;
10461 /* [HGM] send opening position in FRC to first engine */
10463 SendToProgram("force\n", cps);
10465 /* engine is now in force mode! Set flag to wake it up after first move. */
10466 setboardSpoiledMachineBlack = 1;
10469 if (cps->sendICS) {
10470 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10471 SendToProgram(buf, cps);
10473 cps->maybeThinking = FALSE;
10474 cps->offeredDraw = 0;
10475 if (!appData.icsActive) {
10476 SendTimeControl(cps, movesPerSession, timeControl,
10477 timeIncrement, appData.searchDepth,
10480 if (appData.showThinking
10481 // [HGM] thinking: four options require thinking output to be sent
10482 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10484 SendToProgram("post\n", cps);
10486 SendToProgram("hard\n", cps);
10487 if (!appData.ponderNextMove) {
10488 /* Warning: "easy" is a toggle in GNU Chess, so don't send
10489 it without being sure what state we are in first. "hard"
10490 is not a toggle, so that one is OK.
10492 SendToProgram("easy\n", cps);
10494 if (cps->usePing) {
10495 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10496 SendToProgram(buf, cps);
10498 cps->initDone = TRUE;
10499 ClearEngineOutputPane(cps == &second);
10504 ResendOptions (ChessProgramState *cps)
10505 { // send the stored value of the options
10508 Option *opt = cps->option;
10509 for(i=0; i<cps->nrOptions; i++, opt++) {
10510 switch(opt->type) {
10514 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10517 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10520 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10526 SendToProgram(buf, cps);
10531 StartChessProgram (ChessProgramState *cps)
10536 if (appData.noChessProgram) return;
10537 cps->initDone = FALSE;
10539 if (strcmp(cps->host, "localhost") == 0) {
10540 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10541 } else if (*appData.remoteShell == NULLCHAR) {
10542 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10544 if (*appData.remoteUser == NULLCHAR) {
10545 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10548 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10549 cps->host, appData.remoteUser, cps->program);
10551 err = StartChildProcess(buf, "", &cps->pr);
10555 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10556 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10557 if(cps != &first) return;
10558 appData.noChessProgram = TRUE;
10561 // DisplayFatalError(buf, err, 1);
10562 // cps->pr = NoProc;
10563 // cps->isr = NULL;
10567 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10568 if (cps->protocolVersion > 1) {
10569 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10570 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10571 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10572 cps->comboCnt = 0; // and values of combo boxes
10574 SendToProgram(buf, cps);
10575 if(cps->reload) ResendOptions(cps);
10577 SendToProgram("xboard\n", cps);
10582 TwoMachinesEventIfReady P((void))
10584 static int curMess = 0;
10585 if (first.lastPing != first.lastPong) {
10586 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10587 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10590 if (second.lastPing != second.lastPong) {
10591 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10592 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10595 DisplayMessage("", ""); curMess = 0;
10596 TwoMachinesEvent();
10600 MakeName (char *template)
10604 static char buf[MSG_SIZ];
10608 clock = time((time_t *)NULL);
10609 tm = localtime(&clock);
10611 while(*p++ = *template++) if(p[-1] == '%') {
10612 switch(*template++) {
10613 case 0: *p = 0; return buf;
10614 case 'Y': i = tm->tm_year+1900; break;
10615 case 'y': i = tm->tm_year-100; break;
10616 case 'M': i = tm->tm_mon+1; break;
10617 case 'd': i = tm->tm_mday; break;
10618 case 'h': i = tm->tm_hour; break;
10619 case 'm': i = tm->tm_min; break;
10620 case 's': i = tm->tm_sec; break;
10623 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10629 CountPlayers (char *p)
10632 while(p = strchr(p, '\n')) p++, n++; // count participants
10637 WriteTourneyFile (char *results, FILE *f)
10638 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10639 if(f == NULL) f = fopen(appData.tourneyFile, "w");
10640 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10641 // create a file with tournament description
10642 fprintf(f, "-participants {%s}\n", appData.participants);
10643 fprintf(f, "-seedBase %d\n", appData.seedBase);
10644 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10645 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10646 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10647 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10648 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10649 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10650 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10651 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10652 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10653 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10654 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10655 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10656 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10657 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10658 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10659 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10660 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10661 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10662 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10663 fprintf(f, "-smpCores %d\n", appData.smpCores);
10665 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10667 fprintf(f, "-mps %d\n", appData.movesPerSession);
10668 fprintf(f, "-tc %s\n", appData.timeControl);
10669 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10671 fprintf(f, "-results \"%s\"\n", results);
10676 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10679 Substitute (char *participants, int expunge)
10681 int i, changed, changes=0, nPlayers=0;
10682 char *p, *q, *r, buf[MSG_SIZ];
10683 if(participants == NULL) return;
10684 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10685 r = p = participants; q = appData.participants;
10686 while(*p && *p == *q) {
10687 if(*p == '\n') r = p+1, nPlayers++;
10690 if(*p) { // difference
10691 while(*p && *p++ != '\n');
10692 while(*q && *q++ != '\n');
10693 changed = nPlayers;
10694 changes = 1 + (strcmp(p, q) != 0);
10696 if(changes == 1) { // a single engine mnemonic was changed
10697 q = r; while(*q) nPlayers += (*q++ == '\n');
10698 p = buf; while(*r && (*p = *r++) != '\n') p++;
10700 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10701 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10702 if(mnemonic[i]) { // The substitute is valid
10704 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10705 flock(fileno(f), LOCK_EX);
10706 ParseArgsFromFile(f);
10707 fseek(f, 0, SEEK_SET);
10708 FREE(appData.participants); appData.participants = participants;
10709 if(expunge) { // erase results of replaced engine
10710 int len = strlen(appData.results), w, b, dummy;
10711 for(i=0; i<len; i++) {
10712 Pairing(i, nPlayers, &w, &b, &dummy);
10713 if((w == changed || b == changed) && appData.results[i] == '*') {
10714 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10719 for(i=0; i<len; i++) {
10720 Pairing(i, nPlayers, &w, &b, &dummy);
10721 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10724 WriteTourneyFile(appData.results, f);
10725 fclose(f); // release lock
10728 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10730 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10731 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10732 free(participants);
10737 CheckPlayers (char *participants)
10740 char buf[MSG_SIZ], *p;
10741 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10742 while(p = strchr(participants, '\n')) {
10744 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10746 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10748 DisplayError(buf, 0);
10752 participants = p + 1;
10758 CreateTourney (char *name)
10761 if(matchMode && strcmp(name, appData.tourneyFile)) {
10762 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10764 if(name[0] == NULLCHAR) {
10765 if(appData.participants[0])
10766 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10769 f = fopen(name, "r");
10770 if(f) { // file exists
10771 ASSIGN(appData.tourneyFile, name);
10772 ParseArgsFromFile(f); // parse it
10774 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10775 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10776 DisplayError(_("Not enough participants"), 0);
10779 if(CheckPlayers(appData.participants)) return 0;
10780 ASSIGN(appData.tourneyFile, name);
10781 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10782 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10785 appData.noChessProgram = FALSE;
10786 appData.clockMode = TRUE;
10792 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10794 char buf[MSG_SIZ], *p, *q;
10795 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10796 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10797 skip = !all && group[0]; // if group requested, we start in skip mode
10798 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10799 p = names; q = buf; header = 0;
10800 while(*p && *p != '\n') *q++ = *p++;
10802 if(*p == '\n') p++;
10803 if(buf[0] == '#') {
10804 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10805 depth++; // we must be entering a new group
10806 if(all) continue; // suppress printing group headers when complete list requested
10808 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10810 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10811 if(engineList[i]) free(engineList[i]);
10812 engineList[i] = strdup(buf);
10813 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10814 if(engineMnemonic[i]) free(engineMnemonic[i]);
10815 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10817 sscanf(q + 8, "%s", buf + strlen(buf));
10820 engineMnemonic[i] = strdup(buf);
10823 engineList[i] = engineMnemonic[i] = NULL;
10827 // following implemented as macro to avoid type limitations
10828 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10831 SwapEngines (int n)
10832 { // swap settings for first engine and other engine (so far only some selected options)
10837 SWAP(chessProgram, p)
10839 SWAP(hasOwnBookUCI, h)
10840 SWAP(protocolVersion, h)
10842 SWAP(scoreIsAbsolute, h)
10847 SWAP(engOptions, p)
10848 SWAP(engInitString, p)
10849 SWAP(computerString, p)
10851 SWAP(fenOverride, p)
10853 SWAP(accumulateTC, h)
10860 GetEngineLine (char *s, int n)
10864 extern char *icsNames;
10865 if(!s || !*s) return 0;
10866 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10867 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10868 if(!mnemonic[i]) return 0;
10869 if(n == 11) return 1; // just testing if there was a match
10870 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10871 if(n == 1) SwapEngines(n);
10872 ParseArgsFromString(buf);
10873 if(n == 1) SwapEngines(n);
10874 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10875 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10876 ParseArgsFromString(buf);
10882 SetPlayer (int player, char *p)
10883 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10885 char buf[MSG_SIZ], *engineName;
10886 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10887 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10888 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10890 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10891 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10892 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10893 ParseArgsFromString(buf);
10894 } else { // no engine with this nickname is installed!
10895 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10896 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10897 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10899 DisplayError(buf, 0);
10906 char *recentEngines;
10909 RecentEngineEvent (int nr)
10912 // SwapEngines(1); // bump first to second
10913 // ReplaceEngine(&second, 1); // and load it there
10914 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10915 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10916 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10917 ReplaceEngine(&first, 0);
10918 FloatToFront(&appData.recentEngineList, command[n]);
10923 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10924 { // determine players from game number
10925 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10927 if(appData.tourneyType == 0) {
10928 roundsPerCycle = (nPlayers - 1) | 1;
10929 pairingsPerRound = nPlayers / 2;
10930 } else if(appData.tourneyType > 0) {
10931 roundsPerCycle = nPlayers - appData.tourneyType;
10932 pairingsPerRound = appData.tourneyType;
10934 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10935 gamesPerCycle = gamesPerRound * roundsPerCycle;
10936 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10937 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10938 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10939 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10940 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10941 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10943 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10944 if(appData.roundSync) *syncInterval = gamesPerRound;
10946 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10948 if(appData.tourneyType == 0) {
10949 if(curPairing == (nPlayers-1)/2 ) {
10950 *whitePlayer = curRound;
10951 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10953 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10954 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10955 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10956 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10958 } else if(appData.tourneyType > 1) {
10959 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10960 *whitePlayer = curRound + appData.tourneyType;
10961 } else if(appData.tourneyType > 0) {
10962 *whitePlayer = curPairing;
10963 *blackPlayer = curRound + appData.tourneyType;
10966 // take care of white/black alternation per round.
10967 // For cycles and games this is already taken care of by default, derived from matchGame!
10968 return curRound & 1;
10972 NextTourneyGame (int nr, int *swapColors)
10973 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10975 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10977 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10978 tf = fopen(appData.tourneyFile, "r");
10979 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10980 ParseArgsFromFile(tf); fclose(tf);
10981 InitTimeControls(); // TC might be altered from tourney file
10983 nPlayers = CountPlayers(appData.participants); // count participants
10984 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10985 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10988 p = q = appData.results;
10989 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10990 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10991 DisplayMessage(_("Waiting for other game(s)"),"");
10992 waitingForGame = TRUE;
10993 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10996 waitingForGame = FALSE;
10999 if(appData.tourneyType < 0) {
11000 if(nr>=0 && !pairingReceived) {
11002 if(pairing.pr == NoProc) {
11003 if(!appData.pairingEngine[0]) {
11004 DisplayFatalError(_("No pairing engine specified"), 0, 1);
11007 StartChessProgram(&pairing); // starts the pairing engine
11009 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11010 SendToProgram(buf, &pairing);
11011 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11012 SendToProgram(buf, &pairing);
11013 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11015 pairingReceived = 0; // ... so we continue here
11017 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11018 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11019 matchGame = 1; roundNr = nr / syncInterval + 1;
11022 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11024 // redefine engines, engine dir, etc.
11025 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11026 if(first.pr == NoProc) {
11027 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11028 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
11030 if(second.pr == NoProc) {
11032 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11033 SwapEngines(1); // and make that valid for second engine by swapping
11034 InitEngine(&second, 1);
11036 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
11037 UpdateLogos(FALSE); // leave display to ModeHiglight()
11043 { // performs game initialization that does not invoke engines, and then tries to start the game
11044 int res, firstWhite, swapColors = 0;
11045 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11046 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
11048 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11049 if(strcmp(buf, currentDebugFile)) { // name has changed
11050 FILE *f = fopen(buf, "w");
11051 if(f) { // if opening the new file failed, just keep using the old one
11052 ASSIGN(currentDebugFile, buf);
11056 if(appData.serverFileName) {
11057 if(serverFP) fclose(serverFP);
11058 serverFP = fopen(appData.serverFileName, "w");
11059 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11060 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11064 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11065 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11066 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
11067 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11068 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11069 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11070 Reset(FALSE, first.pr != NoProc);
11071 res = LoadGameOrPosition(matchGame); // setup game
11072 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11073 if(!res) return; // abort when bad game/pos file
11074 TwoMachinesEvent();
11078 UserAdjudicationEvent (int result)
11080 ChessMove gameResult = GameIsDrawn;
11083 gameResult = WhiteWins;
11085 else if( result < 0 ) {
11086 gameResult = BlackWins;
11089 if( gameMode == TwoMachinesPlay ) {
11090 GameEnds( gameResult, "User adjudication", GE_XBOARD );
11095 // [HGM] save: calculate checksum of game to make games easily identifiable
11097 StringCheckSum (char *s)
11100 if(s==NULL) return 0;
11101 while(*s) i = i*259 + *s++;
11109 for(i=backwardMostMove; i<forwardMostMove; i++) {
11110 sum += pvInfoList[i].depth;
11111 sum += StringCheckSum(parseList[i]);
11112 sum += StringCheckSum(commentList[i]);
11115 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11116 return sum + StringCheckSum(commentList[i]);
11117 } // end of save patch
11120 GameEnds (ChessMove result, char *resultDetails, int whosays)
11122 GameMode nextGameMode;
11124 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11126 if(endingGame) return; /* [HGM] crash: forbid recursion */
11128 if(twoBoards) { // [HGM] dual: switch back to one board
11129 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11130 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11132 if (appData.debugMode) {
11133 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11134 result, resultDetails ? resultDetails : "(null)", whosays);
11137 fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11139 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11141 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11142 /* If we are playing on ICS, the server decides when the
11143 game is over, but the engine can offer to draw, claim
11147 if (appData.zippyPlay && first.initDone) {
11148 if (result == GameIsDrawn) {
11149 /* In case draw still needs to be claimed */
11150 SendToICS(ics_prefix);
11151 SendToICS("draw\n");
11152 } else if (StrCaseStr(resultDetails, "resign")) {
11153 SendToICS(ics_prefix);
11154 SendToICS("resign\n");
11158 endingGame = 0; /* [HGM] crash */
11162 /* If we're loading the game from a file, stop */
11163 if (whosays == GE_FILE) {
11164 (void) StopLoadGameTimer();
11168 /* Cancel draw offers */
11169 first.offeredDraw = second.offeredDraw = 0;
11171 /* If this is an ICS game, only ICS can really say it's done;
11172 if not, anyone can. */
11173 isIcsGame = (gameMode == IcsPlayingWhite ||
11174 gameMode == IcsPlayingBlack ||
11175 gameMode == IcsObserving ||
11176 gameMode == IcsExamining);
11178 if (!isIcsGame || whosays == GE_ICS) {
11179 /* OK -- not an ICS game, or ICS said it was done */
11181 if (!isIcsGame && !appData.noChessProgram)
11182 SetUserThinkingEnables();
11184 /* [HGM] if a machine claims the game end we verify this claim */
11185 if(gameMode == TwoMachinesPlay && appData.testClaims) {
11186 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11188 ChessMove trueResult = (ChessMove) -1;
11190 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
11191 first.twoMachinesColor[0] :
11192 second.twoMachinesColor[0] ;
11194 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11195 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11196 /* [HGM] verify: engine mate claims accepted if they were flagged */
11197 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11199 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11200 /* [HGM] verify: engine mate claims accepted if they were flagged */
11201 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11203 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11204 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11207 // now verify win claims, but not in drop games, as we don't understand those yet
11208 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11209 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11210 (result == WhiteWins && claimer == 'w' ||
11211 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
11212 if (appData.debugMode) {
11213 fprintf(debugFP, "result=%d sp=%d move=%d\n",
11214 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11216 if(result != trueResult) {
11217 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11218 result = claimer == 'w' ? BlackWins : WhiteWins;
11219 resultDetails = buf;
11222 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11223 && (forwardMostMove <= backwardMostMove ||
11224 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11225 (claimer=='b')==(forwardMostMove&1))
11227 /* [HGM] verify: draws that were not flagged are false claims */
11228 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11229 result = claimer == 'w' ? BlackWins : WhiteWins;
11230 resultDetails = buf;
11232 /* (Claiming a loss is accepted no questions asked!) */
11233 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11234 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11235 result = GameUnfinished;
11236 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11238 /* [HGM] bare: don't allow bare King to win */
11239 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11240 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11241 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11242 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11243 && result != GameIsDrawn)
11244 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11245 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11246 int p = (signed char)boards[forwardMostMove][i][j] - color;
11247 if(p >= 0 && p <= (int)WhiteKing) k++;
11249 if (appData.debugMode) {
11250 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11251 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11254 result = GameIsDrawn;
11255 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11256 resultDetails = buf;
11262 if(serverMoves != NULL && !loadFlag) { char c = '=';
11263 if(result==WhiteWins) c = '+';
11264 if(result==BlackWins) c = '-';
11265 if(resultDetails != NULL)
11266 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11268 if (resultDetails != NULL) {
11269 gameInfo.result = result;
11270 gameInfo.resultDetails = StrSave(resultDetails);
11272 /* display last move only if game was not loaded from file */
11273 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11274 DisplayMove(currentMove - 1);
11276 if (forwardMostMove != 0) {
11277 if (gameMode != PlayFromGameFile && gameMode != EditGame
11278 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11280 if (*appData.saveGameFile != NULLCHAR) {
11281 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11282 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11284 SaveGameToFile(appData.saveGameFile, TRUE);
11285 } else if (appData.autoSaveGames) {
11286 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11288 if (*appData.savePositionFile != NULLCHAR) {
11289 SavePositionToFile(appData.savePositionFile);
11291 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11295 /* Tell program how game ended in case it is learning */
11296 /* [HGM] Moved this to after saving the PGN, just in case */
11297 /* engine died and we got here through time loss. In that */
11298 /* case we will get a fatal error writing the pipe, which */
11299 /* would otherwise lose us the PGN. */
11300 /* [HGM] crash: not needed anymore, but doesn't hurt; */
11301 /* output during GameEnds should never be fatal anymore */
11302 if (gameMode == MachinePlaysWhite ||
11303 gameMode == MachinePlaysBlack ||
11304 gameMode == TwoMachinesPlay ||
11305 gameMode == IcsPlayingWhite ||
11306 gameMode == IcsPlayingBlack ||
11307 gameMode == BeginningOfGame) {
11309 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11311 if (first.pr != NoProc) {
11312 SendToProgram(buf, &first);
11314 if (second.pr != NoProc &&
11315 gameMode == TwoMachinesPlay) {
11316 SendToProgram(buf, &second);
11321 if (appData.icsActive) {
11322 if (appData.quietPlay &&
11323 (gameMode == IcsPlayingWhite ||
11324 gameMode == IcsPlayingBlack)) {
11325 SendToICS(ics_prefix);
11326 SendToICS("set shout 1\n");
11328 nextGameMode = IcsIdle;
11329 ics_user_moved = FALSE;
11330 /* clean up premove. It's ugly when the game has ended and the
11331 * premove highlights are still on the board.
11334 gotPremove = FALSE;
11335 ClearPremoveHighlights();
11336 DrawPosition(FALSE, boards[currentMove]);
11338 if (whosays == GE_ICS) {
11341 if (gameMode == IcsPlayingWhite)
11343 else if(gameMode == IcsPlayingBlack)
11344 PlayIcsLossSound();
11347 if (gameMode == IcsPlayingBlack)
11349 else if(gameMode == IcsPlayingWhite)
11350 PlayIcsLossSound();
11353 PlayIcsDrawSound();
11356 PlayIcsUnfinishedSound();
11359 if(appData.quitNext) { ExitEvent(0); return; }
11360 } else if (gameMode == EditGame ||
11361 gameMode == PlayFromGameFile ||
11362 gameMode == AnalyzeMode ||
11363 gameMode == AnalyzeFile) {
11364 nextGameMode = gameMode;
11366 nextGameMode = EndOfGame;
11371 nextGameMode = gameMode;
11374 if (appData.noChessProgram) {
11375 gameMode = nextGameMode;
11377 endingGame = 0; /* [HGM] crash */
11382 /* Put first chess program into idle state */
11383 if (first.pr != NoProc &&
11384 (gameMode == MachinePlaysWhite ||
11385 gameMode == MachinePlaysBlack ||
11386 gameMode == TwoMachinesPlay ||
11387 gameMode == IcsPlayingWhite ||
11388 gameMode == IcsPlayingBlack ||
11389 gameMode == BeginningOfGame)) {
11390 SendToProgram("force\n", &first);
11391 if (first.usePing) {
11393 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11394 SendToProgram(buf, &first);
11397 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11398 /* Kill off first chess program */
11399 if (first.isr != NULL)
11400 RemoveInputSource(first.isr);
11403 if (first.pr != NoProc) {
11405 DoSleep( appData.delayBeforeQuit );
11406 SendToProgram("quit\n", &first);
11407 DestroyChildProcess(first.pr, 4 + first.useSigterm);
11408 first.reload = TRUE;
11412 if (second.reuse) {
11413 /* Put second chess program into idle state */
11414 if (second.pr != NoProc &&
11415 gameMode == TwoMachinesPlay) {
11416 SendToProgram("force\n", &second);
11417 if (second.usePing) {
11419 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11420 SendToProgram(buf, &second);
11423 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11424 /* Kill off second chess program */
11425 if (second.isr != NULL)
11426 RemoveInputSource(second.isr);
11429 if (second.pr != NoProc) {
11430 DoSleep( appData.delayBeforeQuit );
11431 SendToProgram("quit\n", &second);
11432 DestroyChildProcess(second.pr, 4 + second.useSigterm);
11433 second.reload = TRUE;
11435 second.pr = NoProc;
11438 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11439 char resChar = '=';
11443 if (first.twoMachinesColor[0] == 'w') {
11446 second.matchWins++;
11451 if (first.twoMachinesColor[0] == 'b') {
11454 second.matchWins++;
11457 case GameUnfinished:
11463 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11464 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11465 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11466 ReserveGame(nextGame, resChar); // sets nextGame
11467 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11468 else ranking = strdup("busy"); //suppress popup when aborted but not finished
11469 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11471 if (nextGame <= appData.matchGames && !abortMatch) {
11472 gameMode = nextGameMode;
11473 matchGame = nextGame; // this will be overruled in tourney mode!
11474 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11475 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11476 endingGame = 0; /* [HGM] crash */
11479 gameMode = nextGameMode;
11480 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11481 first.tidy, second.tidy,
11482 first.matchWins, second.matchWins,
11483 appData.matchGames - (first.matchWins + second.matchWins));
11484 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11485 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11486 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11487 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11488 first.twoMachinesColor = "black\n";
11489 second.twoMachinesColor = "white\n";
11491 first.twoMachinesColor = "white\n";
11492 second.twoMachinesColor = "black\n";
11496 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11497 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11499 gameMode = nextGameMode;
11501 endingGame = 0; /* [HGM] crash */
11502 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11503 if(matchMode == TRUE) { // match through command line: exit with or without popup
11505 ToNrEvent(forwardMostMove);
11506 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11508 } else DisplayFatalError(buf, 0, 0);
11509 } else { // match through menu; just stop, with or without popup
11510 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11513 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11514 } else DisplayNote(buf);
11516 if(ranking) free(ranking);
11520 /* Assumes program was just initialized (initString sent).
11521 Leaves program in force mode. */
11523 FeedMovesToProgram (ChessProgramState *cps, int upto)
11527 if (appData.debugMode)
11528 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11529 startedFromSetupPosition ? "position and " : "",
11530 backwardMostMove, upto, cps->which);
11531 if(currentlyInitializedVariant != gameInfo.variant) {
11533 // [HGM] variantswitch: make engine aware of new variant
11534 if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11535 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11536 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11537 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11538 SendToProgram(buf, cps);
11539 currentlyInitializedVariant = gameInfo.variant;
11541 SendToProgram("force\n", cps);
11542 if (startedFromSetupPosition) {
11543 SendBoard(cps, backwardMostMove);
11544 if (appData.debugMode) {
11545 fprintf(debugFP, "feedMoves\n");
11548 for (i = backwardMostMove; i < upto; i++) {
11549 SendMoveToProgram(i, cps);
11555 ResurrectChessProgram ()
11557 /* The chess program may have exited.
11558 If so, restart it and feed it all the moves made so far. */
11559 static int doInit = 0;
11561 if (appData.noChessProgram) return 1;
11563 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11564 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11565 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11566 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11568 if (first.pr != NoProc) return 1;
11569 StartChessProgram(&first);
11571 InitChessProgram(&first, FALSE);
11572 FeedMovesToProgram(&first, currentMove);
11574 if (!first.sendTime) {
11575 /* can't tell gnuchess what its clock should read,
11576 so we bow to its notion. */
11578 timeRemaining[0][currentMove] = whiteTimeRemaining;
11579 timeRemaining[1][currentMove] = blackTimeRemaining;
11582 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11583 appData.icsEngineAnalyze) && first.analysisSupport) {
11584 SendToProgram("analyze\n", &first);
11585 first.analyzing = TRUE;
11591 * Button procedures
11594 Reset (int redraw, int init)
11598 if (appData.debugMode) {
11599 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11600 redraw, init, gameMode);
11602 CleanupTail(); // [HGM] vari: delete any stored variations
11603 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11604 pausing = pauseExamInvalid = FALSE;
11605 startedFromSetupPosition = blackPlaysFirst = FALSE;
11607 whiteFlag = blackFlag = FALSE;
11608 userOfferedDraw = FALSE;
11609 hintRequested = bookRequested = FALSE;
11610 first.maybeThinking = FALSE;
11611 second.maybeThinking = FALSE;
11612 first.bookSuspend = FALSE; // [HGM] book
11613 second.bookSuspend = FALSE;
11614 thinkOutput[0] = NULLCHAR;
11615 lastHint[0] = NULLCHAR;
11616 ClearGameInfo(&gameInfo);
11617 gameInfo.variant = StringToVariant(appData.variant);
11618 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11619 ics_user_moved = ics_clock_paused = FALSE;
11620 ics_getting_history = H_FALSE;
11622 white_holding[0] = black_holding[0] = NULLCHAR;
11623 ClearProgramStats();
11624 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11628 flipView = appData.flipView;
11629 ClearPremoveHighlights();
11630 gotPremove = FALSE;
11631 alarmSounded = FALSE;
11632 killX = killY = -1; // [HGM] lion
11634 GameEnds(EndOfFile, NULL, GE_PLAYER);
11635 if(appData.serverMovesName != NULL) {
11636 /* [HGM] prepare to make moves file for broadcasting */
11637 clock_t t = clock();
11638 if(serverMoves != NULL) fclose(serverMoves);
11639 serverMoves = fopen(appData.serverMovesName, "r");
11640 if(serverMoves != NULL) {
11641 fclose(serverMoves);
11642 /* delay 15 sec before overwriting, so all clients can see end */
11643 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11645 serverMoves = fopen(appData.serverMovesName, "w");
11649 gameMode = BeginningOfGame;
11651 if(appData.icsActive) gameInfo.variant = VariantNormal;
11652 currentMove = forwardMostMove = backwardMostMove = 0;
11653 MarkTargetSquares(1);
11654 InitPosition(redraw);
11655 for (i = 0; i < MAX_MOVES; i++) {
11656 if (commentList[i] != NULL) {
11657 free(commentList[i]);
11658 commentList[i] = NULL;
11662 timeRemaining[0][0] = whiteTimeRemaining;
11663 timeRemaining[1][0] = blackTimeRemaining;
11665 if (first.pr == NoProc) {
11666 StartChessProgram(&first);
11669 InitChessProgram(&first, startedFromSetupPosition);
11672 DisplayMessage("", "");
11673 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11674 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11675 ClearMap(); // [HGM] exclude: invalidate map
11679 AutoPlayGameLoop ()
11682 if (!AutoPlayOneMove())
11684 if (matchMode || appData.timeDelay == 0)
11686 if (appData.timeDelay < 0)
11688 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11696 ReloadGame(1); // next game
11702 int fromX, fromY, toX, toY;
11704 if (appData.debugMode) {
11705 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11708 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11711 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11712 pvInfoList[currentMove].depth = programStats.depth;
11713 pvInfoList[currentMove].score = programStats.score;
11714 pvInfoList[currentMove].time = 0;
11715 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11716 else { // append analysis of final position as comment
11718 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11719 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11721 programStats.depth = 0;
11724 if (currentMove >= forwardMostMove) {
11725 if(gameMode == AnalyzeFile) {
11726 if(appData.loadGameIndex == -1) {
11727 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11728 ScheduleDelayedEvent(AnalyzeNextGame, 10);
11730 ExitAnalyzeMode(); SendToProgram("force\n", &first);
11733 // gameMode = EndOfGame;
11734 // ModeHighlight();
11736 /* [AS] Clear current move marker at the end of a game */
11737 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11742 toX = moveList[currentMove][2] - AAA;
11743 toY = moveList[currentMove][3] - ONE;
11745 if (moveList[currentMove][1] == '@') {
11746 if (appData.highlightLastMove) {
11747 SetHighlights(-1, -1, toX, toY);
11750 fromX = moveList[currentMove][0] - AAA;
11751 fromY = moveList[currentMove][1] - ONE;
11753 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11755 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11757 if (appData.highlightLastMove) {
11758 SetHighlights(fromX, fromY, toX, toY);
11761 DisplayMove(currentMove);
11762 SendMoveToProgram(currentMove++, &first);
11763 DisplayBothClocks();
11764 DrawPosition(FALSE, boards[currentMove]);
11765 // [HGM] PV info: always display, routine tests if empty
11766 DisplayComment(currentMove - 1, commentList[currentMove]);
11772 LoadGameOneMove (ChessMove readAhead)
11774 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11775 char promoChar = NULLCHAR;
11776 ChessMove moveType;
11777 char move[MSG_SIZ];
11780 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11781 gameMode != AnalyzeMode && gameMode != Training) {
11786 yyboardindex = forwardMostMove;
11787 if (readAhead != EndOfFile) {
11788 moveType = readAhead;
11790 if (gameFileFP == NULL)
11792 moveType = (ChessMove) Myylex();
11796 switch (moveType) {
11798 if (appData.debugMode)
11799 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11802 /* append the comment but don't display it */
11803 AppendComment(currentMove, p, FALSE);
11806 case WhiteCapturesEnPassant:
11807 case BlackCapturesEnPassant:
11808 case WhitePromotion:
11809 case BlackPromotion:
11810 case WhiteNonPromotion:
11811 case BlackNonPromotion:
11814 case WhiteKingSideCastle:
11815 case WhiteQueenSideCastle:
11816 case BlackKingSideCastle:
11817 case BlackQueenSideCastle:
11818 case WhiteKingSideCastleWild:
11819 case WhiteQueenSideCastleWild:
11820 case BlackKingSideCastleWild:
11821 case BlackQueenSideCastleWild:
11823 case WhiteHSideCastleFR:
11824 case WhiteASideCastleFR:
11825 case BlackHSideCastleFR:
11826 case BlackASideCastleFR:
11828 if (appData.debugMode)
11829 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11830 fromX = currentMoveString[0] - AAA;
11831 fromY = currentMoveString[1] - ONE;
11832 toX = currentMoveString[2] - AAA;
11833 toY = currentMoveString[3] - ONE;
11834 promoChar = currentMoveString[4];
11835 if(promoChar == ';') promoChar = NULLCHAR;
11840 if (appData.debugMode)
11841 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11842 fromX = moveType == WhiteDrop ?
11843 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11844 (int) CharToPiece(ToLower(currentMoveString[0]));
11846 toX = currentMoveString[2] - AAA;
11847 toY = currentMoveString[3] - ONE;
11853 case GameUnfinished:
11854 if (appData.debugMode)
11855 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11856 p = strchr(yy_text, '{');
11857 if (p == NULL) p = strchr(yy_text, '(');
11860 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11862 q = strchr(p, *p == '{' ? '}' : ')');
11863 if (q != NULL) *q = NULLCHAR;
11866 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11867 GameEnds(moveType, p, GE_FILE);
11869 if (cmailMsgLoaded) {
11871 flipView = WhiteOnMove(currentMove);
11872 if (moveType == GameUnfinished) flipView = !flipView;
11873 if (appData.debugMode)
11874 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11879 if (appData.debugMode)
11880 fprintf(debugFP, "Parser hit end of file\n");
11881 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11887 if (WhiteOnMove(currentMove)) {
11888 GameEnds(BlackWins, "Black mates", GE_FILE);
11890 GameEnds(WhiteWins, "White mates", GE_FILE);
11894 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11900 case MoveNumberOne:
11901 if (lastLoadGameStart == GNUChessGame) {
11902 /* GNUChessGames have numbers, but they aren't move numbers */
11903 if (appData.debugMode)
11904 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11905 yy_text, (int) moveType);
11906 return LoadGameOneMove(EndOfFile); /* tail recursion */
11908 /* else fall thru */
11913 /* Reached start of next game in file */
11914 if (appData.debugMode)
11915 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11916 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11922 if (WhiteOnMove(currentMove)) {
11923 GameEnds(BlackWins, "Black mates", GE_FILE);
11925 GameEnds(WhiteWins, "White mates", GE_FILE);
11929 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11935 case PositionDiagram: /* should not happen; ignore */
11936 case ElapsedTime: /* ignore */
11937 case NAG: /* ignore */
11938 if (appData.debugMode)
11939 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11940 yy_text, (int) moveType);
11941 return LoadGameOneMove(EndOfFile); /* tail recursion */
11944 if (appData.testLegality) {
11945 if (appData.debugMode)
11946 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11947 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11948 (forwardMostMove / 2) + 1,
11949 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11950 DisplayError(move, 0);
11953 if (appData.debugMode)
11954 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11955 yy_text, currentMoveString);
11956 fromX = currentMoveString[0] - AAA;
11957 fromY = currentMoveString[1] - ONE;
11958 toX = currentMoveString[2] - AAA;
11959 toY = currentMoveString[3] - ONE;
11960 promoChar = currentMoveString[4];
11964 case AmbiguousMove:
11965 if (appData.debugMode)
11966 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11967 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11968 (forwardMostMove / 2) + 1,
11969 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11970 DisplayError(move, 0);
11975 case ImpossibleMove:
11976 if (appData.debugMode)
11977 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11978 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11979 (forwardMostMove / 2) + 1,
11980 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11981 DisplayError(move, 0);
11987 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11988 DrawPosition(FALSE, boards[currentMove]);
11989 DisplayBothClocks();
11990 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11991 DisplayComment(currentMove - 1, commentList[currentMove]);
11993 (void) StopLoadGameTimer();
11995 cmailOldMove = forwardMostMove;
11998 /* currentMoveString is set as a side-effect of yylex */
12000 thinkOutput[0] = NULLCHAR;
12001 MakeMove(fromX, fromY, toX, toY, promoChar);
12002 killX = killY = -1; // [HGM] lion: used up
12003 currentMove = forwardMostMove;
12008 /* Load the nth game from the given file */
12010 LoadGameFromFile (char *filename, int n, char *title, int useList)
12015 if (strcmp(filename, "-") == 0) {
12019 f = fopen(filename, "rb");
12021 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12022 DisplayError(buf, errno);
12026 if (fseek(f, 0, 0) == -1) {
12027 /* f is not seekable; probably a pipe */
12030 if (useList && n == 0) {
12031 int error = GameListBuild(f);
12033 DisplayError(_("Cannot build game list"), error);
12034 } else if (!ListEmpty(&gameList) &&
12035 ((ListGame *) gameList.tailPred)->number > 1) {
12036 GameListPopUp(f, title);
12043 return LoadGame(f, n, title, FALSE);
12048 MakeRegisteredMove ()
12050 int fromX, fromY, toX, toY;
12052 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12053 switch (cmailMoveType[lastLoadGameNumber - 1]) {
12056 if (appData.debugMode)
12057 fprintf(debugFP, "Restoring %s for game %d\n",
12058 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12060 thinkOutput[0] = NULLCHAR;
12061 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12062 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12063 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12064 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12065 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12066 promoChar = cmailMove[lastLoadGameNumber - 1][4];
12067 MakeMove(fromX, fromY, toX, toY, promoChar);
12068 ShowMove(fromX, fromY, toX, toY);
12070 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12077 if (WhiteOnMove(currentMove)) {
12078 GameEnds(BlackWins, "Black mates", GE_PLAYER);
12080 GameEnds(WhiteWins, "White mates", GE_PLAYER);
12085 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12092 if (WhiteOnMove(currentMove)) {
12093 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12095 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12100 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12111 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12113 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12117 if (gameNumber > nCmailGames) {
12118 DisplayError(_("No more games in this message"), 0);
12121 if (f == lastLoadGameFP) {
12122 int offset = gameNumber - lastLoadGameNumber;
12124 cmailMsg[0] = NULLCHAR;
12125 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12126 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12127 nCmailMovesRegistered--;
12129 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12130 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12131 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12134 if (! RegisterMove()) return FALSE;
12138 retVal = LoadGame(f, gameNumber, title, useList);
12140 /* Make move registered during previous look at this game, if any */
12141 MakeRegisteredMove();
12143 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12144 commentList[currentMove]
12145 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12146 DisplayComment(currentMove - 1, commentList[currentMove]);
12152 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12154 ReloadGame (int offset)
12156 int gameNumber = lastLoadGameNumber + offset;
12157 if (lastLoadGameFP == NULL) {
12158 DisplayError(_("No game has been loaded yet"), 0);
12161 if (gameNumber <= 0) {
12162 DisplayError(_("Can't back up any further"), 0);
12165 if (cmailMsgLoaded) {
12166 return CmailLoadGame(lastLoadGameFP, gameNumber,
12167 lastLoadGameTitle, lastLoadGameUseList);
12169 return LoadGame(lastLoadGameFP, gameNumber,
12170 lastLoadGameTitle, lastLoadGameUseList);
12174 int keys[EmptySquare+1];
12177 PositionMatches (Board b1, Board b2)
12180 switch(appData.searchMode) {
12181 case 1: return CompareWithRights(b1, b2);
12183 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12184 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12188 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12189 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12190 sum += keys[b1[r][f]] - keys[b2[r][f]];
12194 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12195 sum += keys[b1[r][f]] - keys[b2[r][f]];
12207 int pieceList[256], quickBoard[256];
12208 ChessSquare pieceType[256] = { EmptySquare };
12209 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12210 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12211 int soughtTotal, turn;
12212 Boolean epOK, flipSearch;
12215 unsigned char piece, to;
12218 #define DSIZE (250000)
12220 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12221 Move *moveDatabase = initialSpace;
12222 unsigned int movePtr, dataSize = DSIZE;
12225 MakePieceList (Board board, int *counts)
12227 int r, f, n=Q_PROMO, total=0;
12228 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12229 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12230 int sq = f + (r<<4);
12231 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12232 quickBoard[sq] = ++n;
12234 pieceType[n] = board[r][f];
12235 counts[board[r][f]]++;
12236 if(board[r][f] == WhiteKing) pieceList[1] = n; else
12237 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12241 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12246 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12248 int sq = fromX + (fromY<<4);
12249 int piece = quickBoard[sq], rook;
12250 quickBoard[sq] = 0;
12251 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12252 if(piece == pieceList[1] && fromY == toY) {
12253 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12254 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12255 moveDatabase[movePtr++].piece = Q_WCASTL;
12256 quickBoard[sq] = piece;
12257 piece = quickBoard[from]; quickBoard[from] = 0;
12258 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12259 } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12260 quickBoard[sq] = 0; // remove Rook
12261 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12262 moveDatabase[movePtr++].piece = Q_WCASTL;
12263 quickBoard[sq] = pieceList[1]; // put King
12265 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12268 if(piece == pieceList[2] && fromY == toY) {
12269 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12270 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12271 moveDatabase[movePtr++].piece = Q_BCASTL;
12272 quickBoard[sq] = piece;
12273 piece = quickBoard[from]; quickBoard[from] = 0;
12274 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12275 } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12276 quickBoard[sq] = 0; // remove Rook
12277 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12278 moveDatabase[movePtr++].piece = Q_BCASTL;
12279 quickBoard[sq] = pieceList[2]; // put King
12281 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12284 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12285 quickBoard[(fromY<<4)+toX] = 0;
12286 moveDatabase[movePtr].piece = Q_EP;
12287 moveDatabase[movePtr++].to = (fromY<<4)+toX;
12288 moveDatabase[movePtr].to = sq;
12290 if(promoPiece != pieceType[piece]) {
12291 moveDatabase[movePtr++].piece = Q_PROMO;
12292 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12294 moveDatabase[movePtr].piece = piece;
12295 quickBoard[sq] = piece;
12300 PackGame (Board board)
12302 Move *newSpace = NULL;
12303 moveDatabase[movePtr].piece = 0; // terminate previous game
12304 if(movePtr > dataSize) {
12305 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12306 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12307 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12310 Move *p = moveDatabase, *q = newSpace;
12311 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
12312 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12313 moveDatabase = newSpace;
12314 } else { // calloc failed, we must be out of memory. Too bad...
12315 dataSize = 0; // prevent calloc events for all subsequent games
12316 return 0; // and signal this one isn't cached
12320 MakePieceList(board, counts);
12325 QuickCompare (Board board, int *minCounts, int *maxCounts)
12326 { // compare according to search mode
12328 switch(appData.searchMode)
12330 case 1: // exact position match
12331 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12332 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12333 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12336 case 2: // can have extra material on empty squares
12337 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12338 if(board[r][f] == EmptySquare) continue;
12339 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12342 case 3: // material with exact Pawn structure
12343 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12344 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12345 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12346 } // fall through to material comparison
12347 case 4: // exact material
12348 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12350 case 6: // material range with given imbalance
12351 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12352 // fall through to range comparison
12353 case 5: // material range
12354 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12360 QuickScan (Board board, Move *move)
12361 { // reconstruct game,and compare all positions in it
12362 int cnt=0, stretch=0, total = MakePieceList(board, counts);
12364 int piece = move->piece;
12365 int to = move->to, from = pieceList[piece];
12366 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12367 if(!piece) return -1;
12368 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12369 piece = (++move)->piece;
12370 from = pieceList[piece];
12371 counts[pieceType[piece]]--;
12372 pieceType[piece] = (ChessSquare) move->to;
12373 counts[move->to]++;
12374 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12375 counts[pieceType[quickBoard[to]]]--;
12376 quickBoard[to] = 0; total--;
12379 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12380 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12381 from = pieceList[piece]; // so this must be King
12382 quickBoard[from] = 0;
12383 pieceList[piece] = to;
12384 from = pieceList[(++move)->piece]; // for FRC this has to be done here
12385 quickBoard[from] = 0; // rook
12386 quickBoard[to] = piece;
12387 to = move->to; piece = move->piece;
12391 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12392 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12393 quickBoard[from] = 0;
12395 quickBoard[to] = piece;
12396 pieceList[piece] = to;
12398 if(QuickCompare(soughtBoard, minSought, maxSought) ||
12399 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12400 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12401 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12403 static int lastCounts[EmptySquare+1];
12405 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12406 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12407 } else stretch = 0;
12408 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12417 flipSearch = FALSE;
12418 CopyBoard(soughtBoard, boards[currentMove]);
12419 soughtTotal = MakePieceList(soughtBoard, maxSought);
12420 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12421 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12422 CopyBoard(reverseBoard, boards[currentMove]);
12423 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12424 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12425 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12426 reverseBoard[r][f] = piece;
12428 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12429 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12430 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12431 || (boards[currentMove][CASTLING][2] == NoRights ||
12432 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12433 && (boards[currentMove][CASTLING][5] == NoRights ||
12434 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12437 CopyBoard(flipBoard, soughtBoard);
12438 CopyBoard(rotateBoard, reverseBoard);
12439 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12440 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
12441 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12444 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12445 if(appData.searchMode >= 5) {
12446 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12447 MakePieceList(soughtBoard, minSought);
12448 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12450 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12451 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12454 GameInfo dummyInfo;
12455 static int creatingBook;
12458 GameContainsPosition (FILE *f, ListGame *lg)
12460 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12461 int fromX, fromY, toX, toY;
12463 static int initDone=FALSE;
12465 // weed out games based on numerical tag comparison
12466 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12467 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12468 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12469 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12471 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12474 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12475 else CopyBoard(boards[scratch], initialPosition); // default start position
12478 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12479 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12482 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12483 fseek(f, lg->offset, 0);
12486 yyboardindex = scratch;
12487 quickFlag = plyNr+1;
12492 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12498 if(plyNr) return -1; // after we have seen moves, this is for new game
12501 case AmbiguousMove: // we cannot reconstruct the game beyond these two
12502 case ImpossibleMove:
12503 case WhiteWins: // game ends here with these four
12506 case GameUnfinished:
12510 if(appData.testLegality) return -1;
12511 case WhiteCapturesEnPassant:
12512 case BlackCapturesEnPassant:
12513 case WhitePromotion:
12514 case BlackPromotion:
12515 case WhiteNonPromotion:
12516 case BlackNonPromotion:
12519 case WhiteKingSideCastle:
12520 case WhiteQueenSideCastle:
12521 case BlackKingSideCastle:
12522 case BlackQueenSideCastle:
12523 case WhiteKingSideCastleWild:
12524 case WhiteQueenSideCastleWild:
12525 case BlackKingSideCastleWild:
12526 case BlackQueenSideCastleWild:
12527 case WhiteHSideCastleFR:
12528 case WhiteASideCastleFR:
12529 case BlackHSideCastleFR:
12530 case BlackASideCastleFR:
12531 fromX = currentMoveString[0] - AAA;
12532 fromY = currentMoveString[1] - ONE;
12533 toX = currentMoveString[2] - AAA;
12534 toY = currentMoveString[3] - ONE;
12535 promoChar = currentMoveString[4];
12539 fromX = next == WhiteDrop ?
12540 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12541 (int) CharToPiece(ToLower(currentMoveString[0]));
12543 toX = currentMoveString[2] - AAA;
12544 toY = currentMoveString[3] - ONE;
12548 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12550 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12551 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12552 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12553 if(appData.findMirror) {
12554 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12555 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12560 /* Load the nth game from open file f */
12562 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12566 int gn = gameNumber;
12567 ListGame *lg = NULL;
12568 int numPGNTags = 0;
12570 GameMode oldGameMode;
12571 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12573 if (appData.debugMode)
12574 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12576 if (gameMode == Training )
12577 SetTrainingModeOff();
12579 oldGameMode = gameMode;
12580 if (gameMode != BeginningOfGame) {
12581 Reset(FALSE, TRUE);
12583 killX = killY = -1; // [HGM] lion: in case we did not Reset
12586 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12587 fclose(lastLoadGameFP);
12591 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12594 fseek(f, lg->offset, 0);
12595 GameListHighlight(gameNumber);
12596 pos = lg->position;
12600 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12601 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12603 DisplayError(_("Game number out of range"), 0);
12608 if (fseek(f, 0, 0) == -1) {
12609 if (f == lastLoadGameFP ?
12610 gameNumber == lastLoadGameNumber + 1 :
12614 DisplayError(_("Can't seek on game file"), 0);
12619 lastLoadGameFP = f;
12620 lastLoadGameNumber = gameNumber;
12621 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12622 lastLoadGameUseList = useList;
12626 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12627 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12628 lg->gameInfo.black);
12630 } else if (*title != NULLCHAR) {
12631 if (gameNumber > 1) {
12632 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12635 DisplayTitle(title);
12639 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12640 gameMode = PlayFromGameFile;
12644 currentMove = forwardMostMove = backwardMostMove = 0;
12645 CopyBoard(boards[0], initialPosition);
12649 * Skip the first gn-1 games in the file.
12650 * Also skip over anything that precedes an identifiable
12651 * start of game marker, to avoid being confused by
12652 * garbage at the start of the file. Currently
12653 * recognized start of game markers are the move number "1",
12654 * the pattern "gnuchess .* game", the pattern
12655 * "^[#;%] [^ ]* game file", and a PGN tag block.
12656 * A game that starts with one of the latter two patterns
12657 * will also have a move number 1, possibly
12658 * following a position diagram.
12659 * 5-4-02: Let's try being more lenient and allowing a game to
12660 * start with an unnumbered move. Does that break anything?
12662 cm = lastLoadGameStart = EndOfFile;
12664 yyboardindex = forwardMostMove;
12665 cm = (ChessMove) Myylex();
12668 if (cmailMsgLoaded) {
12669 nCmailGames = CMAIL_MAX_GAMES - gn;
12672 DisplayError(_("Game not found in file"), 0);
12679 lastLoadGameStart = cm;
12682 case MoveNumberOne:
12683 switch (lastLoadGameStart) {
12688 case MoveNumberOne:
12690 gn--; /* count this game */
12691 lastLoadGameStart = cm;
12700 switch (lastLoadGameStart) {
12703 case MoveNumberOne:
12705 gn--; /* count this game */
12706 lastLoadGameStart = cm;
12709 lastLoadGameStart = cm; /* game counted already */
12717 yyboardindex = forwardMostMove;
12718 cm = (ChessMove) Myylex();
12719 } while (cm == PGNTag || cm == Comment);
12726 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12727 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
12728 != CMAIL_OLD_RESULT) {
12730 cmailResult[ CMAIL_MAX_GAMES
12731 - gn - 1] = CMAIL_OLD_RESULT;
12738 /* Only a NormalMove can be at the start of a game
12739 * without a position diagram. */
12740 if (lastLoadGameStart == EndOfFile ) {
12742 lastLoadGameStart = MoveNumberOne;
12751 if (appData.debugMode)
12752 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12754 if (cm == XBoardGame) {
12755 /* Skip any header junk before position diagram and/or move 1 */
12757 yyboardindex = forwardMostMove;
12758 cm = (ChessMove) Myylex();
12760 if (cm == EndOfFile ||
12761 cm == GNUChessGame || cm == XBoardGame) {
12762 /* Empty game; pretend end-of-file and handle later */
12767 if (cm == MoveNumberOne || cm == PositionDiagram ||
12768 cm == PGNTag || cm == Comment)
12771 } else if (cm == GNUChessGame) {
12772 if (gameInfo.event != NULL) {
12773 free(gameInfo.event);
12775 gameInfo.event = StrSave(yy_text);
12778 startedFromSetupPosition = FALSE;
12779 while (cm == PGNTag) {
12780 if (appData.debugMode)
12781 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12782 err = ParsePGNTag(yy_text, &gameInfo);
12783 if (!err) numPGNTags++;
12785 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12786 if(gameInfo.variant != oldVariant) {
12787 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12788 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12789 InitPosition(TRUE);
12790 oldVariant = gameInfo.variant;
12791 if (appData.debugMode)
12792 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12796 if (gameInfo.fen != NULL) {
12797 Board initial_position;
12798 startedFromSetupPosition = TRUE;
12799 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12801 DisplayError(_("Bad FEN position in file"), 0);
12804 CopyBoard(boards[0], initial_position);
12805 if (blackPlaysFirst) {
12806 currentMove = forwardMostMove = backwardMostMove = 1;
12807 CopyBoard(boards[1], initial_position);
12808 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12809 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12810 timeRemaining[0][1] = whiteTimeRemaining;
12811 timeRemaining[1][1] = blackTimeRemaining;
12812 if (commentList[0] != NULL) {
12813 commentList[1] = commentList[0];
12814 commentList[0] = NULL;
12817 currentMove = forwardMostMove = backwardMostMove = 0;
12819 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12821 initialRulePlies = FENrulePlies;
12822 for( i=0; i< nrCastlingRights; i++ )
12823 initialRights[i] = initial_position[CASTLING][i];
12825 yyboardindex = forwardMostMove;
12826 free(gameInfo.fen);
12827 gameInfo.fen = NULL;
12830 yyboardindex = forwardMostMove;
12831 cm = (ChessMove) Myylex();
12833 /* Handle comments interspersed among the tags */
12834 while (cm == Comment) {
12836 if (appData.debugMode)
12837 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12839 AppendComment(currentMove, p, FALSE);
12840 yyboardindex = forwardMostMove;
12841 cm = (ChessMove) Myylex();
12845 /* don't rely on existence of Event tag since if game was
12846 * pasted from clipboard the Event tag may not exist
12848 if (numPGNTags > 0){
12850 if (gameInfo.variant == VariantNormal) {
12851 VariantClass v = StringToVariant(gameInfo.event);
12852 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12853 if(v < VariantShogi) gameInfo.variant = v;
12856 if( appData.autoDisplayTags ) {
12857 tags = PGNTags(&gameInfo);
12858 TagsPopUp(tags, CmailMsg());
12863 /* Make something up, but don't display it now */
12868 if (cm == PositionDiagram) {
12871 Board initial_position;
12873 if (appData.debugMode)
12874 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12876 if (!startedFromSetupPosition) {
12878 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12879 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12890 initial_position[i][j++] = CharToPiece(*p);
12893 while (*p == ' ' || *p == '\t' ||
12894 *p == '\n' || *p == '\r') p++;
12896 if (strncmp(p, "black", strlen("black"))==0)
12897 blackPlaysFirst = TRUE;
12899 blackPlaysFirst = FALSE;
12900 startedFromSetupPosition = TRUE;
12902 CopyBoard(boards[0], initial_position);
12903 if (blackPlaysFirst) {
12904 currentMove = forwardMostMove = backwardMostMove = 1;
12905 CopyBoard(boards[1], initial_position);
12906 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12907 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12908 timeRemaining[0][1] = whiteTimeRemaining;
12909 timeRemaining[1][1] = blackTimeRemaining;
12910 if (commentList[0] != NULL) {
12911 commentList[1] = commentList[0];
12912 commentList[0] = NULL;
12915 currentMove = forwardMostMove = backwardMostMove = 0;
12918 yyboardindex = forwardMostMove;
12919 cm = (ChessMove) Myylex();
12922 if(!creatingBook) {
12923 if (first.pr == NoProc) {
12924 StartChessProgram(&first);
12926 InitChessProgram(&first, FALSE);
12927 SendToProgram("force\n", &first);
12928 if (startedFromSetupPosition) {
12929 SendBoard(&first, forwardMostMove);
12930 if (appData.debugMode) {
12931 fprintf(debugFP, "Load Game\n");
12933 DisplayBothClocks();
12937 /* [HGM] server: flag to write setup moves in broadcast file as one */
12938 loadFlag = appData.suppressLoadMoves;
12940 while (cm == Comment) {
12942 if (appData.debugMode)
12943 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12945 AppendComment(currentMove, p, FALSE);
12946 yyboardindex = forwardMostMove;
12947 cm = (ChessMove) Myylex();
12950 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12951 cm == WhiteWins || cm == BlackWins ||
12952 cm == GameIsDrawn || cm == GameUnfinished) {
12953 DisplayMessage("", _("No moves in game"));
12954 if (cmailMsgLoaded) {
12955 if (appData.debugMode)
12956 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12960 DrawPosition(FALSE, boards[currentMove]);
12961 DisplayBothClocks();
12962 gameMode = EditGame;
12969 // [HGM] PV info: routine tests if comment empty
12970 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12971 DisplayComment(currentMove - 1, commentList[currentMove]);
12973 if (!matchMode && appData.timeDelay != 0)
12974 DrawPosition(FALSE, boards[currentMove]);
12976 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12977 programStats.ok_to_send = 1;
12980 /* if the first token after the PGN tags is a move
12981 * and not move number 1, retrieve it from the parser
12983 if (cm != MoveNumberOne)
12984 LoadGameOneMove(cm);
12986 /* load the remaining moves from the file */
12987 while (LoadGameOneMove(EndOfFile)) {
12988 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12989 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12992 /* rewind to the start of the game */
12993 currentMove = backwardMostMove;
12995 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12997 if (oldGameMode == AnalyzeFile) {
12998 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12999 AnalyzeFileEvent();
13001 if (oldGameMode == AnalyzeMode) {
13002 AnalyzeFileEvent();
13005 if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13006 long int w, b; // [HGM] adjourn: restore saved clock times
13007 char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13008 if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13009 timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13010 timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13014 if(creatingBook) return TRUE;
13015 if (!matchMode && pos > 0) {
13016 ToNrEvent(pos); // [HGM] no autoplay if selected on position
13018 if (matchMode || appData.timeDelay == 0) {
13020 } else if (appData.timeDelay > 0) {
13021 AutoPlayGameLoop();
13024 if (appData.debugMode)
13025 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13027 loadFlag = 0; /* [HGM] true game starts */
13031 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13033 ReloadPosition (int offset)
13035 int positionNumber = lastLoadPositionNumber + offset;
13036 if (lastLoadPositionFP == NULL) {
13037 DisplayError(_("No position has been loaded yet"), 0);
13040 if (positionNumber <= 0) {
13041 DisplayError(_("Can't back up any further"), 0);
13044 return LoadPosition(lastLoadPositionFP, positionNumber,
13045 lastLoadPositionTitle);
13048 /* Load the nth position from the given file */
13050 LoadPositionFromFile (char *filename, int n, char *title)
13055 if (strcmp(filename, "-") == 0) {
13056 return LoadPosition(stdin, n, "stdin");
13058 f = fopen(filename, "rb");
13060 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13061 DisplayError(buf, errno);
13064 return LoadPosition(f, n, title);
13069 /* Load the nth position from the given open file, and close it */
13071 LoadPosition (FILE *f, int positionNumber, char *title)
13073 char *p, line[MSG_SIZ];
13074 Board initial_position;
13075 int i, j, fenMode, pn;
13077 if (gameMode == Training )
13078 SetTrainingModeOff();
13080 if (gameMode != BeginningOfGame) {
13081 Reset(FALSE, TRUE);
13083 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13084 fclose(lastLoadPositionFP);
13086 if (positionNumber == 0) positionNumber = 1;
13087 lastLoadPositionFP = f;
13088 lastLoadPositionNumber = positionNumber;
13089 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13090 if (first.pr == NoProc && !appData.noChessProgram) {
13091 StartChessProgram(&first);
13092 InitChessProgram(&first, FALSE);
13094 pn = positionNumber;
13095 if (positionNumber < 0) {
13096 /* Negative position number means to seek to that byte offset */
13097 if (fseek(f, -positionNumber, 0) == -1) {
13098 DisplayError(_("Can't seek on position file"), 0);
13103 if (fseek(f, 0, 0) == -1) {
13104 if (f == lastLoadPositionFP ?
13105 positionNumber == lastLoadPositionNumber + 1 :
13106 positionNumber == 1) {
13109 DisplayError(_("Can't seek on position file"), 0);
13114 /* See if this file is FEN or old-style xboard */
13115 if (fgets(line, MSG_SIZ, f) == NULL) {
13116 DisplayError(_("Position not found in file"), 0);
13119 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13120 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13123 if (fenMode || line[0] == '#') pn--;
13125 /* skip positions before number pn */
13126 if (fgets(line, MSG_SIZ, f) == NULL) {
13128 DisplayError(_("Position not found in file"), 0);
13131 if (fenMode || line[0] == '#') pn--;
13136 if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13137 DisplayError(_("Bad FEN position in file"), 0);
13141 (void) fgets(line, MSG_SIZ, f);
13142 (void) fgets(line, MSG_SIZ, f);
13144 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13145 (void) fgets(line, MSG_SIZ, f);
13146 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13149 initial_position[i][j++] = CharToPiece(*p);
13153 blackPlaysFirst = FALSE;
13155 (void) fgets(line, MSG_SIZ, f);
13156 if (strncmp(line, "black", strlen("black"))==0)
13157 blackPlaysFirst = TRUE;
13160 startedFromSetupPosition = TRUE;
13162 CopyBoard(boards[0], initial_position);
13163 if (blackPlaysFirst) {
13164 currentMove = forwardMostMove = backwardMostMove = 1;
13165 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13166 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13167 CopyBoard(boards[1], initial_position);
13168 DisplayMessage("", _("Black to play"));
13170 currentMove = forwardMostMove = backwardMostMove = 0;
13171 DisplayMessage("", _("White to play"));
13173 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13174 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13175 SendToProgram("force\n", &first);
13176 SendBoard(&first, forwardMostMove);
13178 if (appData.debugMode) {
13180 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13181 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13182 fprintf(debugFP, "Load Position\n");
13185 if (positionNumber > 1) {
13186 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13187 DisplayTitle(line);
13189 DisplayTitle(title);
13191 gameMode = EditGame;
13194 timeRemaining[0][1] = whiteTimeRemaining;
13195 timeRemaining[1][1] = blackTimeRemaining;
13196 DrawPosition(FALSE, boards[currentMove]);
13203 CopyPlayerNameIntoFileName (char **dest, char *src)
13205 while (*src != NULLCHAR && *src != ',') {
13210 *(*dest)++ = *src++;
13216 DefaultFileName (char *ext)
13218 static char def[MSG_SIZ];
13221 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13223 CopyPlayerNameIntoFileName(&p, gameInfo.white);
13225 CopyPlayerNameIntoFileName(&p, gameInfo.black);
13227 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13234 /* Save the current game to the given file */
13236 SaveGameToFile (char *filename, int append)
13240 int result, i, t,tot=0;
13242 if (strcmp(filename, "-") == 0) {
13243 return SaveGame(stdout, 0, NULL);
13245 for(i=0; i<10; i++) { // upto 10 tries
13246 f = fopen(filename, append ? "a" : "w");
13247 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13248 if(f || errno != 13) break;
13249 DoSleep(t = 5 + random()%11); // wait 5-15 msec
13253 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13254 DisplayError(buf, errno);
13257 safeStrCpy(buf, lastMsg, MSG_SIZ);
13258 DisplayMessage(_("Waiting for access to save file"), "");
13259 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13260 DisplayMessage(_("Saving game"), "");
13261 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
13262 result = SaveGame(f, 0, NULL);
13263 DisplayMessage(buf, "");
13270 SavePart (char *str)
13272 static char buf[MSG_SIZ];
13275 p = strchr(str, ' ');
13276 if (p == NULL) return str;
13277 strncpy(buf, str, p - str);
13278 buf[p - str] = NULLCHAR;
13282 #define PGN_MAX_LINE 75
13284 #define PGN_SIDE_WHITE 0
13285 #define PGN_SIDE_BLACK 1
13288 FindFirstMoveOutOfBook (int side)
13292 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13293 int index = backwardMostMove;
13294 int has_book_hit = 0;
13296 if( (index % 2) != side ) {
13300 while( index < forwardMostMove ) {
13301 /* Check to see if engine is in book */
13302 int depth = pvInfoList[index].depth;
13303 int score = pvInfoList[index].score;
13309 else if( score == 0 && depth == 63 ) {
13310 in_book = 1; /* Zappa */
13312 else if( score == 2 && depth == 99 ) {
13313 in_book = 1; /* Abrok */
13316 has_book_hit += in_book;
13332 GetOutOfBookInfo (char * buf)
13336 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13338 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13339 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13343 if( oob[0] >= 0 || oob[1] >= 0 ) {
13344 for( i=0; i<2; i++ ) {
13348 if( i > 0 && oob[0] >= 0 ) {
13349 strcat( buf, " " );
13352 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13353 sprintf( buf+strlen(buf), "%s%.2f",
13354 pvInfoList[idx].score >= 0 ? "+" : "",
13355 pvInfoList[idx].score / 100.0 );
13361 /* Save game in PGN style and close the file */
13363 SaveGamePGN (FILE *f)
13365 int i, offset, linelen, newblock;
13368 int movelen, numlen, blank;
13369 char move_buffer[100]; /* [AS] Buffer for move+PV info */
13371 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13373 PrintPGNTags(f, &gameInfo);
13375 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13377 if (backwardMostMove > 0 || startedFromSetupPosition) {
13378 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13379 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13380 fprintf(f, "\n{--------------\n");
13381 PrintPosition(f, backwardMostMove);
13382 fprintf(f, "--------------}\n");
13386 /* [AS] Out of book annotation */
13387 if( appData.saveOutOfBookInfo ) {
13390 GetOutOfBookInfo( buf );
13392 if( buf[0] != '\0' ) {
13393 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13400 i = backwardMostMove;
13404 while (i < forwardMostMove) {
13405 /* Print comments preceding this move */
13406 if (commentList[i] != NULL) {
13407 if (linelen > 0) fprintf(f, "\n");
13408 fprintf(f, "%s", commentList[i]);
13413 /* Format move number */
13415 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13418 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13420 numtext[0] = NULLCHAR;
13422 numlen = strlen(numtext);
13425 /* Print move number */
13426 blank = linelen > 0 && numlen > 0;
13427 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13436 fprintf(f, "%s", numtext);
13440 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13441 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13444 blank = linelen > 0 && movelen > 0;
13445 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13454 fprintf(f, "%s", move_buffer);
13455 linelen += movelen;
13457 /* [AS] Add PV info if present */
13458 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13459 /* [HGM] add time */
13460 char buf[MSG_SIZ]; int seconds;
13462 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13468 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13471 seconds = (seconds + 4)/10; // round to full seconds
13473 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13475 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13478 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13479 pvInfoList[i].score >= 0 ? "+" : "",
13480 pvInfoList[i].score / 100.0,
13481 pvInfoList[i].depth,
13484 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13486 /* Print score/depth */
13487 blank = linelen > 0 && movelen > 0;
13488 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13497 fprintf(f, "%s", move_buffer);
13498 linelen += movelen;
13504 /* Start a new line */
13505 if (linelen > 0) fprintf(f, "\n");
13507 /* Print comments after last move */
13508 if (commentList[i] != NULL) {
13509 fprintf(f, "%s\n", commentList[i]);
13513 if (gameInfo.resultDetails != NULL &&
13514 gameInfo.resultDetails[0] != NULLCHAR) {
13515 char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13516 if(gameInfo.result == GameUnfinished && appData.clockMode &&
13517 (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13518 snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13519 fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13521 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13525 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13529 /* Save game in old style and close the file */
13531 SaveGameOldStyle (FILE *f)
13536 tm = time((time_t *) NULL);
13538 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13541 if (backwardMostMove > 0 || startedFromSetupPosition) {
13542 fprintf(f, "\n[--------------\n");
13543 PrintPosition(f, backwardMostMove);
13544 fprintf(f, "--------------]\n");
13549 i = backwardMostMove;
13550 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13552 while (i < forwardMostMove) {
13553 if (commentList[i] != NULL) {
13554 fprintf(f, "[%s]\n", commentList[i]);
13557 if ((i % 2) == 1) {
13558 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
13561 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
13563 if (commentList[i] != NULL) {
13567 if (i >= forwardMostMove) {
13571 fprintf(f, "%s\n", parseList[i]);
13576 if (commentList[i] != NULL) {
13577 fprintf(f, "[%s]\n", commentList[i]);
13580 /* This isn't really the old style, but it's close enough */
13581 if (gameInfo.resultDetails != NULL &&
13582 gameInfo.resultDetails[0] != NULLCHAR) {
13583 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13584 gameInfo.resultDetails);
13586 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13593 /* Save the current game to open file f and close the file */
13595 SaveGame (FILE *f, int dummy, char *dummy2)
13597 if (gameMode == EditPosition) EditPositionDone(TRUE);
13598 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13599 if (appData.oldSaveStyle)
13600 return SaveGameOldStyle(f);
13602 return SaveGamePGN(f);
13605 /* Save the current position to the given file */
13607 SavePositionToFile (char *filename)
13612 if (strcmp(filename, "-") == 0) {
13613 return SavePosition(stdout, 0, NULL);
13615 f = fopen(filename, "a");
13617 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13618 DisplayError(buf, errno);
13621 safeStrCpy(buf, lastMsg, MSG_SIZ);
13622 DisplayMessage(_("Waiting for access to save file"), "");
13623 flock(fileno(f), LOCK_EX); // [HGM] lock
13624 DisplayMessage(_("Saving position"), "");
13625 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
13626 SavePosition(f, 0, NULL);
13627 DisplayMessage(buf, "");
13633 /* Save the current position to the given open file and close the file */
13635 SavePosition (FILE *f, int dummy, char *dummy2)
13640 if (gameMode == EditPosition) EditPositionDone(TRUE);
13641 if (appData.oldSaveStyle) {
13642 tm = time((time_t *) NULL);
13644 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13646 fprintf(f, "[--------------\n");
13647 PrintPosition(f, currentMove);
13648 fprintf(f, "--------------]\n");
13650 fen = PositionToFEN(currentMove, NULL, 1);
13651 fprintf(f, "%s\n", fen);
13659 ReloadCmailMsgEvent (int unregister)
13662 static char *inFilename = NULL;
13663 static char *outFilename;
13665 struct stat inbuf, outbuf;
13668 /* Any registered moves are unregistered if unregister is set, */
13669 /* i.e. invoked by the signal handler */
13671 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13672 cmailMoveRegistered[i] = FALSE;
13673 if (cmailCommentList[i] != NULL) {
13674 free(cmailCommentList[i]);
13675 cmailCommentList[i] = NULL;
13678 nCmailMovesRegistered = 0;
13681 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13682 cmailResult[i] = CMAIL_NOT_RESULT;
13686 if (inFilename == NULL) {
13687 /* Because the filenames are static they only get malloced once */
13688 /* and they never get freed */
13689 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13690 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13692 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13693 sprintf(outFilename, "%s.out", appData.cmailGameName);
13696 status = stat(outFilename, &outbuf);
13698 cmailMailedMove = FALSE;
13700 status = stat(inFilename, &inbuf);
13701 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13704 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13705 counts the games, notes how each one terminated, etc.
13707 It would be nice to remove this kludge and instead gather all
13708 the information while building the game list. (And to keep it
13709 in the game list nodes instead of having a bunch of fixed-size
13710 parallel arrays.) Note this will require getting each game's
13711 termination from the PGN tags, as the game list builder does
13712 not process the game moves. --mann
13714 cmailMsgLoaded = TRUE;
13715 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13717 /* Load first game in the file or popup game menu */
13718 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13720 #endif /* !WIN32 */
13728 char string[MSG_SIZ];
13730 if ( cmailMailedMove
13731 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13732 return TRUE; /* Allow free viewing */
13735 /* Unregister move to ensure that we don't leave RegisterMove */
13736 /* with the move registered when the conditions for registering no */
13738 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13739 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13740 nCmailMovesRegistered --;
13742 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13744 free(cmailCommentList[lastLoadGameNumber - 1]);
13745 cmailCommentList[lastLoadGameNumber - 1] = NULL;
13749 if (cmailOldMove == -1) {
13750 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13754 if (currentMove > cmailOldMove + 1) {
13755 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13759 if (currentMove < cmailOldMove) {
13760 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13764 if (forwardMostMove > currentMove) {
13765 /* Silently truncate extra moves */
13769 if ( (currentMove == cmailOldMove + 1)
13770 || ( (currentMove == cmailOldMove)
13771 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13772 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13773 if (gameInfo.result != GameUnfinished) {
13774 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13777 if (commentList[currentMove] != NULL) {
13778 cmailCommentList[lastLoadGameNumber - 1]
13779 = StrSave(commentList[currentMove]);
13781 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13783 if (appData.debugMode)
13784 fprintf(debugFP, "Saving %s for game %d\n",
13785 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13787 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13789 f = fopen(string, "w");
13790 if (appData.oldSaveStyle) {
13791 SaveGameOldStyle(f); /* also closes the file */
13793 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13794 f = fopen(string, "w");
13795 SavePosition(f, 0, NULL); /* also closes the file */
13797 fprintf(f, "{--------------\n");
13798 PrintPosition(f, currentMove);
13799 fprintf(f, "--------------}\n\n");
13801 SaveGame(f, 0, NULL); /* also closes the file*/
13804 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13805 nCmailMovesRegistered ++;
13806 } else if (nCmailGames == 1) {
13807 DisplayError(_("You have not made a move yet"), 0);
13818 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13819 FILE *commandOutput;
13820 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13821 int nBytes = 0; /* Suppress warnings on uninitialized variables */
13827 if (! cmailMsgLoaded) {
13828 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13832 if (nCmailGames == nCmailResults) {
13833 DisplayError(_("No unfinished games"), 0);
13837 #if CMAIL_PROHIBIT_REMAIL
13838 if (cmailMailedMove) {
13839 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);
13840 DisplayError(msg, 0);
13845 if (! (cmailMailedMove || RegisterMove())) return;
13847 if ( cmailMailedMove
13848 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13849 snprintf(string, MSG_SIZ, partCommandString,
13850 appData.debugMode ? " -v" : "", appData.cmailGameName);
13851 commandOutput = popen(string, "r");
13853 if (commandOutput == NULL) {
13854 DisplayError(_("Failed to invoke cmail"), 0);
13856 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13857 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13859 if (nBuffers > 1) {
13860 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13861 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13862 nBytes = MSG_SIZ - 1;
13864 (void) memcpy(msg, buffer, nBytes);
13866 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13868 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13869 cmailMailedMove = TRUE; /* Prevent >1 moves */
13872 for (i = 0; i < nCmailGames; i ++) {
13873 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13878 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13880 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13882 appData.cmailGameName,
13884 LoadGameFromFile(buffer, 1, buffer, FALSE);
13885 cmailMsgLoaded = FALSE;
13889 DisplayInformation(msg);
13890 pclose(commandOutput);
13893 if ((*cmailMsg) != '\0') {
13894 DisplayInformation(cmailMsg);
13899 #endif /* !WIN32 */
13908 int prependComma = 0;
13910 char string[MSG_SIZ]; /* Space for game-list */
13913 if (!cmailMsgLoaded) return "";
13915 if (cmailMailedMove) {
13916 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13918 /* Create a list of games left */
13919 snprintf(string, MSG_SIZ, "[");
13920 for (i = 0; i < nCmailGames; i ++) {
13921 if (! ( cmailMoveRegistered[i]
13922 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13923 if (prependComma) {
13924 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13926 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13930 strcat(string, number);
13933 strcat(string, "]");
13935 if (nCmailMovesRegistered + nCmailResults == 0) {
13936 switch (nCmailGames) {
13938 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13942 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13946 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13951 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13953 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13958 if (nCmailResults == nCmailGames) {
13959 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13961 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13966 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13978 if (gameMode == Training)
13979 SetTrainingModeOff();
13982 cmailMsgLoaded = FALSE;
13983 if (appData.icsActive) {
13984 SendToICS(ics_prefix);
13985 SendToICS("refresh\n");
13990 ExitEvent (int status)
13994 /* Give up on clean exit */
13998 /* Keep trying for clean exit */
14002 if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14003 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14005 if (telnetISR != NULL) {
14006 RemoveInputSource(telnetISR);
14008 if (icsPR != NoProc) {
14009 DestroyChildProcess(icsPR, TRUE);
14012 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14013 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14015 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14016 /* make sure this other one finishes before killing it! */
14017 if(endingGame) { int count = 0;
14018 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14019 while(endingGame && count++ < 10) DoSleep(1);
14020 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14023 /* Kill off chess programs */
14024 if (first.pr != NoProc) {
14027 DoSleep( appData.delayBeforeQuit );
14028 SendToProgram("quit\n", &first);
14029 DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14031 if (second.pr != NoProc) {
14032 DoSleep( appData.delayBeforeQuit );
14033 SendToProgram("quit\n", &second);
14034 DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14036 if (first.isr != NULL) {
14037 RemoveInputSource(first.isr);
14039 if (second.isr != NULL) {
14040 RemoveInputSource(second.isr);
14043 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14044 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14046 ShutDownFrontEnd();
14051 PauseEngine (ChessProgramState *cps)
14053 SendToProgram("pause\n", cps);
14058 UnPauseEngine (ChessProgramState *cps)
14060 SendToProgram("resume\n", cps);
14067 if (appData.debugMode)
14068 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14072 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14074 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14075 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14076 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14078 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14079 HandleMachineMove(stashedInputMove, stalledEngine);
14080 stalledEngine = NULL;
14083 if (gameMode == MachinePlaysWhite ||
14084 gameMode == TwoMachinesPlay ||
14085 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14086 if(first.pause) UnPauseEngine(&first);
14087 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14088 if(second.pause) UnPauseEngine(&second);
14089 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14092 DisplayBothClocks();
14094 if (gameMode == PlayFromGameFile) {
14095 if (appData.timeDelay >= 0)
14096 AutoPlayGameLoop();
14097 } else if (gameMode == IcsExamining && pauseExamInvalid) {
14098 Reset(FALSE, TRUE);
14099 SendToICS(ics_prefix);
14100 SendToICS("refresh\n");
14101 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14102 ForwardInner(forwardMostMove);
14104 pauseExamInvalid = FALSE;
14106 switch (gameMode) {
14110 pauseExamForwardMostMove = forwardMostMove;
14111 pauseExamInvalid = FALSE;
14114 case IcsPlayingWhite:
14115 case IcsPlayingBlack:
14119 case PlayFromGameFile:
14120 (void) StopLoadGameTimer();
14124 case BeginningOfGame:
14125 if (appData.icsActive) return;
14126 /* else fall through */
14127 case MachinePlaysWhite:
14128 case MachinePlaysBlack:
14129 case TwoMachinesPlay:
14130 if (forwardMostMove == 0)
14131 return; /* don't pause if no one has moved */
14132 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14133 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14134 if(onMove->pause) { // thinking engine can be paused
14135 PauseEngine(onMove); // do it
14136 if(onMove->other->pause) // pondering opponent can always be paused immediately
14137 PauseEngine(onMove->other);
14139 SendToProgram("easy\n", onMove->other);
14141 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14142 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14144 PauseEngine(&first);
14146 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14147 } else { // human on move, pause pondering by either method
14149 PauseEngine(&first);
14150 else if(appData.ponderNextMove)
14151 SendToProgram("easy\n", &first);
14154 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14164 EditCommentEvent ()
14166 char title[MSG_SIZ];
14168 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14169 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14171 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14172 WhiteOnMove(currentMove - 1) ? " " : ".. ",
14173 parseList[currentMove - 1]);
14176 EditCommentPopUp(currentMove, title, commentList[currentMove]);
14183 char *tags = PGNTags(&gameInfo);
14185 EditTagsPopUp(tags, NULL);
14192 if(second.analyzing) {
14193 SendToProgram("exit\n", &second);
14194 second.analyzing = FALSE;
14196 if (second.pr == NoProc) StartChessProgram(&second);
14197 InitChessProgram(&second, FALSE);
14198 FeedMovesToProgram(&second, currentMove);
14200 SendToProgram("analyze\n", &second);
14201 second.analyzing = TRUE;
14205 /* Toggle ShowThinking */
14207 ToggleShowThinking()
14209 appData.showThinking = !appData.showThinking;
14210 ShowThinkingEvent();
14214 AnalyzeModeEvent ()
14218 if (!first.analysisSupport) {
14219 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14220 DisplayError(buf, 0);
14223 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14224 if (appData.icsActive) {
14225 if (gameMode != IcsObserving) {
14226 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14227 DisplayError(buf, 0);
14229 if (appData.icsEngineAnalyze) {
14230 if (appData.debugMode)
14231 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14237 /* if enable, user wants to disable icsEngineAnalyze */
14238 if (appData.icsEngineAnalyze) {
14243 appData.icsEngineAnalyze = TRUE;
14244 if (appData.debugMode)
14245 fprintf(debugFP, "ICS engine analyze starting... \n");
14248 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14249 if (appData.noChessProgram || gameMode == AnalyzeMode)
14252 if (gameMode != AnalyzeFile) {
14253 if (!appData.icsEngineAnalyze) {
14255 if (gameMode != EditGame) return 0;
14257 if (!appData.showThinking) ToggleShowThinking();
14258 ResurrectChessProgram();
14259 SendToProgram("analyze\n", &first);
14260 first.analyzing = TRUE;
14261 /*first.maybeThinking = TRUE;*/
14262 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14263 EngineOutputPopUp();
14265 if (!appData.icsEngineAnalyze) {
14266 gameMode = AnalyzeMode;
14267 ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14273 StartAnalysisClock();
14274 GetTimeMark(&lastNodeCountTime);
14280 AnalyzeFileEvent ()
14282 if (appData.noChessProgram || gameMode == AnalyzeFile)
14285 if (!first.analysisSupport) {
14287 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14288 DisplayError(buf, 0);
14292 if (gameMode != AnalyzeMode) {
14293 keepInfo = 1; // mere annotating should not alter PGN tags
14296 if (gameMode != EditGame) return;
14297 if (!appData.showThinking) ToggleShowThinking();
14298 ResurrectChessProgram();
14299 SendToProgram("analyze\n", &first);
14300 first.analyzing = TRUE;
14301 /*first.maybeThinking = TRUE;*/
14302 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14303 EngineOutputPopUp();
14305 gameMode = AnalyzeFile;
14309 StartAnalysisClock();
14310 GetTimeMark(&lastNodeCountTime);
14312 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14313 AnalysisPeriodicEvent(1);
14317 MachineWhiteEvent ()
14320 char *bookHit = NULL;
14322 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14326 if (gameMode == PlayFromGameFile ||
14327 gameMode == TwoMachinesPlay ||
14328 gameMode == Training ||
14329 gameMode == AnalyzeMode ||
14330 gameMode == EndOfGame)
14333 if (gameMode == EditPosition)
14334 EditPositionDone(TRUE);
14336 if (!WhiteOnMove(currentMove)) {
14337 DisplayError(_("It is not White's turn"), 0);
14341 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14344 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14345 gameMode == AnalyzeFile)
14348 ResurrectChessProgram(); /* in case it isn't running */
14349 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14350 gameMode = MachinePlaysWhite;
14353 gameMode = MachinePlaysWhite;
14357 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14359 if (first.sendName) {
14360 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14361 SendToProgram(buf, &first);
14363 if (first.sendTime) {
14364 if (first.useColors) {
14365 SendToProgram("black\n", &first); /*gnu kludge*/
14367 SendTimeRemaining(&first, TRUE);
14369 if (first.useColors) {
14370 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14372 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14373 SetMachineThinkingEnables();
14374 first.maybeThinking = TRUE;
14378 if (appData.autoFlipView && !flipView) {
14379 flipView = !flipView;
14380 DrawPosition(FALSE, NULL);
14381 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14384 if(bookHit) { // [HGM] book: simulate book reply
14385 static char bookMove[MSG_SIZ]; // a bit generous?
14387 programStats.nodes = programStats.depth = programStats.time =
14388 programStats.score = programStats.got_only_move = 0;
14389 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14391 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14392 strcat(bookMove, bookHit);
14393 HandleMachineMove(bookMove, &first);
14398 MachineBlackEvent ()
14401 char *bookHit = NULL;
14403 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14407 if (gameMode == PlayFromGameFile ||
14408 gameMode == TwoMachinesPlay ||
14409 gameMode == Training ||
14410 gameMode == AnalyzeMode ||
14411 gameMode == EndOfGame)
14414 if (gameMode == EditPosition)
14415 EditPositionDone(TRUE);
14417 if (WhiteOnMove(currentMove)) {
14418 DisplayError(_("It is not Black's turn"), 0);
14422 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14425 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14426 gameMode == AnalyzeFile)
14429 ResurrectChessProgram(); /* in case it isn't running */
14430 gameMode = MachinePlaysBlack;
14434 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14436 if (first.sendName) {
14437 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14438 SendToProgram(buf, &first);
14440 if (first.sendTime) {
14441 if (first.useColors) {
14442 SendToProgram("white\n", &first); /*gnu kludge*/
14444 SendTimeRemaining(&first, FALSE);
14446 if (first.useColors) {
14447 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14449 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14450 SetMachineThinkingEnables();
14451 first.maybeThinking = TRUE;
14454 if (appData.autoFlipView && flipView) {
14455 flipView = !flipView;
14456 DrawPosition(FALSE, NULL);
14457 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14459 if(bookHit) { // [HGM] book: simulate book reply
14460 static char bookMove[MSG_SIZ]; // a bit generous?
14462 programStats.nodes = programStats.depth = programStats.time =
14463 programStats.score = programStats.got_only_move = 0;
14464 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14466 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14467 strcat(bookMove, bookHit);
14468 HandleMachineMove(bookMove, &first);
14474 DisplayTwoMachinesTitle ()
14477 if (appData.matchGames > 0) {
14478 if(appData.tourneyFile[0]) {
14479 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14480 gameInfo.white, _("vs."), gameInfo.black,
14481 nextGame+1, appData.matchGames+1,
14482 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14484 if (first.twoMachinesColor[0] == 'w') {
14485 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14486 gameInfo.white, _("vs."), gameInfo.black,
14487 first.matchWins, second.matchWins,
14488 matchGame - 1 - (first.matchWins + second.matchWins));
14490 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14491 gameInfo.white, _("vs."), gameInfo.black,
14492 second.matchWins, first.matchWins,
14493 matchGame - 1 - (first.matchWins + second.matchWins));
14496 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14502 SettingsMenuIfReady ()
14504 if (second.lastPing != second.lastPong) {
14505 DisplayMessage("", _("Waiting for second chess program"));
14506 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14510 DisplayMessage("", "");
14511 SettingsPopUp(&second);
14515 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14518 if (cps->pr == NoProc) {
14519 StartChessProgram(cps);
14520 if (cps->protocolVersion == 1) {
14522 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14524 /* kludge: allow timeout for initial "feature" command */
14525 if(retry != TwoMachinesEventIfReady) FreezeUI();
14526 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14527 DisplayMessage("", buf);
14528 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14536 TwoMachinesEvent P((void))
14540 ChessProgramState *onmove;
14541 char *bookHit = NULL;
14542 static int stalling = 0;
14546 if (appData.noChessProgram) return;
14548 switch (gameMode) {
14549 case TwoMachinesPlay:
14551 case MachinePlaysWhite:
14552 case MachinePlaysBlack:
14553 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14554 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14558 case BeginningOfGame:
14559 case PlayFromGameFile:
14562 if (gameMode != EditGame) return;
14565 EditPositionDone(TRUE);
14576 // forwardMostMove = currentMove;
14577 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14578 startingEngine = TRUE;
14580 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14582 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14583 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14584 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14587 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14589 if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14590 gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14591 startingEngine = FALSE;
14592 DisplayError("second engine does not play this", 0);
14597 InitChessProgram(&second, FALSE); // unbalances ping of second engine
14598 SendToProgram("force\n", &second);
14600 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14603 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14604 if(appData.matchPause>10000 || appData.matchPause<10)
14605 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14606 wait = SubtractTimeMarks(&now, &pauseStart);
14607 if(wait < appData.matchPause) {
14608 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14611 // we are now committed to starting the game
14613 DisplayMessage("", "");
14614 if (startedFromSetupPosition) {
14615 SendBoard(&second, backwardMostMove);
14616 if (appData.debugMode) {
14617 fprintf(debugFP, "Two Machines\n");
14620 for (i = backwardMostMove; i < forwardMostMove; i++) {
14621 SendMoveToProgram(i, &second);
14624 gameMode = TwoMachinesPlay;
14625 pausing = startingEngine = FALSE;
14626 ModeHighlight(); // [HGM] logo: this triggers display update of logos
14628 DisplayTwoMachinesTitle();
14630 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14635 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14636 SendToProgram(first.computerString, &first);
14637 if (first.sendName) {
14638 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14639 SendToProgram(buf, &first);
14641 SendToProgram(second.computerString, &second);
14642 if (second.sendName) {
14643 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14644 SendToProgram(buf, &second);
14648 if (!first.sendTime || !second.sendTime) {
14649 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14650 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14652 if (onmove->sendTime) {
14653 if (onmove->useColors) {
14654 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14656 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14658 if (onmove->useColors) {
14659 SendToProgram(onmove->twoMachinesColor, onmove);
14661 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14662 // SendToProgram("go\n", onmove);
14663 onmove->maybeThinking = TRUE;
14664 SetMachineThinkingEnables();
14668 if(bookHit) { // [HGM] book: simulate book reply
14669 static char bookMove[MSG_SIZ]; // a bit generous?
14671 programStats.nodes = programStats.depth = programStats.time =
14672 programStats.score = programStats.got_only_move = 0;
14673 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14675 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14676 strcat(bookMove, bookHit);
14677 savedMessage = bookMove; // args for deferred call
14678 savedState = onmove;
14679 ScheduleDelayedEvent(DeferredBookMove, 1);
14686 if (gameMode == Training) {
14687 SetTrainingModeOff();
14688 gameMode = PlayFromGameFile;
14689 DisplayMessage("", _("Training mode off"));
14691 gameMode = Training;
14692 animateTraining = appData.animate;
14694 /* make sure we are not already at the end of the game */
14695 if (currentMove < forwardMostMove) {
14696 SetTrainingModeOn();
14697 DisplayMessage("", _("Training mode on"));
14699 gameMode = PlayFromGameFile;
14700 DisplayError(_("Already at end of game"), 0);
14709 if (!appData.icsActive) return;
14710 switch (gameMode) {
14711 case IcsPlayingWhite:
14712 case IcsPlayingBlack:
14715 case BeginningOfGame:
14723 EditPositionDone(TRUE);
14736 gameMode = IcsIdle;
14746 switch (gameMode) {
14748 SetTrainingModeOff();
14750 case MachinePlaysWhite:
14751 case MachinePlaysBlack:
14752 case BeginningOfGame:
14753 SendToProgram("force\n", &first);
14754 SetUserThinkingEnables();
14756 case PlayFromGameFile:
14757 (void) StopLoadGameTimer();
14758 if (gameFileFP != NULL) {
14763 EditPositionDone(TRUE);
14768 SendToProgram("force\n", &first);
14770 case TwoMachinesPlay:
14771 GameEnds(EndOfFile, NULL, GE_PLAYER);
14772 ResurrectChessProgram();
14773 SetUserThinkingEnables();
14776 ResurrectChessProgram();
14778 case IcsPlayingBlack:
14779 case IcsPlayingWhite:
14780 DisplayError(_("Warning: You are still playing a game"), 0);
14783 DisplayError(_("Warning: You are still observing a game"), 0);
14786 DisplayError(_("Warning: You are still examining a game"), 0);
14797 first.offeredDraw = second.offeredDraw = 0;
14799 if (gameMode == PlayFromGameFile) {
14800 whiteTimeRemaining = timeRemaining[0][currentMove];
14801 blackTimeRemaining = timeRemaining[1][currentMove];
14805 if (gameMode == MachinePlaysWhite ||
14806 gameMode == MachinePlaysBlack ||
14807 gameMode == TwoMachinesPlay ||
14808 gameMode == EndOfGame) {
14809 i = forwardMostMove;
14810 while (i > currentMove) {
14811 SendToProgram("undo\n", &first);
14814 if(!adjustedClock) {
14815 whiteTimeRemaining = timeRemaining[0][currentMove];
14816 blackTimeRemaining = timeRemaining[1][currentMove];
14817 DisplayBothClocks();
14819 if (whiteFlag || blackFlag) {
14820 whiteFlag = blackFlag = 0;
14825 gameMode = EditGame;
14832 EditPositionEvent ()
14834 if (gameMode == EditPosition) {
14840 if (gameMode != EditGame) return;
14842 gameMode = EditPosition;
14845 if (currentMove > 0)
14846 CopyBoard(boards[0], boards[currentMove]);
14848 blackPlaysFirst = !WhiteOnMove(currentMove);
14850 currentMove = forwardMostMove = backwardMostMove = 0;
14851 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14853 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14859 /* [DM] icsEngineAnalyze - possible call from other functions */
14860 if (appData.icsEngineAnalyze) {
14861 appData.icsEngineAnalyze = FALSE;
14863 DisplayMessage("",_("Close ICS engine analyze..."));
14865 if (first.analysisSupport && first.analyzing) {
14866 SendToBoth("exit\n");
14867 first.analyzing = second.analyzing = FALSE;
14869 thinkOutput[0] = NULLCHAR;
14873 EditPositionDone (Boolean fakeRights)
14875 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14877 startedFromSetupPosition = TRUE;
14878 InitChessProgram(&first, FALSE);
14879 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14880 boards[0][EP_STATUS] = EP_NONE;
14881 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14882 if(boards[0][0][BOARD_WIDTH>>1] == king) {
14883 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14884 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14885 } else boards[0][CASTLING][2] = NoRights;
14886 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14887 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14888 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14889 } else boards[0][CASTLING][5] = NoRights;
14890 if(gameInfo.variant == VariantSChess) {
14892 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14893 boards[0][VIRGIN][i] = 0;
14894 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14895 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14899 SendToProgram("force\n", &first);
14900 if (blackPlaysFirst) {
14901 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14902 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14903 currentMove = forwardMostMove = backwardMostMove = 1;
14904 CopyBoard(boards[1], boards[0]);
14906 currentMove = forwardMostMove = backwardMostMove = 0;
14908 SendBoard(&first, forwardMostMove);
14909 if (appData.debugMode) {
14910 fprintf(debugFP, "EditPosDone\n");
14913 DisplayMessage("", "");
14914 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14915 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14916 gameMode = EditGame;
14918 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14919 ClearHighlights(); /* [AS] */
14922 /* Pause for `ms' milliseconds */
14923 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14925 TimeDelay (long ms)
14932 } while (SubtractTimeMarks(&m2, &m1) < ms);
14935 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14937 SendMultiLineToICS (char *buf)
14939 char temp[MSG_SIZ+1], *p;
14946 strncpy(temp, buf, len);
14951 if (*p == '\n' || *p == '\r')
14956 strcat(temp, "\n");
14958 SendToPlayer(temp, strlen(temp));
14962 SetWhiteToPlayEvent ()
14964 if (gameMode == EditPosition) {
14965 blackPlaysFirst = FALSE;
14966 DisplayBothClocks(); /* works because currentMove is 0 */
14967 } else if (gameMode == IcsExamining) {
14968 SendToICS(ics_prefix);
14969 SendToICS("tomove white\n");
14974 SetBlackToPlayEvent ()
14976 if (gameMode == EditPosition) {
14977 blackPlaysFirst = TRUE;
14978 currentMove = 1; /* kludge */
14979 DisplayBothClocks();
14981 } else if (gameMode == IcsExamining) {
14982 SendToICS(ics_prefix);
14983 SendToICS("tomove black\n");
14988 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14991 ChessSquare piece = boards[0][y][x];
14992 static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14993 static int lastVariant;
14995 if (gameMode != EditPosition && gameMode != IcsExamining) return;
14997 switch (selection) {
14999 CopyBoard(currentBoard, boards[0]);
15000 CopyBoard(menuBoard, initialPosition);
15001 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15002 SendToICS(ics_prefix);
15003 SendToICS("bsetup clear\n");
15004 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15005 SendToICS(ics_prefix);
15006 SendToICS("clearboard\n");
15009 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15010 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15011 for (y = 0; y < BOARD_HEIGHT; y++) {
15012 if (gameMode == IcsExamining) {
15013 if (boards[currentMove][y][x] != EmptySquare) {
15014 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15019 if(boards[0][y][x] != p) nonEmpty++;
15020 boards[0][y][x] = p;
15024 if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15026 for(r = 0; r < BOARD_HEIGHT; r++) {
15027 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
15028 ChessSquare p = menuBoard[r][x];
15029 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15032 DisplayMessage("Clicking clock again restores position", "");
15033 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15034 if(!nonEmpty) { // asked to clear an empty board
15035 CopyBoard(boards[0], menuBoard);
15037 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15038 CopyBoard(boards[0], initialPosition);
15040 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15041 && !CompareBoards(nullBoard, erasedBoard)) {
15042 CopyBoard(boards[0], erasedBoard);
15044 CopyBoard(erasedBoard, currentBoard);
15048 if (gameMode == EditPosition) {
15049 DrawPosition(FALSE, boards[0]);
15054 SetWhiteToPlayEvent();
15058 SetBlackToPlayEvent();
15062 if (gameMode == IcsExamining) {
15063 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15064 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15067 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15068 if(x == BOARD_LEFT-2) {
15069 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15070 boards[0][y][1] = 0;
15072 if(x == BOARD_RGHT+1) {
15073 if(y >= gameInfo.holdingsSize) break;
15074 boards[0][y][BOARD_WIDTH-2] = 0;
15077 boards[0][y][x] = EmptySquare;
15078 DrawPosition(FALSE, boards[0]);
15083 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15084 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
15085 selection = (ChessSquare) (PROMOTED piece);
15086 } else if(piece == EmptySquare) selection = WhiteSilver;
15087 else selection = (ChessSquare)((int)piece - 1);
15091 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15092 piece > (int)BlackMan && piece <= (int)BlackKing ) {
15093 selection = (ChessSquare) (DEMOTED piece);
15094 } else if(piece == EmptySquare) selection = BlackSilver;
15095 else selection = (ChessSquare)((int)piece + 1);
15100 if(gameInfo.variant == VariantShatranj ||
15101 gameInfo.variant == VariantXiangqi ||
15102 gameInfo.variant == VariantCourier ||
15103 gameInfo.variant == VariantASEAN ||
15104 gameInfo.variant == VariantMakruk )
15105 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15110 if(gameInfo.variant == VariantXiangqi)
15111 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15112 if(gameInfo.variant == VariantKnightmate)
15113 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15116 if (gameMode == IcsExamining) {
15117 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15118 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15119 PieceToChar(selection), AAA + x, ONE + y);
15122 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15124 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15125 n = PieceToNumber(selection - BlackPawn);
15126 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15127 boards[0][BOARD_HEIGHT-1-n][0] = selection;
15128 boards[0][BOARD_HEIGHT-1-n][1]++;
15130 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15131 n = PieceToNumber(selection);
15132 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15133 boards[0][n][BOARD_WIDTH-1] = selection;
15134 boards[0][n][BOARD_WIDTH-2]++;
15137 boards[0][y][x] = selection;
15138 DrawPosition(TRUE, boards[0]);
15140 fromX = fromY = -1;
15148 DropMenuEvent (ChessSquare selection, int x, int y)
15150 ChessMove moveType;
15152 switch (gameMode) {
15153 case IcsPlayingWhite:
15154 case MachinePlaysBlack:
15155 if (!WhiteOnMove(currentMove)) {
15156 DisplayMoveError(_("It is Black's turn"));
15159 moveType = WhiteDrop;
15161 case IcsPlayingBlack:
15162 case MachinePlaysWhite:
15163 if (WhiteOnMove(currentMove)) {
15164 DisplayMoveError(_("It is White's turn"));
15167 moveType = BlackDrop;
15170 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15176 if (moveType == BlackDrop && selection < BlackPawn) {
15177 selection = (ChessSquare) ((int) selection
15178 + (int) BlackPawn - (int) WhitePawn);
15180 if (boards[currentMove][y][x] != EmptySquare) {
15181 DisplayMoveError(_("That square is occupied"));
15185 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15191 /* Accept a pending offer of any kind from opponent */
15193 if (appData.icsActive) {
15194 SendToICS(ics_prefix);
15195 SendToICS("accept\n");
15196 } else if (cmailMsgLoaded) {
15197 if (currentMove == cmailOldMove &&
15198 commentList[cmailOldMove] != NULL &&
15199 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15200 "Black offers a draw" : "White offers a draw")) {
15202 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15203 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15205 DisplayError(_("There is no pending offer on this move"), 0);
15206 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15209 /* Not used for offers from chess program */
15216 /* Decline a pending offer of any kind from opponent */
15218 if (appData.icsActive) {
15219 SendToICS(ics_prefix);
15220 SendToICS("decline\n");
15221 } else if (cmailMsgLoaded) {
15222 if (currentMove == cmailOldMove &&
15223 commentList[cmailOldMove] != NULL &&
15224 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15225 "Black offers a draw" : "White offers a draw")) {
15227 AppendComment(cmailOldMove, "Draw declined", TRUE);
15228 DisplayComment(cmailOldMove - 1, "Draw declined");
15231 DisplayError(_("There is no pending offer on this move"), 0);
15234 /* Not used for offers from chess program */
15241 /* Issue ICS rematch command */
15242 if (appData.icsActive) {
15243 SendToICS(ics_prefix);
15244 SendToICS("rematch\n");
15251 /* Call your opponent's flag (claim a win on time) */
15252 if (appData.icsActive) {
15253 SendToICS(ics_prefix);
15254 SendToICS("flag\n");
15256 switch (gameMode) {
15259 case MachinePlaysWhite:
15262 GameEnds(GameIsDrawn, "Both players ran out of time",
15265 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15267 DisplayError(_("Your opponent is not out of time"), 0);
15270 case MachinePlaysBlack:
15273 GameEnds(GameIsDrawn, "Both players ran out of time",
15276 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15278 DisplayError(_("Your opponent is not out of time"), 0);
15286 ClockClick (int which)
15287 { // [HGM] code moved to back-end from winboard.c
15288 if(which) { // black clock
15289 if (gameMode == EditPosition || gameMode == IcsExamining) {
15290 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15291 SetBlackToPlayEvent();
15292 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15293 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15294 } else if (shiftKey) {
15295 AdjustClock(which, -1);
15296 } else if (gameMode == IcsPlayingWhite ||
15297 gameMode == MachinePlaysBlack) {
15300 } else { // white clock
15301 if (gameMode == EditPosition || gameMode == IcsExamining) {
15302 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15303 SetWhiteToPlayEvent();
15304 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15305 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15306 } else if (shiftKey) {
15307 AdjustClock(which, -1);
15308 } else if (gameMode == IcsPlayingBlack ||
15309 gameMode == MachinePlaysWhite) {
15318 /* Offer draw or accept pending draw offer from opponent */
15320 if (appData.icsActive) {
15321 /* Note: tournament rules require draw offers to be
15322 made after you make your move but before you punch
15323 your clock. Currently ICS doesn't let you do that;
15324 instead, you immediately punch your clock after making
15325 a move, but you can offer a draw at any time. */
15327 SendToICS(ics_prefix);
15328 SendToICS("draw\n");
15329 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15330 } else if (cmailMsgLoaded) {
15331 if (currentMove == cmailOldMove &&
15332 commentList[cmailOldMove] != NULL &&
15333 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15334 "Black offers a draw" : "White offers a draw")) {
15335 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15336 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15337 } else if (currentMove == cmailOldMove + 1) {
15338 char *offer = WhiteOnMove(cmailOldMove) ?
15339 "White offers a draw" : "Black offers a draw";
15340 AppendComment(currentMove, offer, TRUE);
15341 DisplayComment(currentMove - 1, offer);
15342 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15344 DisplayError(_("You must make your move before offering a draw"), 0);
15345 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15347 } else if (first.offeredDraw) {
15348 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15350 if (first.sendDrawOffers) {
15351 SendToProgram("draw\n", &first);
15352 userOfferedDraw = TRUE;
15360 /* Offer Adjourn or accept pending Adjourn offer from opponent */
15362 if (appData.icsActive) {
15363 SendToICS(ics_prefix);
15364 SendToICS("adjourn\n");
15366 /* Currently GNU Chess doesn't offer or accept Adjourns */
15374 /* Offer Abort or accept pending Abort offer from opponent */
15376 if (appData.icsActive) {
15377 SendToICS(ics_prefix);
15378 SendToICS("abort\n");
15380 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15387 /* Resign. You can do this even if it's not your turn. */
15389 if (appData.icsActive) {
15390 SendToICS(ics_prefix);
15391 SendToICS("resign\n");
15393 switch (gameMode) {
15394 case MachinePlaysWhite:
15395 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15397 case MachinePlaysBlack:
15398 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15401 if (cmailMsgLoaded) {
15403 if (WhiteOnMove(cmailOldMove)) {
15404 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15406 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15408 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15419 StopObservingEvent ()
15421 /* Stop observing current games */
15422 SendToICS(ics_prefix);
15423 SendToICS("unobserve\n");
15427 StopExaminingEvent ()
15429 /* Stop observing current game */
15430 SendToICS(ics_prefix);
15431 SendToICS("unexamine\n");
15435 ForwardInner (int target)
15437 int limit; int oldSeekGraphUp = seekGraphUp;
15439 if (appData.debugMode)
15440 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15441 target, currentMove, forwardMostMove);
15443 if (gameMode == EditPosition)
15446 seekGraphUp = FALSE;
15447 MarkTargetSquares(1);
15449 if (gameMode == PlayFromGameFile && !pausing)
15452 if (gameMode == IcsExamining && pausing)
15453 limit = pauseExamForwardMostMove;
15455 limit = forwardMostMove;
15457 if (target > limit) target = limit;
15459 if (target > 0 && moveList[target - 1][0]) {
15460 int fromX, fromY, toX, toY;
15461 toX = moveList[target - 1][2] - AAA;
15462 toY = moveList[target - 1][3] - ONE;
15463 if (moveList[target - 1][1] == '@') {
15464 if (appData.highlightLastMove) {
15465 SetHighlights(-1, -1, toX, toY);
15468 fromX = moveList[target - 1][0] - AAA;
15469 fromY = moveList[target - 1][1] - ONE;
15470 if (target == currentMove + 1) {
15471 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15473 if (appData.highlightLastMove) {
15474 SetHighlights(fromX, fromY, toX, toY);
15478 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15479 gameMode == Training || gameMode == PlayFromGameFile ||
15480 gameMode == AnalyzeFile) {
15481 while (currentMove < target) {
15482 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15483 SendMoveToProgram(currentMove++, &first);
15486 currentMove = target;
15489 if (gameMode == EditGame || gameMode == EndOfGame) {
15490 whiteTimeRemaining = timeRemaining[0][currentMove];
15491 blackTimeRemaining = timeRemaining[1][currentMove];
15493 DisplayBothClocks();
15494 DisplayMove(currentMove - 1);
15495 DrawPosition(oldSeekGraphUp, boards[currentMove]);
15496 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15497 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15498 DisplayComment(currentMove - 1, commentList[currentMove]);
15500 ClearMap(); // [HGM] exclude: invalidate map
15507 if (gameMode == IcsExamining && !pausing) {
15508 SendToICS(ics_prefix);
15509 SendToICS("forward\n");
15511 ForwardInner(currentMove + 1);
15518 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15519 /* to optimze, we temporarily turn off analysis mode while we feed
15520 * the remaining moves to the engine. Otherwise we get analysis output
15523 if (first.analysisSupport) {
15524 SendToProgram("exit\nforce\n", &first);
15525 first.analyzing = FALSE;
15529 if (gameMode == IcsExamining && !pausing) {
15530 SendToICS(ics_prefix);
15531 SendToICS("forward 999999\n");
15533 ForwardInner(forwardMostMove);
15536 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15537 /* we have fed all the moves, so reactivate analysis mode */
15538 SendToProgram("analyze\n", &first);
15539 first.analyzing = TRUE;
15540 /*first.maybeThinking = TRUE;*/
15541 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15546 BackwardInner (int target)
15548 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15550 if (appData.debugMode)
15551 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15552 target, currentMove, forwardMostMove);
15554 if (gameMode == EditPosition) return;
15555 seekGraphUp = FALSE;
15556 MarkTargetSquares(1);
15557 if (currentMove <= backwardMostMove) {
15559 DrawPosition(full_redraw, boards[currentMove]);
15562 if (gameMode == PlayFromGameFile && !pausing)
15565 if (moveList[target][0]) {
15566 int fromX, fromY, toX, toY;
15567 toX = moveList[target][2] - AAA;
15568 toY = moveList[target][3] - ONE;
15569 if (moveList[target][1] == '@') {
15570 if (appData.highlightLastMove) {
15571 SetHighlights(-1, -1, toX, toY);
15574 fromX = moveList[target][0] - AAA;
15575 fromY = moveList[target][1] - ONE;
15576 if (target == currentMove - 1) {
15577 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15579 if (appData.highlightLastMove) {
15580 SetHighlights(fromX, fromY, toX, toY);
15584 if (gameMode == EditGame || gameMode==AnalyzeMode ||
15585 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15586 while (currentMove > target) {
15587 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15588 // null move cannot be undone. Reload program with move history before it.
15590 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15591 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15593 SendBoard(&first, i);
15594 if(second.analyzing) SendBoard(&second, i);
15595 for(currentMove=i; currentMove<target; currentMove++) {
15596 SendMoveToProgram(currentMove, &first);
15597 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15601 SendToBoth("undo\n");
15605 currentMove = target;
15608 if (gameMode == EditGame || gameMode == EndOfGame) {
15609 whiteTimeRemaining = timeRemaining[0][currentMove];
15610 blackTimeRemaining = timeRemaining[1][currentMove];
15612 DisplayBothClocks();
15613 DisplayMove(currentMove - 1);
15614 DrawPosition(full_redraw, boards[currentMove]);
15615 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15616 // [HGM] PV info: routine tests if comment empty
15617 DisplayComment(currentMove - 1, commentList[currentMove]);
15618 ClearMap(); // [HGM] exclude: invalidate map
15624 if (gameMode == IcsExamining && !pausing) {
15625 SendToICS(ics_prefix);
15626 SendToICS("backward\n");
15628 BackwardInner(currentMove - 1);
15635 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15636 /* to optimize, we temporarily turn off analysis mode while we undo
15637 * all the moves. Otherwise we get analysis output after each undo.
15639 if (first.analysisSupport) {
15640 SendToProgram("exit\nforce\n", &first);
15641 first.analyzing = FALSE;
15645 if (gameMode == IcsExamining && !pausing) {
15646 SendToICS(ics_prefix);
15647 SendToICS("backward 999999\n");
15649 BackwardInner(backwardMostMove);
15652 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15653 /* we have fed all the moves, so reactivate analysis mode */
15654 SendToProgram("analyze\n", &first);
15655 first.analyzing = TRUE;
15656 /*first.maybeThinking = TRUE;*/
15657 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15664 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15665 if (to >= forwardMostMove) to = forwardMostMove;
15666 if (to <= backwardMostMove) to = backwardMostMove;
15667 if (to < currentMove) {
15675 RevertEvent (Boolean annotate)
15677 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15680 if (gameMode != IcsExamining) {
15681 DisplayError(_("You are not examining a game"), 0);
15685 DisplayError(_("You can't revert while pausing"), 0);
15688 SendToICS(ics_prefix);
15689 SendToICS("revert\n");
15693 RetractMoveEvent ()
15695 switch (gameMode) {
15696 case MachinePlaysWhite:
15697 case MachinePlaysBlack:
15698 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15699 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15702 if (forwardMostMove < 2) return;
15703 currentMove = forwardMostMove = forwardMostMove - 2;
15704 whiteTimeRemaining = timeRemaining[0][currentMove];
15705 blackTimeRemaining = timeRemaining[1][currentMove];
15706 DisplayBothClocks();
15707 DisplayMove(currentMove - 1);
15708 ClearHighlights();/*!! could figure this out*/
15709 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15710 SendToProgram("remove\n", &first);
15711 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15714 case BeginningOfGame:
15718 case IcsPlayingWhite:
15719 case IcsPlayingBlack:
15720 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15721 SendToICS(ics_prefix);
15722 SendToICS("takeback 2\n");
15724 SendToICS(ics_prefix);
15725 SendToICS("takeback 1\n");
15734 ChessProgramState *cps;
15736 switch (gameMode) {
15737 case MachinePlaysWhite:
15738 if (!WhiteOnMove(forwardMostMove)) {
15739 DisplayError(_("It is your turn"), 0);
15744 case MachinePlaysBlack:
15745 if (WhiteOnMove(forwardMostMove)) {
15746 DisplayError(_("It is your turn"), 0);
15751 case TwoMachinesPlay:
15752 if (WhiteOnMove(forwardMostMove) ==
15753 (first.twoMachinesColor[0] == 'w')) {
15759 case BeginningOfGame:
15763 SendToProgram("?\n", cps);
15767 TruncateGameEvent ()
15770 if (gameMode != EditGame) return;
15777 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15778 if (forwardMostMove > currentMove) {
15779 if (gameInfo.resultDetails != NULL) {
15780 free(gameInfo.resultDetails);
15781 gameInfo.resultDetails = NULL;
15782 gameInfo.result = GameUnfinished;
15784 forwardMostMove = currentMove;
15785 HistorySet(parseList, backwardMostMove, forwardMostMove,
15793 if (appData.noChessProgram) return;
15794 switch (gameMode) {
15795 case MachinePlaysWhite:
15796 if (WhiteOnMove(forwardMostMove)) {
15797 DisplayError(_("Wait until your turn."), 0);
15801 case BeginningOfGame:
15802 case MachinePlaysBlack:
15803 if (!WhiteOnMove(forwardMostMove)) {
15804 DisplayError(_("Wait until your turn."), 0);
15809 DisplayError(_("No hint available"), 0);
15812 SendToProgram("hint\n", &first);
15813 hintRequested = TRUE;
15819 ListGame * lg = (ListGame *) gameList.head;
15822 static int secondTime = FALSE;
15824 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15825 DisplayError(_("Game list not loaded or empty"), 0);
15829 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15832 DisplayNote(_("Book file exists! Try again for overwrite."));
15836 creatingBook = TRUE;
15837 secondTime = FALSE;
15839 /* Get list size */
15840 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15841 LoadGame(f, nItem, "", TRUE);
15842 AddGameToBook(TRUE);
15843 lg = (ListGame *) lg->node.succ;
15846 creatingBook = FALSE;
15853 if (appData.noChessProgram) return;
15854 switch (gameMode) {
15855 case MachinePlaysWhite:
15856 if (WhiteOnMove(forwardMostMove)) {
15857 DisplayError(_("Wait until your turn."), 0);
15861 case BeginningOfGame:
15862 case MachinePlaysBlack:
15863 if (!WhiteOnMove(forwardMostMove)) {
15864 DisplayError(_("Wait until your turn."), 0);
15869 EditPositionDone(TRUE);
15871 case TwoMachinesPlay:
15876 SendToProgram("bk\n", &first);
15877 bookOutput[0] = NULLCHAR;
15878 bookRequested = TRUE;
15884 char *tags = PGNTags(&gameInfo);
15885 TagsPopUp(tags, CmailMsg());
15889 /* end button procedures */
15892 PrintPosition (FILE *fp, int move)
15896 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15897 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15898 char c = PieceToChar(boards[move][i][j]);
15899 fputc(c == 'x' ? '.' : c, fp);
15900 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15903 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15904 fprintf(fp, "white to play\n");
15906 fprintf(fp, "black to play\n");
15910 PrintOpponents (FILE *fp)
15912 if (gameInfo.white != NULL) {
15913 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15919 /* Find last component of program's own name, using some heuristics */
15921 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15924 int local = (strcmp(host, "localhost") == 0);
15925 while (!local && (p = strchr(prog, ';')) != NULL) {
15927 while (*p == ' ') p++;
15930 if (*prog == '"' || *prog == '\'') {
15931 q = strchr(prog + 1, *prog);
15933 q = strchr(prog, ' ');
15935 if (q == NULL) q = prog + strlen(prog);
15937 while (p >= prog && *p != '/' && *p != '\\') p--;
15939 if(p == prog && *p == '"') p++;
15941 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15942 memcpy(buf, p, q - p);
15943 buf[q - p] = NULLCHAR;
15951 TimeControlTagValue ()
15954 if (!appData.clockMode) {
15955 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15956 } else if (movesPerSession > 0) {
15957 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15958 } else if (timeIncrement == 0) {
15959 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15961 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15963 return StrSave(buf);
15969 /* This routine is used only for certain modes */
15970 VariantClass v = gameInfo.variant;
15971 ChessMove r = GameUnfinished;
15974 if(keepInfo) return;
15976 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15977 r = gameInfo.result;
15978 p = gameInfo.resultDetails;
15979 gameInfo.resultDetails = NULL;
15981 ClearGameInfo(&gameInfo);
15982 gameInfo.variant = v;
15984 switch (gameMode) {
15985 case MachinePlaysWhite:
15986 gameInfo.event = StrSave( appData.pgnEventHeader );
15987 gameInfo.site = StrSave(HostName());
15988 gameInfo.date = PGNDate();
15989 gameInfo.round = StrSave("-");
15990 gameInfo.white = StrSave(first.tidy);
15991 gameInfo.black = StrSave(UserName());
15992 gameInfo.timeControl = TimeControlTagValue();
15995 case MachinePlaysBlack:
15996 gameInfo.event = StrSave( appData.pgnEventHeader );
15997 gameInfo.site = StrSave(HostName());
15998 gameInfo.date = PGNDate();
15999 gameInfo.round = StrSave("-");
16000 gameInfo.white = StrSave(UserName());
16001 gameInfo.black = StrSave(first.tidy);
16002 gameInfo.timeControl = TimeControlTagValue();
16005 case TwoMachinesPlay:
16006 gameInfo.event = StrSave( appData.pgnEventHeader );
16007 gameInfo.site = StrSave(HostName());
16008 gameInfo.date = PGNDate();
16011 snprintf(buf, MSG_SIZ, "%d", roundNr);
16012 gameInfo.round = StrSave(buf);
16014 gameInfo.round = StrSave("-");
16016 if (first.twoMachinesColor[0] == 'w') {
16017 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16018 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16020 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16021 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16023 gameInfo.timeControl = TimeControlTagValue();
16027 gameInfo.event = StrSave("Edited game");
16028 gameInfo.site = StrSave(HostName());
16029 gameInfo.date = PGNDate();
16030 gameInfo.round = StrSave("-");
16031 gameInfo.white = StrSave("-");
16032 gameInfo.black = StrSave("-");
16033 gameInfo.result = r;
16034 gameInfo.resultDetails = p;
16038 gameInfo.event = StrSave("Edited position");
16039 gameInfo.site = StrSave(HostName());
16040 gameInfo.date = PGNDate();
16041 gameInfo.round = StrSave("-");
16042 gameInfo.white = StrSave("-");
16043 gameInfo.black = StrSave("-");
16046 case IcsPlayingWhite:
16047 case IcsPlayingBlack:
16052 case PlayFromGameFile:
16053 gameInfo.event = StrSave("Game from non-PGN file");
16054 gameInfo.site = StrSave(HostName());
16055 gameInfo.date = PGNDate();
16056 gameInfo.round = StrSave("-");
16057 gameInfo.white = StrSave("?");
16058 gameInfo.black = StrSave("?");
16067 ReplaceComment (int index, char *text)
16073 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16074 pvInfoList[index-1].depth == len &&
16075 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16076 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16077 while (*text == '\n') text++;
16078 len = strlen(text);
16079 while (len > 0 && text[len - 1] == '\n') len--;
16081 if (commentList[index] != NULL)
16082 free(commentList[index]);
16085 commentList[index] = NULL;
16088 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16089 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16090 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16091 commentList[index] = (char *) malloc(len + 2);
16092 strncpy(commentList[index], text, len);
16093 commentList[index][len] = '\n';
16094 commentList[index][len + 1] = NULLCHAR;
16096 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16098 commentList[index] = (char *) malloc(len + 7);
16099 safeStrCpy(commentList[index], "{\n", 3);
16100 safeStrCpy(commentList[index]+2, text, len+1);
16101 commentList[index][len+2] = NULLCHAR;
16102 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16103 strcat(commentList[index], "\n}\n");
16108 CrushCRs (char *text)
16116 if (ch == '\r') continue;
16118 } while (ch != '\0');
16122 AppendComment (int index, char *text, Boolean addBraces)
16123 /* addBraces tells if we should add {} */
16128 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16129 if(addBraces == 3) addBraces = 0; else // force appending literally
16130 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16133 while (*text == '\n') text++;
16134 len = strlen(text);
16135 while (len > 0 && text[len - 1] == '\n') len--;
16136 text[len] = NULLCHAR;
16138 if (len == 0) return;
16140 if (commentList[index] != NULL) {
16141 Boolean addClosingBrace = addBraces;
16142 old = commentList[index];
16143 oldlen = strlen(old);
16144 while(commentList[index][oldlen-1] == '\n')
16145 commentList[index][--oldlen] = NULLCHAR;
16146 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16147 safeStrCpy(commentList[index], old, oldlen + len + 6);
16149 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16150 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16151 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16152 while (*text == '\n') { text++; len--; }
16153 commentList[index][--oldlen] = NULLCHAR;
16155 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16156 else strcat(commentList[index], "\n");
16157 strcat(commentList[index], text);
16158 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16159 else strcat(commentList[index], "\n");
16161 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16163 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16164 else commentList[index][0] = NULLCHAR;
16165 strcat(commentList[index], text);
16166 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16167 if(addBraces == TRUE) strcat(commentList[index], "}\n");
16172 FindStr (char * text, char * sub_text)
16174 char * result = strstr( text, sub_text );
16176 if( result != NULL ) {
16177 result += strlen( sub_text );
16183 /* [AS] Try to extract PV info from PGN comment */
16184 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16186 GetInfoFromComment (int index, char * text)
16188 char * sep = text, *p;
16190 if( text != NULL && index > 0 ) {
16193 int time = -1, sec = 0, deci;
16194 char * s_eval = FindStr( text, "[%eval " );
16195 char * s_emt = FindStr( text, "[%emt " );
16197 if( s_eval != NULL || s_emt != NULL ) {
16199 if(0) { // [HGM] this code is not finished, and could actually be detrimental
16204 if( s_eval != NULL ) {
16205 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16209 if( delim != ']' ) {
16214 if( s_emt != NULL ) {
16219 /* We expect something like: [+|-]nnn.nn/dd */
16222 if(*text != '{') return text; // [HGM] braces: must be normal comment
16224 sep = strchr( text, '/' );
16225 if( sep == NULL || sep < (text+4) ) {
16230 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16231 if(p[1] == '(') { // comment starts with PV
16232 p = strchr(p, ')'); // locate end of PV
16233 if(p == NULL || sep < p+5) return text;
16234 // at this point we have something like "{(.*) +0.23/6 ..."
16235 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16236 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16237 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16239 time = -1; sec = -1; deci = -1;
16240 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16241 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16242 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16243 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
16247 if( score_lo < 0 || score_lo >= 100 ) {
16251 if(sec >= 0) time = 600*time + 10*sec; else
16252 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16254 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16256 /* [HGM] PV time: now locate end of PV info */
16257 while( *++sep >= '0' && *sep <= '9'); // strip depth
16259 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16261 while( *++sep >= '0' && *sep <= '9'); // strip seconds
16263 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16264 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16275 pvInfoList[index-1].depth = depth;
16276 pvInfoList[index-1].score = score;
16277 pvInfoList[index-1].time = 10*time; // centi-sec
16278 if(*sep == '}') *sep = 0; else *--sep = '{';
16279 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16285 SendToProgram (char *message, ChessProgramState *cps)
16287 int count, outCount, error;
16290 if (cps->pr == NoProc) return;
16293 if (appData.debugMode) {
16296 fprintf(debugFP, "%ld >%-6s: %s",
16297 SubtractTimeMarks(&now, &programStartTime),
16298 cps->which, message);
16300 fprintf(serverFP, "%ld >%-6s: %s",
16301 SubtractTimeMarks(&now, &programStartTime),
16302 cps->which, message), fflush(serverFP);
16305 count = strlen(message);
16306 outCount = OutputToProcess(cps->pr, message, count, &error);
16307 if (outCount < count && !exiting
16308 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16309 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16310 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16311 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16312 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16313 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16314 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16315 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16317 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16318 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16319 gameInfo.result = res;
16321 gameInfo.resultDetails = StrSave(buf);
16323 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16324 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16329 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16333 ChessProgramState *cps = (ChessProgramState *)closure;
16335 if (isr != cps->isr) return; /* Killed intentionally */
16338 RemoveInputSource(cps->isr);
16339 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16340 _(cps->which), cps->program);
16341 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16342 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16343 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16344 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16345 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16346 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16348 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16349 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16350 gameInfo.result = res;
16352 gameInfo.resultDetails = StrSave(buf);
16354 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16355 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16357 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16358 _(cps->which), cps->program);
16359 RemoveInputSource(cps->isr);
16361 /* [AS] Program is misbehaving badly... kill it */
16362 if( count == -2 ) {
16363 DestroyChildProcess( cps->pr, 9 );
16367 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16372 if ((end_str = strchr(message, '\r')) != NULL)
16373 *end_str = NULLCHAR;
16374 if ((end_str = strchr(message, '\n')) != NULL)
16375 *end_str = NULLCHAR;
16377 if (appData.debugMode) {
16378 TimeMark now; int print = 1;
16379 char *quote = ""; char c; int i;
16381 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16382 char start = message[0];
16383 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16384 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16385 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
16386 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16387 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16388 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
16389 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16390 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
16391 sscanf(message, "hint: %c", &c)!=1 &&
16392 sscanf(message, "pong %c", &c)!=1 && start != '#') {
16393 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16394 print = (appData.engineComments >= 2);
16396 message[0] = start; // restore original message
16400 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16401 SubtractTimeMarks(&now, &programStartTime), cps->which,
16405 fprintf(serverFP, "%ld <%-6s: %s%s\n",
16406 SubtractTimeMarks(&now, &programStartTime), cps->which,
16408 message), fflush(serverFP);
16412 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16413 if (appData.icsEngineAnalyze) {
16414 if (strstr(message, "whisper") != NULL ||
16415 strstr(message, "kibitz") != NULL ||
16416 strstr(message, "tellics") != NULL) return;
16419 HandleMachineMove(message, cps);
16424 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16429 if( timeControl_2 > 0 ) {
16430 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16431 tc = timeControl_2;
16434 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16435 inc /= cps->timeOdds;
16436 st /= cps->timeOdds;
16438 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16441 /* Set exact time per move, normally using st command */
16442 if (cps->stKludge) {
16443 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16445 if (seconds == 0) {
16446 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16448 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16451 snprintf(buf, MSG_SIZ, "st %d\n", st);
16454 /* Set conventional or incremental time control, using level command */
16455 if (seconds == 0) {
16456 /* Note old gnuchess bug -- minutes:seconds used to not work.
16457 Fixed in later versions, but still avoid :seconds
16458 when seconds is 0. */
16459 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16461 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16462 seconds, inc/1000.);
16465 SendToProgram(buf, cps);
16467 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16468 /* Orthogonally, limit search to given depth */
16470 if (cps->sdKludge) {
16471 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16473 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16475 SendToProgram(buf, cps);
16478 if(cps->nps >= 0) { /* [HGM] nps */
16479 if(cps->supportsNPS == FALSE)
16480 cps->nps = -1; // don't use if engine explicitly says not supported!
16482 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16483 SendToProgram(buf, cps);
16488 ChessProgramState *
16490 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16492 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16493 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16499 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16501 char message[MSG_SIZ];
16504 /* Note: this routine must be called when the clocks are stopped
16505 or when they have *just* been set or switched; otherwise
16506 it will be off by the time since the current tick started.
16508 if (machineWhite) {
16509 time = whiteTimeRemaining / 10;
16510 otime = blackTimeRemaining / 10;
16512 time = blackTimeRemaining / 10;
16513 otime = whiteTimeRemaining / 10;
16515 /* [HGM] translate opponent's time by time-odds factor */
16516 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16518 if (time <= 0) time = 1;
16519 if (otime <= 0) otime = 1;
16521 snprintf(message, MSG_SIZ, "time %ld\n", time);
16522 SendToProgram(message, cps);
16524 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16525 SendToProgram(message, cps);
16529 EngineDefinedVariant (ChessProgramState *cps, int n)
16530 { // return name of n-th unknown variant that engine supports
16531 static char buf[MSG_SIZ];
16532 char *p, *s = cps->variants;
16533 if(!s) return NULL;
16534 do { // parse string from variants feature
16536 p = strchr(s, ',');
16537 if(p) *p = NULLCHAR;
16538 v = StringToVariant(s);
16539 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16540 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16541 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16544 if(n < 0) return buf;
16550 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16553 int len = strlen(name);
16556 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16558 sscanf(*p, "%d", &val);
16560 while (**p && **p != ' ')
16562 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16563 SendToProgram(buf, cps);
16570 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16573 int len = strlen(name);
16574 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16576 sscanf(*p, "%d", loc);
16577 while (**p && **p != ' ') (*p)++;
16578 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16579 SendToProgram(buf, cps);
16586 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16589 int len = strlen(name);
16590 if (strncmp((*p), name, len) == 0
16591 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16593 ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16594 sscanf(*p, "%[^\"]", *loc);
16595 while (**p && **p != '\"') (*p)++;
16596 if (**p == '\"') (*p)++;
16597 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16598 SendToProgram(buf, cps);
16605 ParseOption (Option *opt, ChessProgramState *cps)
16606 // [HGM] options: process the string that defines an engine option, and determine
16607 // name, type, default value, and allowed value range
16609 char *p, *q, buf[MSG_SIZ];
16610 int n, min = (-1)<<31, max = 1<<31, def;
16612 if(p = strstr(opt->name, " -spin ")) {
16613 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16614 if(max < min) max = min; // enforce consistency
16615 if(def < min) def = min;
16616 if(def > max) def = max;
16621 } else if((p = strstr(opt->name, " -slider "))) {
16622 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16623 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16624 if(max < min) max = min; // enforce consistency
16625 if(def < min) def = min;
16626 if(def > max) def = max;
16630 opt->type = Spin; // Slider;
16631 } else if((p = strstr(opt->name, " -string "))) {
16632 opt->textValue = p+9;
16633 opt->type = TextBox;
16634 } else if((p = strstr(opt->name, " -file "))) {
16635 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16636 opt->textValue = p+7;
16637 opt->type = FileName; // FileName;
16638 } else if((p = strstr(opt->name, " -path "))) {
16639 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16640 opt->textValue = p+7;
16641 opt->type = PathName; // PathName;
16642 } else if(p = strstr(opt->name, " -check ")) {
16643 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16644 opt->value = (def != 0);
16645 opt->type = CheckBox;
16646 } else if(p = strstr(opt->name, " -combo ")) {
16647 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16648 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16649 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16650 opt->value = n = 0;
16651 while(q = StrStr(q, " /// ")) {
16652 n++; *q = 0; // count choices, and null-terminate each of them
16654 if(*q == '*') { // remember default, which is marked with * prefix
16658 cps->comboList[cps->comboCnt++] = q;
16660 cps->comboList[cps->comboCnt++] = NULL;
16662 opt->type = ComboBox;
16663 } else if(p = strstr(opt->name, " -button")) {
16664 opt->type = Button;
16665 } else if(p = strstr(opt->name, " -save")) {
16666 opt->type = SaveButton;
16667 } else return FALSE;
16668 *p = 0; // terminate option name
16669 // now look if the command-line options define a setting for this engine option.
16670 if(cps->optionSettings && cps->optionSettings[0])
16671 p = strstr(cps->optionSettings, opt->name); else p = NULL;
16672 if(p && (p == cps->optionSettings || p[-1] == ',')) {
16673 snprintf(buf, MSG_SIZ, "option %s", p);
16674 if(p = strstr(buf, ",")) *p = 0;
16675 if(q = strchr(buf, '=')) switch(opt->type) {
16677 for(n=0; n<opt->max; n++)
16678 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16681 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16685 opt->value = atoi(q+1);
16690 SendToProgram(buf, cps);
16696 FeatureDone (ChessProgramState *cps, int val)
16698 DelayedEventCallback cb = GetDelayedEvent();
16699 if ((cb == InitBackEnd3 && cps == &first) ||
16700 (cb == SettingsMenuIfReady && cps == &second) ||
16701 (cb == LoadEngine) ||
16702 (cb == TwoMachinesEventIfReady)) {
16703 CancelDelayedEvent();
16704 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16706 cps->initDone = val;
16707 if(val) cps->reload = FALSE;
16710 /* Parse feature command from engine */
16712 ParseFeatures (char *args, ChessProgramState *cps)
16720 while (*p == ' ') p++;
16721 if (*p == NULLCHAR) return;
16723 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16724 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16725 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16726 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16727 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16728 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16729 if (BoolFeature(&p, "reuse", &val, cps)) {
16730 /* Engine can disable reuse, but can't enable it if user said no */
16731 if (!val) cps->reuse = FALSE;
16734 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16735 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16736 if (gameMode == TwoMachinesPlay) {
16737 DisplayTwoMachinesTitle();
16743 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16744 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16745 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16746 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16747 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16748 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16749 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16750 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16751 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16752 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16753 if (IntFeature(&p, "done", &val, cps)) {
16754 FeatureDone(cps, val);
16757 /* Added by Tord: */
16758 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16759 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16760 /* End of additions by Tord */
16762 /* [HGM] added features: */
16763 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16764 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16765 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16766 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16767 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16768 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16769 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16770 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16771 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16772 FREE(cps->option[cps->nrOptions].name);
16773 cps->option[cps->nrOptions].name = q; q = NULL;
16774 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16775 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16776 SendToProgram(buf, cps);
16779 if(cps->nrOptions >= MAX_OPTIONS) {
16781 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16782 DisplayError(buf, 0);
16786 /* End of additions by HGM */
16788 /* unknown feature: complain and skip */
16790 while (*q && *q != '=') q++;
16791 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16792 SendToProgram(buf, cps);
16798 while (*p && *p != '\"') p++;
16799 if (*p == '\"') p++;
16801 while (*p && *p != ' ') p++;
16809 PeriodicUpdatesEvent (int newState)
16811 if (newState == appData.periodicUpdates)
16814 appData.periodicUpdates=newState;
16816 /* Display type changes, so update it now */
16817 // DisplayAnalysis();
16819 /* Get the ball rolling again... */
16821 AnalysisPeriodicEvent(1);
16822 StartAnalysisClock();
16827 PonderNextMoveEvent (int newState)
16829 if (newState == appData.ponderNextMove) return;
16830 if (gameMode == EditPosition) EditPositionDone(TRUE);
16832 SendToProgram("hard\n", &first);
16833 if (gameMode == TwoMachinesPlay) {
16834 SendToProgram("hard\n", &second);
16837 SendToProgram("easy\n", &first);
16838 thinkOutput[0] = NULLCHAR;
16839 if (gameMode == TwoMachinesPlay) {
16840 SendToProgram("easy\n", &second);
16843 appData.ponderNextMove = newState;
16847 NewSettingEvent (int option, int *feature, char *command, int value)
16851 if (gameMode == EditPosition) EditPositionDone(TRUE);
16852 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16853 if(feature == NULL || *feature) SendToProgram(buf, &first);
16854 if (gameMode == TwoMachinesPlay) {
16855 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16860 ShowThinkingEvent ()
16861 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16863 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16864 int newState = appData.showThinking
16865 // [HGM] thinking: other features now need thinking output as well
16866 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16868 if (oldState == newState) return;
16869 oldState = newState;
16870 if (gameMode == EditPosition) EditPositionDone(TRUE);
16872 SendToProgram("post\n", &first);
16873 if (gameMode == TwoMachinesPlay) {
16874 SendToProgram("post\n", &second);
16877 SendToProgram("nopost\n", &first);
16878 thinkOutput[0] = NULLCHAR;
16879 if (gameMode == TwoMachinesPlay) {
16880 SendToProgram("nopost\n", &second);
16883 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16887 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16889 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16890 if (pr == NoProc) return;
16891 AskQuestion(title, question, replyPrefix, pr);
16895 TypeInEvent (char firstChar)
16897 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16898 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16899 gameMode == AnalyzeMode || gameMode == EditGame ||
16900 gameMode == EditPosition || gameMode == IcsExamining ||
16901 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16902 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16903 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16904 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
16905 gameMode == Training) PopUpMoveDialog(firstChar);
16909 TypeInDoneEvent (char *move)
16912 int n, fromX, fromY, toX, toY;
16914 ChessMove moveType;
16917 if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16918 EditPositionPasteFEN(move);
16921 // [HGM] movenum: allow move number to be typed in any mode
16922 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16926 // undocumented kludge: allow command-line option to be typed in!
16927 // (potentially fatal, and does not implement the effect of the option.)
16928 // should only be used for options that are values on which future decisions will be made,
16929 // and definitely not on options that would be used during initialization.
16930 if(strstr(move, "!!! -") == move) {
16931 ParseArgsFromString(move+4);
16935 if (gameMode != EditGame && currentMove != forwardMostMove &&
16936 gameMode != Training) {
16937 DisplayMoveError(_("Displayed move is not current"));
16939 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16940 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16941 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16942 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16943 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16944 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16946 DisplayMoveError(_("Could not parse move"));
16952 DisplayMove (int moveNumber)
16954 char message[MSG_SIZ];
16956 char cpThinkOutput[MSG_SIZ];
16958 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16960 if (moveNumber == forwardMostMove - 1 ||
16961 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16963 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16965 if (strchr(cpThinkOutput, '\n')) {
16966 *strchr(cpThinkOutput, '\n') = NULLCHAR;
16969 *cpThinkOutput = NULLCHAR;
16972 /* [AS] Hide thinking from human user */
16973 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16974 *cpThinkOutput = NULLCHAR;
16975 if( thinkOutput[0] != NULLCHAR ) {
16978 for( i=0; i<=hiddenThinkOutputState; i++ ) {
16979 cpThinkOutput[i] = '.';
16981 cpThinkOutput[i] = NULLCHAR;
16982 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16986 if (moveNumber == forwardMostMove - 1 &&
16987 gameInfo.resultDetails != NULL) {
16988 if (gameInfo.resultDetails[0] == NULLCHAR) {
16989 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16991 snprintf(res, MSG_SIZ, " {%s} %s",
16992 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16998 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16999 DisplayMessage(res, cpThinkOutput);
17001 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17002 WhiteOnMove(moveNumber) ? " " : ".. ",
17003 parseList[moveNumber], res);
17004 DisplayMessage(message, cpThinkOutput);
17009 DisplayComment (int moveNumber, char *text)
17011 char title[MSG_SIZ];
17013 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17014 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17016 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17017 WhiteOnMove(moveNumber) ? " " : ".. ",
17018 parseList[moveNumber]);
17020 if (text != NULL && (appData.autoDisplayComment || commentUp))
17021 CommentPopUp(title, text);
17024 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17025 * might be busy thinking or pondering. It can be omitted if your
17026 * gnuchess is configured to stop thinking immediately on any user
17027 * input. However, that gnuchess feature depends on the FIONREAD
17028 * ioctl, which does not work properly on some flavors of Unix.
17031 Attention (ChessProgramState *cps)
17034 if (!cps->useSigint) return;
17035 if (appData.noChessProgram || (cps->pr == NoProc)) return;
17036 switch (gameMode) {
17037 case MachinePlaysWhite:
17038 case MachinePlaysBlack:
17039 case TwoMachinesPlay:
17040 case IcsPlayingWhite:
17041 case IcsPlayingBlack:
17044 /* Skip if we know it isn't thinking */
17045 if (!cps->maybeThinking) return;
17046 if (appData.debugMode)
17047 fprintf(debugFP, "Interrupting %s\n", cps->which);
17048 InterruptChildProcess(cps->pr);
17049 cps->maybeThinking = FALSE;
17054 #endif /*ATTENTION*/
17060 if (whiteTimeRemaining <= 0) {
17063 if (appData.icsActive) {
17064 if (appData.autoCallFlag &&
17065 gameMode == IcsPlayingBlack && !blackFlag) {
17066 SendToICS(ics_prefix);
17067 SendToICS("flag\n");
17071 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17073 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17074 if (appData.autoCallFlag) {
17075 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17082 if (blackTimeRemaining <= 0) {
17085 if (appData.icsActive) {
17086 if (appData.autoCallFlag &&
17087 gameMode == IcsPlayingWhite && !whiteFlag) {
17088 SendToICS(ics_prefix);
17089 SendToICS("flag\n");
17093 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17095 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17096 if (appData.autoCallFlag) {
17097 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17108 CheckTimeControl ()
17110 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17111 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17114 * add time to clocks when time control is achieved ([HGM] now also used for increment)
17116 if ( !WhiteOnMove(forwardMostMove) ) {
17117 /* White made time control */
17118 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17119 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17120 /* [HGM] time odds: correct new time quota for time odds! */
17121 / WhitePlayer()->timeOdds;
17122 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17124 lastBlack -= blackTimeRemaining;
17125 /* Black made time control */
17126 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17127 / WhitePlayer()->other->timeOdds;
17128 lastWhite = whiteTimeRemaining;
17133 DisplayBothClocks ()
17135 int wom = gameMode == EditPosition ?
17136 !blackPlaysFirst : WhiteOnMove(currentMove);
17137 DisplayWhiteClock(whiteTimeRemaining, wom);
17138 DisplayBlackClock(blackTimeRemaining, !wom);
17142 /* Timekeeping seems to be a portability nightmare. I think everyone
17143 has ftime(), but I'm really not sure, so I'm including some ifdefs
17144 to use other calls if you don't. Clocks will be less accurate if
17145 you have neither ftime nor gettimeofday.
17148 /* VS 2008 requires the #include outside of the function */
17149 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17150 #include <sys/timeb.h>
17153 /* Get the current time as a TimeMark */
17155 GetTimeMark (TimeMark *tm)
17157 #if HAVE_GETTIMEOFDAY
17159 struct timeval timeVal;
17160 struct timezone timeZone;
17162 gettimeofday(&timeVal, &timeZone);
17163 tm->sec = (long) timeVal.tv_sec;
17164 tm->ms = (int) (timeVal.tv_usec / 1000L);
17166 #else /*!HAVE_GETTIMEOFDAY*/
17169 // include <sys/timeb.h> / moved to just above start of function
17170 struct timeb timeB;
17173 tm->sec = (long) timeB.time;
17174 tm->ms = (int) timeB.millitm;
17176 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17177 tm->sec = (long) time(NULL);
17183 /* Return the difference in milliseconds between two
17184 time marks. We assume the difference will fit in a long!
17187 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17189 return 1000L*(tm2->sec - tm1->sec) +
17190 (long) (tm2->ms - tm1->ms);
17195 * Code to manage the game clocks.
17197 * In tournament play, black starts the clock and then white makes a move.
17198 * We give the human user a slight advantage if he is playing white---the
17199 * clocks don't run until he makes his first move, so it takes zero time.
17200 * Also, we don't account for network lag, so we could get out of sync
17201 * with GNU Chess's clock -- but then, referees are always right.
17204 static TimeMark tickStartTM;
17205 static long intendedTickLength;
17208 NextTickLength (long timeRemaining)
17210 long nominalTickLength, nextTickLength;
17212 if (timeRemaining > 0L && timeRemaining <= 10000L)
17213 nominalTickLength = 100L;
17215 nominalTickLength = 1000L;
17216 nextTickLength = timeRemaining % nominalTickLength;
17217 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17219 return nextTickLength;
17222 /* Adjust clock one minute up or down */
17224 AdjustClock (Boolean which, int dir)
17226 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17227 if(which) blackTimeRemaining += 60000*dir;
17228 else whiteTimeRemaining += 60000*dir;
17229 DisplayBothClocks();
17230 adjustedClock = TRUE;
17233 /* Stop clocks and reset to a fresh time control */
17237 (void) StopClockTimer();
17238 if (appData.icsActive) {
17239 whiteTimeRemaining = blackTimeRemaining = 0;
17240 } else if (searchTime) {
17241 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17242 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17243 } else { /* [HGM] correct new time quote for time odds */
17244 whiteTC = blackTC = fullTimeControlString;
17245 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17246 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17248 if (whiteFlag || blackFlag) {
17250 whiteFlag = blackFlag = FALSE;
17252 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17253 DisplayBothClocks();
17254 adjustedClock = FALSE;
17257 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17259 /* Decrement running clock by amount of time that has passed */
17263 long timeRemaining;
17264 long lastTickLength, fudge;
17267 if (!appData.clockMode) return;
17268 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17272 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17274 /* Fudge if we woke up a little too soon */
17275 fudge = intendedTickLength - lastTickLength;
17276 if (fudge < 0 || fudge > FUDGE) fudge = 0;
17278 if (WhiteOnMove(forwardMostMove)) {
17279 if(whiteNPS >= 0) lastTickLength = 0;
17280 timeRemaining = whiteTimeRemaining -= lastTickLength;
17281 if(timeRemaining < 0 && !appData.icsActive) {
17282 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17283 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17284 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17285 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17288 DisplayWhiteClock(whiteTimeRemaining - fudge,
17289 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17291 if(blackNPS >= 0) lastTickLength = 0;
17292 timeRemaining = blackTimeRemaining -= lastTickLength;
17293 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17294 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17296 blackStartMove = forwardMostMove;
17297 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17300 DisplayBlackClock(blackTimeRemaining - fudge,
17301 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17303 if (CheckFlags()) return;
17305 if(twoBoards) { // count down secondary board's clocks as well
17306 activePartnerTime -= lastTickLength;
17308 if(activePartner == 'W')
17309 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17311 DisplayBlackClock(activePartnerTime, TRUE);
17316 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17317 StartClockTimer(intendedTickLength);
17319 /* if the time remaining has fallen below the alarm threshold, sound the
17320 * alarm. if the alarm has sounded and (due to a takeback or time control
17321 * with increment) the time remaining has increased to a level above the
17322 * threshold, reset the alarm so it can sound again.
17325 if (appData.icsActive && appData.icsAlarm) {
17327 /* make sure we are dealing with the user's clock */
17328 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17329 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17332 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17333 alarmSounded = FALSE;
17334 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17336 alarmSounded = TRUE;
17342 /* A player has just moved, so stop the previously running
17343 clock and (if in clock mode) start the other one.
17344 We redisplay both clocks in case we're in ICS mode, because
17345 ICS gives us an update to both clocks after every move.
17346 Note that this routine is called *after* forwardMostMove
17347 is updated, so the last fractional tick must be subtracted
17348 from the color that is *not* on move now.
17351 SwitchClocks (int newMoveNr)
17353 long lastTickLength;
17355 int flagged = FALSE;
17359 if (StopClockTimer() && appData.clockMode) {
17360 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17361 if (!WhiteOnMove(forwardMostMove)) {
17362 if(blackNPS >= 0) lastTickLength = 0;
17363 blackTimeRemaining -= 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 = // use GUI time
17367 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17369 if(whiteNPS >= 0) lastTickLength = 0;
17370 whiteTimeRemaining -= lastTickLength;
17371 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17372 // if(pvInfoList[forwardMostMove].time == -1)
17373 pvInfoList[forwardMostMove].time =
17374 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17376 flagged = CheckFlags();
17378 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17379 CheckTimeControl();
17381 if (flagged || !appData.clockMode) return;
17383 switch (gameMode) {
17384 case MachinePlaysBlack:
17385 case MachinePlaysWhite:
17386 case BeginningOfGame:
17387 if (pausing) return;
17391 case PlayFromGameFile:
17399 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17400 if(WhiteOnMove(forwardMostMove))
17401 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17402 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17406 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17407 whiteTimeRemaining : blackTimeRemaining);
17408 StartClockTimer(intendedTickLength);
17412 /* Stop both clocks */
17416 long lastTickLength;
17419 if (!StopClockTimer()) return;
17420 if (!appData.clockMode) return;
17424 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17425 if (WhiteOnMove(forwardMostMove)) {
17426 if(whiteNPS >= 0) lastTickLength = 0;
17427 whiteTimeRemaining -= lastTickLength;
17428 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17430 if(blackNPS >= 0) lastTickLength = 0;
17431 blackTimeRemaining -= lastTickLength;
17432 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17437 /* Start clock of player on move. Time may have been reset, so
17438 if clock is already running, stop and restart it. */
17442 (void) StopClockTimer(); /* in case it was running already */
17443 DisplayBothClocks();
17444 if (CheckFlags()) return;
17446 if (!appData.clockMode) return;
17447 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17449 GetTimeMark(&tickStartTM);
17450 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17451 whiteTimeRemaining : blackTimeRemaining);
17453 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17454 whiteNPS = blackNPS = -1;
17455 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17456 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17457 whiteNPS = first.nps;
17458 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17459 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17460 blackNPS = first.nps;
17461 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17462 whiteNPS = second.nps;
17463 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17464 blackNPS = second.nps;
17465 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17467 StartClockTimer(intendedTickLength);
17471 TimeString (long ms)
17473 long second, minute, hour, day;
17475 static char buf[32];
17477 if (ms > 0 && ms <= 9900) {
17478 /* convert milliseconds to tenths, rounding up */
17479 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17481 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17485 /* convert milliseconds to seconds, rounding up */
17486 /* use floating point to avoid strangeness of integer division
17487 with negative dividends on many machines */
17488 second = (long) floor(((double) (ms + 999L)) / 1000.0);
17495 day = second / (60 * 60 * 24);
17496 second = second % (60 * 60 * 24);
17497 hour = second / (60 * 60);
17498 second = second % (60 * 60);
17499 minute = second / 60;
17500 second = second % 60;
17503 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17504 sign, day, hour, minute, second);
17506 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17508 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17515 * This is necessary because some C libraries aren't ANSI C compliant yet.
17518 StrStr (char *string, char *match)
17522 length = strlen(match);
17524 for (i = strlen(string) - length; i >= 0; i--, string++)
17525 if (!strncmp(match, string, length))
17532 StrCaseStr (char *string, char *match)
17536 length = strlen(match);
17538 for (i = strlen(string) - length; i >= 0; i--, string++) {
17539 for (j = 0; j < length; j++) {
17540 if (ToLower(match[j]) != ToLower(string[j]))
17543 if (j == length) return string;
17551 StrCaseCmp (char *s1, char *s2)
17556 c1 = ToLower(*s1++);
17557 c2 = ToLower(*s2++);
17558 if (c1 > c2) return 1;
17559 if (c1 < c2) return -1;
17560 if (c1 == NULLCHAR) return 0;
17568 return isupper(c) ? tolower(c) : c;
17575 return islower(c) ? toupper(c) : c;
17577 #endif /* !_amigados */
17584 if ((ret = (char *) malloc(strlen(s) + 1)))
17586 safeStrCpy(ret, s, strlen(s)+1);
17592 StrSavePtr (char *s, char **savePtr)
17597 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17598 safeStrCpy(*savePtr, s, strlen(s)+1);
17610 clock = time((time_t *)NULL);
17611 tm = localtime(&clock);
17612 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17613 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17614 return StrSave(buf);
17619 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17621 int i, j, fromX, fromY, toX, toY;
17628 whiteToPlay = (gameMode == EditPosition) ?
17629 !blackPlaysFirst : (move % 2 == 0);
17632 /* Piece placement data */
17633 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17634 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17636 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17637 if (boards[move][i][j] == EmptySquare) {
17639 } else { ChessSquare piece = boards[move][i][j];
17640 if (emptycount > 0) {
17641 if(emptycount<10) /* [HGM] can be >= 10 */
17642 *p++ = '0' + emptycount;
17643 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17646 if(PieceToChar(piece) == '+') {
17647 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17649 piece = (ChessSquare)(CHUDEMOTED piece);
17651 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17653 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17654 p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17659 if (emptycount > 0) {
17660 if(emptycount<10) /* [HGM] can be >= 10 */
17661 *p++ = '0' + emptycount;
17662 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17669 /* [HGM] print Crazyhouse or Shogi holdings */
17670 if( gameInfo.holdingsWidth ) {
17671 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17673 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17674 piece = boards[move][i][BOARD_WIDTH-1];
17675 if( piece != EmptySquare )
17676 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17677 *p++ = PieceToChar(piece);
17679 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17680 piece = boards[move][BOARD_HEIGHT-i-1][0];
17681 if( piece != EmptySquare )
17682 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17683 *p++ = PieceToChar(piece);
17686 if( q == p ) *p++ = '-';
17692 *p++ = whiteToPlay ? 'w' : 'b';
17695 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17696 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17698 if(nrCastlingRights) {
17700 if(appData.fischerCastling) {
17701 /* [HGM] write directly from rights */
17702 if(boards[move][CASTLING][2] != NoRights &&
17703 boards[move][CASTLING][0] != NoRights )
17704 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17705 if(boards[move][CASTLING][2] != NoRights &&
17706 boards[move][CASTLING][1] != NoRights )
17707 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17708 if(boards[move][CASTLING][5] != NoRights &&
17709 boards[move][CASTLING][3] != NoRights )
17710 *p++ = boards[move][CASTLING][3] + AAA;
17711 if(boards[move][CASTLING][5] != NoRights &&
17712 boards[move][CASTLING][4] != NoRights )
17713 *p++ = boards[move][CASTLING][4] + AAA;
17716 /* [HGM] write true castling rights */
17717 if( nrCastlingRights == 6 ) {
17719 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17720 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
17721 q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17722 boards[move][CASTLING][2] != NoRights );
17723 if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17724 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17725 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17726 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17727 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17731 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17732 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
17733 q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17734 boards[move][CASTLING][5] != NoRights );
17735 if(gameInfo.variant == VariantSChess) {
17736 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17737 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17738 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17739 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17744 if (q == p) *p++ = '-'; /* No castling rights */
17748 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17749 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17750 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17751 /* En passant target square */
17752 if (move > backwardMostMove) {
17753 fromX = moveList[move - 1][0] - AAA;
17754 fromY = moveList[move - 1][1] - ONE;
17755 toX = moveList[move - 1][2] - AAA;
17756 toY = moveList[move - 1][3] - ONE;
17757 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17758 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17759 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17761 /* 2-square pawn move just happened */
17763 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17767 } else if(move == backwardMostMove) {
17768 // [HGM] perhaps we should always do it like this, and forget the above?
17769 if((signed char)boards[move][EP_STATUS] >= 0) {
17770 *p++ = boards[move][EP_STATUS] + AAA;
17771 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17783 { int i = 0, j=move;
17785 /* [HGM] find reversible plies */
17786 if (appData.debugMode) { int k;
17787 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17788 for(k=backwardMostMove; k<=forwardMostMove; k++)
17789 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17793 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17794 if( j == backwardMostMove ) i += initialRulePlies;
17795 sprintf(p, "%d ", i);
17796 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17798 /* Fullmove number */
17799 sprintf(p, "%d", (move / 2) + 1);
17800 } else *--p = NULLCHAR;
17802 return StrSave(buf);
17806 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17808 int i, j, k, w=0, subst=0, shuffle=0;
17810 int emptycount, virgin[BOARD_FILES];
17815 /* Piece placement data */
17816 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17819 if (*p == '/' || *p == ' ' || *p == '[' ) {
17821 emptycount = gameInfo.boardWidth - j;
17822 while (emptycount--)
17823 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17824 if (*p == '/') p++;
17825 else if(autoSize) { // we stumbled unexpectedly into end of board
17826 for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17827 for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17829 appData.NrRanks = gameInfo.boardHeight - i; i=0;
17832 #if(BOARD_FILES >= 10)*0
17833 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17834 p++; emptycount=10;
17835 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17836 while (emptycount--)
17837 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17839 } else if (*p == '*') {
17840 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17841 } else if (isdigit(*p)) {
17842 emptycount = *p++ - '0';
17843 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17844 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17845 while (emptycount--)
17846 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17847 } else if (*p == '<') {
17848 if(i == BOARD_HEIGHT-1) shuffle = 1;
17849 else if (i != 0 || !shuffle) return FALSE;
17851 } else if (shuffle && *p == '>') {
17852 p++; // for now ignore closing shuffle range, and assume rank-end
17853 } else if (*p == '?') {
17854 if (j >= gameInfo.boardWidth) return FALSE;
17855 if (i != 0 && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
17856 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
17857 } else if (*p == '+' || isalpha(*p)) {
17858 if (j >= gameInfo.boardWidth) return FALSE;
17860 piece = CharToPiece(*++p);
17861 if(piece == EmptySquare) return FALSE; /* unknown piece */
17862 piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17863 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17864 } else piece = CharToPiece(*p++);
17866 if(piece==EmptySquare) return FALSE; /* unknown piece */
17867 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17868 piece = (ChessSquare) (PROMOTED piece);
17869 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17872 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17878 while (*p == '/' || *p == ' ') p++;
17880 if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17882 /* [HGM] by default clear Crazyhouse holdings, if present */
17883 if(gameInfo.holdingsWidth) {
17884 for(i=0; i<BOARD_HEIGHT; i++) {
17885 board[i][0] = EmptySquare; /* black holdings */
17886 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17887 board[i][1] = (ChessSquare) 0; /* black counts */
17888 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17892 /* [HGM] look for Crazyhouse holdings here */
17893 while(*p==' ') p++;
17894 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17895 int swap=0, wcnt=0, bcnt=0;
17897 if(*p == '<') swap++, p++;
17898 if(*p == '-' ) p++; /* empty holdings */ else {
17899 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17900 /* if we would allow FEN reading to set board size, we would */
17901 /* have to add holdings and shift the board read so far here */
17902 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17904 if((int) piece >= (int) BlackPawn ) {
17905 i = (int)piece - (int)BlackPawn;
17906 i = PieceToNumber((ChessSquare)i);
17907 if( i >= gameInfo.holdingsSize ) return FALSE;
17908 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17909 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
17912 i = (int)piece - (int)WhitePawn;
17913 i = PieceToNumber((ChessSquare)i);
17914 if( i >= gameInfo.holdingsSize ) return FALSE;
17915 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
17916 board[i][BOARD_WIDTH-2]++; /* black holdings */
17920 if(subst) { // substitute back-rank question marks by holdings pieces
17921 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
17922 int k, m, n = bcnt + 1;
17923 if(board[0][j] == ClearBoard) {
17924 if(!wcnt) return FALSE;
17926 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
17927 board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
17928 if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
17932 if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
17933 if(!bcnt) return FALSE;
17934 if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
17935 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
17936 board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
17937 if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
17948 if(subst) return FALSE; // substitution requested, but no holdings
17950 while(*p == ' ') p++;
17954 if(appData.colorNickNames) {
17955 if( c == appData.colorNickNames[0] ) c = 'w'; else
17956 if( c == appData.colorNickNames[1] ) c = 'b';
17960 *blackPlaysFirst = FALSE;
17963 *blackPlaysFirst = TRUE;
17969 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17970 /* return the extra info in global variiables */
17972 /* set defaults in case FEN is incomplete */
17973 board[EP_STATUS] = EP_UNKNOWN;
17974 for(i=0; i<nrCastlingRights; i++ ) {
17975 board[CASTLING][i] =
17976 appData.fischerCastling ? NoRights : initialRights[i];
17977 } /* assume possible unless obviously impossible */
17978 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17979 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17980 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17981 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17982 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17983 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17984 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17985 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17988 while(*p==' ') p++;
17989 if(nrCastlingRights) {
17991 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17992 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17993 /* castling indicator present, so default becomes no castlings */
17994 for(i=0; i<nrCastlingRights; i++ ) {
17995 board[CASTLING][i] = NoRights;
17998 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17999 (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18000 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18001 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
18002 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18004 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18005 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
18006 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
18008 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18009 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18010 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18011 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18012 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18013 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18016 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
18017 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18018 board[CASTLING][2] = whiteKingFile;
18019 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18020 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18021 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18024 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
18025 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18026 board[CASTLING][2] = whiteKingFile;
18027 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18028 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18029 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18032 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
18033 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18034 board[CASTLING][5] = blackKingFile;
18035 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18036 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18037 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18040 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
18041 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18042 board[CASTLING][5] = blackKingFile;
18043 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18044 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18045 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18048 default: /* FRC castlings */
18049 if(c >= 'a') { /* black rights */
18050 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18051 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18052 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18053 if(i == BOARD_RGHT) break;
18054 board[CASTLING][5] = i;
18056 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
18057 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
18059 board[CASTLING][3] = c;
18061 board[CASTLING][4] = c;
18062 } else { /* white rights */
18063 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18064 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18065 if(board[0][i] == WhiteKing) break;
18066 if(i == BOARD_RGHT) break;
18067 board[CASTLING][2] = i;
18068 c -= AAA - 'a' + 'A';
18069 if(board[0][c] >= WhiteKing) break;
18071 board[CASTLING][0] = c;
18073 board[CASTLING][1] = c;
18077 for(i=0; i<nrCastlingRights; i++)
18078 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18079 if(gameInfo.variant == VariantSChess)
18080 for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18081 if(fischer && shuffle) appData.fischerCastling = TRUE;
18082 if (appData.debugMode) {
18083 fprintf(debugFP, "FEN castling rights:");
18084 for(i=0; i<nrCastlingRights; i++)
18085 fprintf(debugFP, " %d", board[CASTLING][i]);
18086 fprintf(debugFP, "\n");
18089 while(*p==' ') p++;
18092 if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18094 /* read e.p. field in games that know e.p. capture */
18095 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
18096 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18097 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18099 p++; board[EP_STATUS] = EP_NONE;
18101 char c = *p++ - AAA;
18103 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18104 if(*p >= '0' && *p <='9') p++;
18105 board[EP_STATUS] = c;
18110 if(sscanf(p, "%d", &i) == 1) {
18111 FENrulePlies = i; /* 50-move ply counter */
18112 /* (The move number is still ignored) */
18119 EditPositionPasteFEN (char *fen)
18122 Board initial_position;
18124 if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18125 DisplayError(_("Bad FEN position in clipboard"), 0);
18128 int savedBlackPlaysFirst = blackPlaysFirst;
18129 EditPositionEvent();
18130 blackPlaysFirst = savedBlackPlaysFirst;
18131 CopyBoard(boards[0], initial_position);
18132 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18133 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18134 DisplayBothClocks();
18135 DrawPosition(FALSE, boards[currentMove]);
18140 static char cseq[12] = "\\ ";
18143 set_cont_sequence (char *new_seq)
18148 // handle bad attempts to set the sequence
18150 return 0; // acceptable error - no debug
18152 len = strlen(new_seq);
18153 ret = (len > 0) && (len < sizeof(cseq));
18155 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18156 else if (appData.debugMode)
18157 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18162 reformat a source message so words don't cross the width boundary. internal
18163 newlines are not removed. returns the wrapped size (no null character unless
18164 included in source message). If dest is NULL, only calculate the size required
18165 for the dest buffer. lp argument indicats line position upon entry, and it's
18166 passed back upon exit.
18169 wrap (char *dest, char *src, int count, int width, int *lp)
18171 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18173 cseq_len = strlen(cseq);
18174 old_line = line = *lp;
18175 ansi = len = clen = 0;
18177 for (i=0; i < count; i++)
18179 if (src[i] == '\033')
18182 // if we hit the width, back up
18183 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18185 // store i & len in case the word is too long
18186 old_i = i, old_len = len;
18188 // find the end of the last word
18189 while (i && src[i] != ' ' && src[i] != '\n')
18195 // word too long? restore i & len before splitting it
18196 if ((old_i-i+clen) >= width)
18203 if (i && src[i-1] == ' ')
18206 if (src[i] != ' ' && src[i] != '\n')
18213 // now append the newline and continuation sequence
18218 strncpy(dest+len, cseq, cseq_len);
18226 dest[len] = src[i];
18230 if (src[i] == '\n')
18235 if (dest && appData.debugMode)
18237 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18238 count, width, line, len, *lp);
18239 show_bytes(debugFP, src, count);
18240 fprintf(debugFP, "\ndest: ");
18241 show_bytes(debugFP, dest, len);
18242 fprintf(debugFP, "\n");
18244 *lp = dest ? line : old_line;
18249 // [HGM] vari: routines for shelving variations
18250 Boolean modeRestore = FALSE;
18253 PushInner (int firstMove, int lastMove)
18255 int i, j, nrMoves = lastMove - firstMove;
18257 // push current tail of game on stack
18258 savedResult[storedGames] = gameInfo.result;
18259 savedDetails[storedGames] = gameInfo.resultDetails;
18260 gameInfo.resultDetails = NULL;
18261 savedFirst[storedGames] = firstMove;
18262 savedLast [storedGames] = lastMove;
18263 savedFramePtr[storedGames] = framePtr;
18264 framePtr -= nrMoves; // reserve space for the boards
18265 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18266 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18267 for(j=0; j<MOVE_LEN; j++)
18268 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18269 for(j=0; j<2*MOVE_LEN; j++)
18270 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18271 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18272 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18273 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18274 pvInfoList[firstMove+i-1].depth = 0;
18275 commentList[framePtr+i] = commentList[firstMove+i];
18276 commentList[firstMove+i] = NULL;
18280 forwardMostMove = firstMove; // truncate game so we can start variation
18284 PushTail (int firstMove, int lastMove)
18286 if(appData.icsActive) { // only in local mode
18287 forwardMostMove = currentMove; // mimic old ICS behavior
18290 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18292 PushInner(firstMove, lastMove);
18293 if(storedGames == 1) GreyRevert(FALSE);
18294 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18298 PopInner (Boolean annotate)
18301 char buf[8000], moveBuf[20];
18303 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18304 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18305 nrMoves = savedLast[storedGames] - currentMove;
18308 if(!WhiteOnMove(currentMove))
18309 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18310 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18311 for(i=currentMove; i<forwardMostMove; i++) {
18313 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18314 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18315 strcat(buf, moveBuf);
18316 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18317 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18321 for(i=1; i<=nrMoves; i++) { // copy last variation back
18322 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18323 for(j=0; j<MOVE_LEN; j++)
18324 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18325 for(j=0; j<2*MOVE_LEN; j++)
18326 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18327 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18328 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18329 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18330 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18331 commentList[currentMove+i] = commentList[framePtr+i];
18332 commentList[framePtr+i] = NULL;
18334 if(annotate) AppendComment(currentMove+1, buf, FALSE);
18335 framePtr = savedFramePtr[storedGames];
18336 gameInfo.result = savedResult[storedGames];
18337 if(gameInfo.resultDetails != NULL) {
18338 free(gameInfo.resultDetails);
18340 gameInfo.resultDetails = savedDetails[storedGames];
18341 forwardMostMove = currentMove + nrMoves;
18345 PopTail (Boolean annotate)
18347 if(appData.icsActive) return FALSE; // only in local mode
18348 if(!storedGames) return FALSE; // sanity
18349 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18351 PopInner(annotate);
18352 if(currentMove < forwardMostMove) ForwardEvent(); else
18353 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18355 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18361 { // remove all shelved variations
18363 for(i=0; i<storedGames; i++) {
18364 if(savedDetails[i])
18365 free(savedDetails[i]);
18366 savedDetails[i] = NULL;
18368 for(i=framePtr; i<MAX_MOVES; i++) {
18369 if(commentList[i]) free(commentList[i]);
18370 commentList[i] = NULL;
18372 framePtr = MAX_MOVES-1;
18377 LoadVariation (int index, char *text)
18378 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18379 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18380 int level = 0, move;
18382 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18383 // first find outermost bracketing variation
18384 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18385 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18386 if(*p == '{') wait = '}'; else
18387 if(*p == '[') wait = ']'; else
18388 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18389 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18391 if(*p == wait) wait = NULLCHAR; // closing ]} found
18394 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18395 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18396 end[1] = NULLCHAR; // clip off comment beyond variation
18397 ToNrEvent(currentMove-1);
18398 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18399 // kludge: use ParsePV() to append variation to game
18400 move = currentMove;
18401 ParsePV(start, TRUE, TRUE);
18402 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18403 ClearPremoveHighlights();
18405 ToNrEvent(currentMove+1);
18411 char *p, *q, buf[MSG_SIZ];
18412 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18413 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18414 ParseArgsFromString(buf);
18415 ActivateTheme(TRUE); // also redo colors
18419 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18422 q = appData.themeNames;
18423 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18424 if(appData.useBitmaps) {
18425 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18426 appData.liteBackTextureFile, appData.darkBackTextureFile,
18427 appData.liteBackTextureMode,
18428 appData.darkBackTextureMode );
18430 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18431 Col2Text(2), // lightSquareColor
18432 Col2Text(3) ); // darkSquareColor
18434 if(appData.useBorder) {
18435 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18438 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18440 if(appData.useFont) {
18441 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18442 appData.renderPiecesWithFont,
18443 appData.fontToPieceTable,
18444 Col2Text(9), // appData.fontBackColorWhite
18445 Col2Text(10) ); // appData.fontForeColorBlack
18447 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18448 appData.pieceDirectory);
18449 if(!appData.pieceDirectory[0])
18450 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18451 Col2Text(0), // whitePieceColor
18452 Col2Text(1) ); // blackPieceColor
18454 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18455 Col2Text(4), // highlightSquareColor
18456 Col2Text(5) ); // premoveHighlightColor
18457 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18458 if(insert != q) insert[-1] = NULLCHAR;
18459 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18462 ActivateTheme(FALSE);