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;
851 /* New features added by Tord: */
852 cps->useFEN960 = FALSE;
853 cps->useOOCastle = TRUE;
854 /* End of new features added by Tord. */
855 cps->fenOverride = appData.fenOverride[n];
857 /* [HGM] time odds: set factor for each machine */
858 cps->timeOdds = appData.timeOdds[n];
860 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
861 cps->accumulateTC = appData.accumulateTC[n];
862 cps->maxNrOfSessions = 1;
867 cps->drawDepth = appData.drawDepth[n];
868 cps->supportsNPS = UNKNOWN;
869 cps->memSize = FALSE;
870 cps->maxCores = FALSE;
871 ASSIGN(cps->egtFormats, "");
874 cps->optionSettings = appData.engOptions[n];
876 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
877 cps->isUCI = appData.isUCI[n]; /* [AS] */
878 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
881 if (appData.protocolVersion[n] > PROTOVER
882 || appData.protocolVersion[n] < 1)
887 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
888 appData.protocolVersion[n]);
889 if( (len >= MSG_SIZ) && appData.debugMode )
890 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
892 DisplayFatalError(buf, 0, 2);
896 cps->protocolVersion = appData.protocolVersion[n];
899 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
900 ParseFeatures(appData.featureDefaults, cps);
903 ChessProgramState *savCps;
911 if(WaitForEngine(savCps, LoadEngine)) return;
912 CommonEngineInit(); // recalculate time odds
913 if(gameInfo.variant != StringToVariant(appData.variant)) {
914 // we changed variant when loading the engine; this forces us to reset
915 Reset(TRUE, savCps != &first);
916 oldMode = BeginningOfGame; // to prevent restoring old mode
918 InitChessProgram(savCps, FALSE);
919 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
920 DisplayMessage("", "");
921 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
922 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
925 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
929 ReplaceEngine (ChessProgramState *cps, int n)
931 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
933 if(oldMode != BeginningOfGame) EditGameEvent();
936 appData.noChessProgram = FALSE;
937 appData.clockMode = TRUE;
940 if(n) return; // only startup first engine immediately; second can wait
941 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
945 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
946 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
948 static char resetOptions[] =
949 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
950 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
951 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
952 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
955 FloatToFront(char **list, char *engineLine)
957 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
959 if(appData.recentEngines <= 0) return;
960 TidyProgramName(engineLine, "localhost", tidy+1);
961 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
962 strncpy(buf+1, *list, MSG_SIZ-50);
963 if(p = strstr(buf, tidy)) { // tidy name appears in list
964 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
965 while(*p++ = *++q); // squeeze out
967 strcat(tidy, buf+1); // put list behind tidy name
968 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
969 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
970 ASSIGN(*list, tidy+1);
973 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
976 Load (ChessProgramState *cps, int i)
978 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
979 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
980 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
981 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
982 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
983 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
984 appData.firstProtocolVersion = PROTOVER;
985 ParseArgsFromString(buf);
987 ReplaceEngine(cps, i);
988 FloatToFront(&appData.recentEngineList, engineLine);
992 while(q = strchr(p, SLASH)) p = q+1;
993 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
994 if(engineDir[0] != NULLCHAR) {
995 ASSIGN(appData.directory[i], engineDir); p = engineName;
996 } else if(p != engineName) { // derive directory from engine path, when not given
998 ASSIGN(appData.directory[i], engineName);
1000 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1001 } else { ASSIGN(appData.directory[i], "."); }
1002 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1004 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1005 snprintf(command, MSG_SIZ, "%s %s", p, params);
1008 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1009 ASSIGN(appData.chessProgram[i], p);
1010 appData.isUCI[i] = isUCI;
1011 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1012 appData.hasOwnBookUCI[i] = hasBook;
1013 if(!nickName[0]) useNick = FALSE;
1014 if(useNick) ASSIGN(appData.pgnName[i], nickName);
1018 q = firstChessProgramNames;
1019 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1020 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1021 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1022 quote, p, quote, appData.directory[i],
1023 useNick ? " -fn \"" : "",
1024 useNick ? nickName : "",
1025 useNick ? "\"" : "",
1026 v1 ? " -firstProtocolVersion 1" : "",
1027 hasBook ? "" : " -fNoOwnBookUCI",
1028 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1029 storeVariant ? " -variant " : "",
1030 storeVariant ? VariantName(gameInfo.variant) : "");
1031 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1032 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1033 if(insert != q) insert[-1] = NULLCHAR;
1034 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1036 FloatToFront(&appData.recentEngineList, buf);
1038 ReplaceEngine(cps, i);
1044 int matched, min, sec;
1046 * Parse timeControl resource
1048 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1049 appData.movesPerSession)) {
1051 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1052 DisplayFatalError(buf, 0, 2);
1056 * Parse searchTime resource
1058 if (*appData.searchTime != NULLCHAR) {
1059 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1061 searchTime = min * 60;
1062 } else if (matched == 2) {
1063 searchTime = min * 60 + sec;
1066 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1067 DisplayFatalError(buf, 0, 2);
1076 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1077 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1079 GetTimeMark(&programStartTime);
1080 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1081 appData.seedBase = random() + (random()<<15);
1082 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1084 ClearProgramStats();
1085 programStats.ok_to_send = 1;
1086 programStats.seen_stat = 0;
1089 * Initialize game list
1095 * Internet chess server status
1097 if (appData.icsActive) {
1098 appData.matchMode = FALSE;
1099 appData.matchGames = 0;
1101 appData.noChessProgram = !appData.zippyPlay;
1103 appData.zippyPlay = FALSE;
1104 appData.zippyTalk = FALSE;
1105 appData.noChessProgram = TRUE;
1107 if (*appData.icsHelper != NULLCHAR) {
1108 appData.useTelnet = TRUE;
1109 appData.telnetProgram = appData.icsHelper;
1112 appData.zippyTalk = appData.zippyPlay = FALSE;
1115 /* [AS] Initialize pv info list [HGM] and game state */
1119 for( i=0; i<=framePtr; i++ ) {
1120 pvInfoList[i].depth = -1;
1121 boards[i][EP_STATUS] = EP_NONE;
1122 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1128 /* [AS] Adjudication threshold */
1129 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1131 InitEngine(&first, 0);
1132 InitEngine(&second, 1);
1135 pairing.which = "pairing"; // pairing engine
1136 pairing.pr = NoProc;
1138 pairing.program = appData.pairingEngine;
1139 pairing.host = "localhost";
1142 if (appData.icsActive) {
1143 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1144 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1145 appData.clockMode = FALSE;
1146 first.sendTime = second.sendTime = 0;
1150 /* Override some settings from environment variables, for backward
1151 compatibility. Unfortunately it's not feasible to have the env
1152 vars just set defaults, at least in xboard. Ugh.
1154 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1159 if (!appData.icsActive) {
1163 /* Check for variants that are supported only in ICS mode,
1164 or not at all. Some that are accepted here nevertheless
1165 have bugs; see comments below.
1167 VariantClass variant = StringToVariant(appData.variant);
1169 case VariantBughouse: /* need four players and two boards */
1170 case VariantKriegspiel: /* need to hide pieces and move details */
1171 /* case VariantFischeRandom: (Fabien: moved below) */
1172 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1173 if( (len >= MSG_SIZ) && appData.debugMode )
1174 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1176 DisplayFatalError(buf, 0, 2);
1179 case VariantUnknown:
1180 case VariantLoadable:
1190 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1191 if( (len >= MSG_SIZ) && appData.debugMode )
1192 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1194 DisplayFatalError(buf, 0, 2);
1197 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1198 case VariantFairy: /* [HGM] TestLegality definitely off! */
1199 case VariantGothic: /* [HGM] should work */
1200 case VariantCapablanca: /* [HGM] should work */
1201 case VariantCourier: /* [HGM] initial forced moves not implemented */
1202 case VariantShogi: /* [HGM] could still mate with pawn drop */
1203 case VariantChu: /* [HGM] experimental */
1204 case VariantKnightmate: /* [HGM] should work */
1205 case VariantCylinder: /* [HGM] untested */
1206 case VariantFalcon: /* [HGM] untested */
1207 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1208 offboard interposition not understood */
1209 case VariantNormal: /* definitely works! */
1210 case VariantWildCastle: /* pieces not automatically shuffled */
1211 case VariantNoCastle: /* pieces not automatically shuffled */
1212 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1213 case VariantLosers: /* should work except for win condition,
1214 and doesn't know captures are mandatory */
1215 case VariantSuicide: /* should work except for win condition,
1216 and doesn't know captures are mandatory */
1217 case VariantGiveaway: /* should work except for win condition,
1218 and doesn't know captures are mandatory */
1219 case VariantTwoKings: /* should work */
1220 case VariantAtomic: /* should work except for win condition */
1221 case Variant3Check: /* should work except for win condition */
1222 case VariantShatranj: /* should work except for all win conditions */
1223 case VariantMakruk: /* should work except for draw countdown */
1224 case VariantASEAN : /* should work except for draw countdown */
1225 case VariantBerolina: /* might work if TestLegality is off */
1226 case VariantCapaRandom: /* should work */
1227 case VariantJanus: /* should work */
1228 case VariantSuper: /* experimental */
1229 case VariantGreat: /* experimental, requires legality testing to be off */
1230 case VariantSChess: /* S-Chess, should work */
1231 case VariantGrand: /* should work */
1232 case VariantSpartan: /* should work */
1233 case VariantLion: /* should work */
1234 case VariantChuChess: /* should work */
1242 NextIntegerFromString (char ** str, long * value)
1247 while( *s == ' ' || *s == '\t' ) {
1253 if( *s >= '0' && *s <= '9' ) {
1254 while( *s >= '0' && *s <= '9' ) {
1255 *value = *value * 10 + (*s - '0');
1268 NextTimeControlFromString (char ** str, long * value)
1271 int result = NextIntegerFromString( str, &temp );
1274 *value = temp * 60; /* Minutes */
1275 if( **str == ':' ) {
1277 result = NextIntegerFromString( str, &temp );
1278 *value += temp; /* Seconds */
1286 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1287 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1288 int result = -1, type = 0; long temp, temp2;
1290 if(**str != ':') return -1; // old params remain in force!
1292 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1293 if( NextIntegerFromString( str, &temp ) ) return -1;
1294 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1297 /* time only: incremental or sudden-death time control */
1298 if(**str == '+') { /* increment follows; read it */
1300 if(**str == '!') type = *(*str)++; // Bronstein TC
1301 if(result = NextIntegerFromString( str, &temp2)) return -1;
1302 *inc = temp2 * 1000;
1303 if(**str == '.') { // read fraction of increment
1304 char *start = ++(*str);
1305 if(result = NextIntegerFromString( str, &temp2)) return -1;
1307 while(start++ < *str) temp2 /= 10;
1311 *moves = 0; *tc = temp * 1000; *incType = type;
1315 (*str)++; /* classical time control */
1316 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1328 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1329 { /* [HGM] get time to add from the multi-session time-control string */
1330 int incType, moves=1; /* kludge to force reading of first session */
1331 long time, increment;
1334 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1336 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1337 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1338 if(movenr == -1) return time; /* last move before new session */
1339 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1340 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1341 if(!moves) return increment; /* current session is incremental */
1342 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1343 } while(movenr >= -1); /* try again for next session */
1345 return 0; // no new time quota on this move
1349 ParseTimeControl (char *tc, float ti, int mps)
1353 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1356 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1357 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1358 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1362 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1364 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1367 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1369 snprintf(buf, MSG_SIZ, ":%s", mytc);
1371 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1373 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1378 /* Parse second time control */
1381 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1389 timeControl_2 = tc2 * 1000;
1399 timeControl = tc1 * 1000;
1402 timeIncrement = ti * 1000; /* convert to ms */
1403 movesPerSession = 0;
1406 movesPerSession = mps;
1414 if (appData.debugMode) {
1415 # ifdef __GIT_VERSION
1416 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1418 fprintf(debugFP, "Version: %s\n", programVersion);
1421 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1423 set_cont_sequence(appData.wrapContSeq);
1424 if (appData.matchGames > 0) {
1425 appData.matchMode = TRUE;
1426 } else if (appData.matchMode) {
1427 appData.matchGames = 1;
1429 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1430 appData.matchGames = appData.sameColorGames;
1431 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1432 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1433 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1436 if (appData.noChessProgram || first.protocolVersion == 1) {
1439 /* kludge: allow timeout for initial "feature" commands */
1441 DisplayMessage("", _("Starting chess program"));
1442 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1447 CalculateIndex (int index, int gameNr)
1448 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1450 if(index > 0) return index; // fixed nmber
1451 if(index == 0) return 1;
1452 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1453 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1458 LoadGameOrPosition (int gameNr)
1459 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1460 if (*appData.loadGameFile != NULLCHAR) {
1461 if (!LoadGameFromFile(appData.loadGameFile,
1462 CalculateIndex(appData.loadGameIndex, gameNr),
1463 appData.loadGameFile, FALSE)) {
1464 DisplayFatalError(_("Bad game file"), 0, 1);
1467 } else if (*appData.loadPositionFile != NULLCHAR) {
1468 if (!LoadPositionFromFile(appData.loadPositionFile,
1469 CalculateIndex(appData.loadPositionIndex, gameNr),
1470 appData.loadPositionFile)) {
1471 DisplayFatalError(_("Bad position file"), 0, 1);
1479 ReserveGame (int gameNr, char resChar)
1481 FILE *tf = fopen(appData.tourneyFile, "r+");
1482 char *p, *q, c, buf[MSG_SIZ];
1483 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1484 safeStrCpy(buf, lastMsg, MSG_SIZ);
1485 DisplayMessage(_("Pick new game"), "");
1486 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1487 ParseArgsFromFile(tf);
1488 p = q = appData.results;
1489 if(appData.debugMode) {
1490 char *r = appData.participants;
1491 fprintf(debugFP, "results = '%s'\n", p);
1492 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1493 fprintf(debugFP, "\n");
1495 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1497 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1498 safeStrCpy(q, p, strlen(p) + 2);
1499 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1500 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1501 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1502 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1505 fseek(tf, -(strlen(p)+4), SEEK_END);
1507 if(c != '"') // depending on DOS or Unix line endings we can be one off
1508 fseek(tf, -(strlen(p)+2), SEEK_END);
1509 else fseek(tf, -(strlen(p)+3), SEEK_END);
1510 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1511 DisplayMessage(buf, "");
1512 free(p); appData.results = q;
1513 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1514 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1515 int round = appData.defaultMatchGames * appData.tourneyType;
1516 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1517 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1518 UnloadEngine(&first); // next game belongs to other pairing;
1519 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1521 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1525 MatchEvent (int mode)
1526 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1528 if(matchMode) { // already in match mode: switch it off
1530 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1533 // if(gameMode != BeginningOfGame) {
1534 // DisplayError(_("You can only start a match from the initial position."), 0);
1538 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1539 /* Set up machine vs. machine match */
1541 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1542 if(appData.tourneyFile[0]) {
1544 if(nextGame > appData.matchGames) {
1546 if(strchr(appData.results, '*') == NULL) {
1548 appData.tourneyCycles++;
1549 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1551 NextTourneyGame(-1, &dummy);
1553 if(nextGame <= appData.matchGames) {
1554 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1556 ScheduleDelayedEvent(NextMatchGame, 10000);
1561 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1562 DisplayError(buf, 0);
1563 appData.tourneyFile[0] = 0;
1567 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1568 DisplayFatalError(_("Can't have a match with no chess programs"),
1573 matchGame = roundNr = 1;
1574 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1578 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1581 InitBackEnd3 P((void))
1583 GameMode initialMode;
1587 if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode && // mode involves only first engine
1588 !strcmp(appData.variant, "normal") && // no explicit variant request
1589 appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 && // no size overrides requested
1590 !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") && // but 'normal' won't work with engine
1591 !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1592 char c, *q = first.variants, *p = strchr(q, ',');
1593 if(p) *p = NULLCHAR;
1594 if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1596 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1597 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1598 ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1599 Reset(TRUE, FALSE); // and re-initialize
1604 InitChessProgram(&first, startedFromSetupPosition);
1606 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1607 free(programVersion);
1608 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1609 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1610 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1613 if (appData.icsActive) {
1615 /* [DM] Make a console window if needed [HGM] merged ifs */
1621 if (*appData.icsCommPort != NULLCHAR)
1622 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1623 appData.icsCommPort);
1625 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1626 appData.icsHost, appData.icsPort);
1628 if( (len >= MSG_SIZ) && appData.debugMode )
1629 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1631 DisplayFatalError(buf, err, 1);
1636 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1638 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1639 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1640 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1641 } else if (appData.noChessProgram) {
1647 if (*appData.cmailGameName != NULLCHAR) {
1649 OpenLoopback(&cmailPR);
1651 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1655 DisplayMessage("", "");
1656 if (StrCaseCmp(appData.initialMode, "") == 0) {
1657 initialMode = BeginningOfGame;
1658 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1659 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1660 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1661 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1664 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1665 initialMode = TwoMachinesPlay;
1666 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1667 initialMode = AnalyzeFile;
1668 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1669 initialMode = AnalyzeMode;
1670 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1671 initialMode = MachinePlaysWhite;
1672 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1673 initialMode = MachinePlaysBlack;
1674 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1675 initialMode = EditGame;
1676 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1677 initialMode = EditPosition;
1678 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1679 initialMode = Training;
1681 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1682 if( (len >= MSG_SIZ) && appData.debugMode )
1683 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1685 DisplayFatalError(buf, 0, 2);
1689 if (appData.matchMode) {
1690 if(appData.tourneyFile[0]) { // start tourney from command line
1692 if(f = fopen(appData.tourneyFile, "r")) {
1693 ParseArgsFromFile(f); // make sure tourney parmeters re known
1695 appData.clockMode = TRUE;
1697 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1700 } else if (*appData.cmailGameName != NULLCHAR) {
1701 /* Set up cmail mode */
1702 ReloadCmailMsgEvent(TRUE);
1704 /* Set up other modes */
1705 if (initialMode == AnalyzeFile) {
1706 if (*appData.loadGameFile == NULLCHAR) {
1707 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1711 if (*appData.loadGameFile != NULLCHAR) {
1712 (void) LoadGameFromFile(appData.loadGameFile,
1713 appData.loadGameIndex,
1714 appData.loadGameFile, TRUE);
1715 } else if (*appData.loadPositionFile != NULLCHAR) {
1716 (void) LoadPositionFromFile(appData.loadPositionFile,
1717 appData.loadPositionIndex,
1718 appData.loadPositionFile);
1719 /* [HGM] try to make self-starting even after FEN load */
1720 /* to allow automatic setup of fairy variants with wtm */
1721 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1722 gameMode = BeginningOfGame;
1723 setboardSpoiledMachineBlack = 1;
1725 /* [HGM] loadPos: make that every new game uses the setup */
1726 /* from file as long as we do not switch variant */
1727 if(!blackPlaysFirst) {
1728 startedFromPositionFile = TRUE;
1729 CopyBoard(filePosition, boards[0]);
1732 if (initialMode == AnalyzeMode) {
1733 if (appData.noChessProgram) {
1734 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1737 if (appData.icsActive) {
1738 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1742 } else if (initialMode == AnalyzeFile) {
1743 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1744 ShowThinkingEvent();
1746 AnalysisPeriodicEvent(1);
1747 } else if (initialMode == MachinePlaysWhite) {
1748 if (appData.noChessProgram) {
1749 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1753 if (appData.icsActive) {
1754 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1758 MachineWhiteEvent();
1759 } else if (initialMode == MachinePlaysBlack) {
1760 if (appData.noChessProgram) {
1761 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1765 if (appData.icsActive) {
1766 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1770 MachineBlackEvent();
1771 } else if (initialMode == TwoMachinesPlay) {
1772 if (appData.noChessProgram) {
1773 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1777 if (appData.icsActive) {
1778 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1783 } else if (initialMode == EditGame) {
1785 } else if (initialMode == EditPosition) {
1786 EditPositionEvent();
1787 } else if (initialMode == Training) {
1788 if (*appData.loadGameFile == NULLCHAR) {
1789 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1798 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1800 DisplayBook(current+1);
1802 MoveHistorySet( movelist, first, last, current, pvInfoList );
1804 EvalGraphSet( first, last, current, pvInfoList );
1806 MakeEngineOutputTitle();
1810 * Establish will establish a contact to a remote host.port.
1811 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1812 * used to talk to the host.
1813 * Returns 0 if okay, error code if not.
1820 if (*appData.icsCommPort != NULLCHAR) {
1821 /* Talk to the host through a serial comm port */
1822 return OpenCommPort(appData.icsCommPort, &icsPR);
1824 } else if (*appData.gateway != NULLCHAR) {
1825 if (*appData.remoteShell == NULLCHAR) {
1826 /* Use the rcmd protocol to run telnet program on a gateway host */
1827 snprintf(buf, sizeof(buf), "%s %s %s",
1828 appData.telnetProgram, appData.icsHost, appData.icsPort);
1829 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1832 /* Use the rsh program to run telnet program on a gateway host */
1833 if (*appData.remoteUser == NULLCHAR) {
1834 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1835 appData.gateway, appData.telnetProgram,
1836 appData.icsHost, appData.icsPort);
1838 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1839 appData.remoteShell, appData.gateway,
1840 appData.remoteUser, appData.telnetProgram,
1841 appData.icsHost, appData.icsPort);
1843 return StartChildProcess(buf, "", &icsPR);
1846 } else if (appData.useTelnet) {
1847 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1850 /* TCP socket interface differs somewhat between
1851 Unix and NT; handle details in the front end.
1853 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1858 EscapeExpand (char *p, char *q)
1859 { // [HGM] initstring: routine to shape up string arguments
1860 while(*p++ = *q++) if(p[-1] == '\\')
1862 case 'n': p[-1] = '\n'; break;
1863 case 'r': p[-1] = '\r'; break;
1864 case 't': p[-1] = '\t'; break;
1865 case '\\': p[-1] = '\\'; break;
1866 case 0: *p = 0; return;
1867 default: p[-1] = q[-1]; break;
1872 show_bytes (FILE *fp, char *buf, int count)
1875 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1876 fprintf(fp, "\\%03o", *buf & 0xff);
1885 /* Returns an errno value */
1887 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1889 char buf[8192], *p, *q, *buflim;
1890 int left, newcount, outcount;
1892 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1893 *appData.gateway != NULLCHAR) {
1894 if (appData.debugMode) {
1895 fprintf(debugFP, ">ICS: ");
1896 show_bytes(debugFP, message, count);
1897 fprintf(debugFP, "\n");
1899 return OutputToProcess(pr, message, count, outError);
1902 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1909 if (appData.debugMode) {
1910 fprintf(debugFP, ">ICS: ");
1911 show_bytes(debugFP, buf, newcount);
1912 fprintf(debugFP, "\n");
1914 outcount = OutputToProcess(pr, buf, newcount, outError);
1915 if (outcount < newcount) return -1; /* to be sure */
1922 } else if (((unsigned char) *p) == TN_IAC) {
1923 *q++ = (char) TN_IAC;
1930 if (appData.debugMode) {
1931 fprintf(debugFP, ">ICS: ");
1932 show_bytes(debugFP, buf, newcount);
1933 fprintf(debugFP, "\n");
1935 outcount = OutputToProcess(pr, buf, newcount, outError);
1936 if (outcount < newcount) return -1; /* to be sure */
1941 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1943 int outError, outCount;
1944 static int gotEof = 0;
1947 /* Pass data read from player on to ICS */
1950 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1951 if (outCount < count) {
1952 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1954 if(have_sent_ICS_logon == 2) {
1955 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1956 fprintf(ini, "%s", message);
1957 have_sent_ICS_logon = 3;
1959 have_sent_ICS_logon = 1;
1960 } else if(have_sent_ICS_logon == 3) {
1961 fprintf(ini, "%s", message);
1963 have_sent_ICS_logon = 1;
1965 } else if (count < 0) {
1966 RemoveInputSource(isr);
1967 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1968 } else if (gotEof++ > 0) {
1969 RemoveInputSource(isr);
1970 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1976 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1977 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1978 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1979 SendToICS("date\n");
1980 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1983 /* added routine for printf style output to ics */
1985 ics_printf (char *format, ...)
1987 char buffer[MSG_SIZ];
1990 va_start(args, format);
1991 vsnprintf(buffer, sizeof(buffer), format, args);
1992 buffer[sizeof(buffer)-1] = '\0';
2000 int count, outCount, outError;
2002 if (icsPR == NoProc) return;
2005 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2006 if (outCount < count) {
2007 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2011 /* This is used for sending logon scripts to the ICS. Sending
2012 without a delay causes problems when using timestamp on ICC
2013 (at least on my machine). */
2015 SendToICSDelayed (char *s, long msdelay)
2017 int count, outCount, outError;
2019 if (icsPR == NoProc) return;
2022 if (appData.debugMode) {
2023 fprintf(debugFP, ">ICS: ");
2024 show_bytes(debugFP, s, count);
2025 fprintf(debugFP, "\n");
2027 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2029 if (outCount < count) {
2030 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2035 /* Remove all highlighting escape sequences in s
2036 Also deletes any suffix starting with '('
2039 StripHighlightAndTitle (char *s)
2041 static char retbuf[MSG_SIZ];
2044 while (*s != NULLCHAR) {
2045 while (*s == '\033') {
2046 while (*s != NULLCHAR && !isalpha(*s)) s++;
2047 if (*s != NULLCHAR) s++;
2049 while (*s != NULLCHAR && *s != '\033') {
2050 if (*s == '(' || *s == '[') {
2061 /* Remove all highlighting escape sequences in s */
2063 StripHighlight (char *s)
2065 static char retbuf[MSG_SIZ];
2068 while (*s != NULLCHAR) {
2069 while (*s == '\033') {
2070 while (*s != NULLCHAR && !isalpha(*s)) s++;
2071 if (*s != NULLCHAR) s++;
2073 while (*s != NULLCHAR && *s != '\033') {
2081 char engineVariant[MSG_SIZ];
2082 char *variantNames[] = VARIANT_NAMES;
2084 VariantName (VariantClass v)
2086 if(v == VariantUnknown || *engineVariant) return engineVariant;
2087 return variantNames[v];
2091 /* Identify a variant from the strings the chess servers use or the
2092 PGN Variant tag names we use. */
2094 StringToVariant (char *e)
2098 VariantClass v = VariantNormal;
2099 int i, found = FALSE;
2105 /* [HGM] skip over optional board-size prefixes */
2106 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2107 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2108 while( *e++ != '_');
2111 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2115 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2116 if (p = StrCaseStr(e, variantNames[i])) {
2117 if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
2118 v = (VariantClass) i;
2125 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2126 || StrCaseStr(e, "wild/fr")
2127 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2128 v = VariantFischeRandom;
2129 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2130 (i = 1, p = StrCaseStr(e, "w"))) {
2132 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2139 case 0: /* FICS only, actually */
2141 /* Castling legal even if K starts on d-file */
2142 v = VariantWildCastle;
2147 /* Castling illegal even if K & R happen to start in
2148 normal positions. */
2149 v = VariantNoCastle;
2162 /* Castling legal iff K & R start in normal positions */
2168 /* Special wilds for position setup; unclear what to do here */
2169 v = VariantLoadable;
2172 /* Bizarre ICC game */
2173 v = VariantTwoKings;
2176 v = VariantKriegspiel;
2182 v = VariantFischeRandom;
2185 v = VariantCrazyhouse;
2188 v = VariantBughouse;
2194 /* Not quite the same as FICS suicide! */
2195 v = VariantGiveaway;
2201 v = VariantShatranj;
2204 /* Temporary names for future ICC types. The name *will* change in
2205 the next xboard/WinBoard release after ICC defines it. */
2243 v = VariantCapablanca;
2246 v = VariantKnightmate;
2252 v = VariantCylinder;
2258 v = VariantCapaRandom;
2261 v = VariantBerolina;
2273 /* Found "wild" or "w" in the string but no number;
2274 must assume it's normal chess. */
2278 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2279 if( (len >= MSG_SIZ) && appData.debugMode )
2280 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2282 DisplayError(buf, 0);
2288 if (appData.debugMode) {
2289 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2290 e, wnum, VariantName(v));
2295 static int leftover_start = 0, leftover_len = 0;
2296 char star_match[STAR_MATCH_N][MSG_SIZ];
2298 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2299 advance *index beyond it, and set leftover_start to the new value of
2300 *index; else return FALSE. If pattern contains the character '*', it
2301 matches any sequence of characters not containing '\r', '\n', or the
2302 character following the '*' (if any), and the matched sequence(s) are
2303 copied into star_match.
2306 looking_at ( char *buf, int *index, char *pattern)
2308 char *bufp = &buf[*index], *patternp = pattern;
2310 char *matchp = star_match[0];
2313 if (*patternp == NULLCHAR) {
2314 *index = leftover_start = bufp - buf;
2318 if (*bufp == NULLCHAR) return FALSE;
2319 if (*patternp == '*') {
2320 if (*bufp == *(patternp + 1)) {
2322 matchp = star_match[++star_count];
2326 } else if (*bufp == '\n' || *bufp == '\r') {
2328 if (*patternp == NULLCHAR)
2333 *matchp++ = *bufp++;
2337 if (*patternp != *bufp) return FALSE;
2344 SendToPlayer (char *data, int length)
2346 int error, outCount;
2347 outCount = OutputToProcess(NoProc, data, length, &error);
2348 if (outCount < length) {
2349 DisplayFatalError(_("Error writing to display"), error, 1);
2354 PackHolding (char packed[], char *holding)
2364 switch (runlength) {
2375 sprintf(q, "%d", runlength);
2387 /* Telnet protocol requests from the front end */
2389 TelnetRequest (unsigned char ddww, unsigned char option)
2391 unsigned char msg[3];
2392 int outCount, outError;
2394 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2396 if (appData.debugMode) {
2397 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2413 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2422 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2425 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2430 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2432 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2439 if (!appData.icsActive) return;
2440 TelnetRequest(TN_DO, TN_ECHO);
2446 if (!appData.icsActive) return;
2447 TelnetRequest(TN_DONT, TN_ECHO);
2451 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2453 /* put the holdings sent to us by the server on the board holdings area */
2454 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2458 if(gameInfo.holdingsWidth < 2) return;
2459 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2460 return; // prevent overwriting by pre-board holdings
2462 if( (int)lowestPiece >= BlackPawn ) {
2465 holdingsStartRow = BOARD_HEIGHT-1;
2468 holdingsColumn = BOARD_WIDTH-1;
2469 countsColumn = BOARD_WIDTH-2;
2470 holdingsStartRow = 0;
2474 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2475 board[i][holdingsColumn] = EmptySquare;
2476 board[i][countsColumn] = (ChessSquare) 0;
2478 while( (p=*holdings++) != NULLCHAR ) {
2479 piece = CharToPiece( ToUpper(p) );
2480 if(piece == EmptySquare) continue;
2481 /*j = (int) piece - (int) WhitePawn;*/
2482 j = PieceToNumber(piece);
2483 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2484 if(j < 0) continue; /* should not happen */
2485 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2486 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2487 board[holdingsStartRow+j*direction][countsColumn]++;
2493 VariantSwitch (Board board, VariantClass newVariant)
2495 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2496 static Board oldBoard;
2498 startedFromPositionFile = FALSE;
2499 if(gameInfo.variant == newVariant) return;
2501 /* [HGM] This routine is called each time an assignment is made to
2502 * gameInfo.variant during a game, to make sure the board sizes
2503 * are set to match the new variant. If that means adding or deleting
2504 * holdings, we shift the playing board accordingly
2505 * This kludge is needed because in ICS observe mode, we get boards
2506 * of an ongoing game without knowing the variant, and learn about the
2507 * latter only later. This can be because of the move list we requested,
2508 * in which case the game history is refilled from the beginning anyway,
2509 * but also when receiving holdings of a crazyhouse game. In the latter
2510 * case we want to add those holdings to the already received position.
2514 if (appData.debugMode) {
2515 fprintf(debugFP, "Switch board from %s to %s\n",
2516 VariantName(gameInfo.variant), VariantName(newVariant));
2517 setbuf(debugFP, NULL);
2519 shuffleOpenings = 0; /* [HGM] shuffle */
2520 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2524 newWidth = 9; newHeight = 9;
2525 gameInfo.holdingsSize = 7;
2526 case VariantBughouse:
2527 case VariantCrazyhouse:
2528 newHoldingsWidth = 2; break;
2532 newHoldingsWidth = 2;
2533 gameInfo.holdingsSize = 8;
2536 case VariantCapablanca:
2537 case VariantCapaRandom:
2540 newHoldingsWidth = gameInfo.holdingsSize = 0;
2543 if(newWidth != gameInfo.boardWidth ||
2544 newHeight != gameInfo.boardHeight ||
2545 newHoldingsWidth != gameInfo.holdingsWidth ) {
2547 /* shift position to new playing area, if needed */
2548 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2549 for(i=0; i<BOARD_HEIGHT; i++)
2550 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2551 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2553 for(i=0; i<newHeight; i++) {
2554 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2555 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2557 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2558 for(i=0; i<BOARD_HEIGHT; i++)
2559 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2560 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2563 board[HOLDINGS_SET] = 0;
2564 gameInfo.boardWidth = newWidth;
2565 gameInfo.boardHeight = newHeight;
2566 gameInfo.holdingsWidth = newHoldingsWidth;
2567 gameInfo.variant = newVariant;
2568 InitDrawingSizes(-2, 0);
2569 } else gameInfo.variant = newVariant;
2570 CopyBoard(oldBoard, board); // remember correctly formatted board
2571 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2572 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2575 static int loggedOn = FALSE;
2577 /*-- Game start info cache: --*/
2579 char gs_kind[MSG_SIZ];
2580 static char player1Name[128] = "";
2581 static char player2Name[128] = "";
2582 static char cont_seq[] = "\n\\ ";
2583 static int player1Rating = -1;
2584 static int player2Rating = -1;
2585 /*----------------------------*/
2587 ColorClass curColor = ColorNormal;
2588 int suppressKibitz = 0;
2591 Boolean soughtPending = FALSE;
2592 Boolean seekGraphUp;
2593 #define MAX_SEEK_ADS 200
2595 char *seekAdList[MAX_SEEK_ADS];
2596 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2597 float tcList[MAX_SEEK_ADS];
2598 char colorList[MAX_SEEK_ADS];
2599 int nrOfSeekAds = 0;
2600 int minRating = 1010, maxRating = 2800;
2601 int hMargin = 10, vMargin = 20, h, w;
2602 extern int squareSize, lineGap;
2607 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2608 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2609 if(r < minRating+100 && r >=0 ) r = minRating+100;
2610 if(r > maxRating) r = maxRating;
2611 if(tc < 1.f) tc = 1.f;
2612 if(tc > 95.f) tc = 95.f;
2613 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2614 y = ((double)r - minRating)/(maxRating - minRating)
2615 * (h-vMargin-squareSize/8-1) + vMargin;
2616 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2617 if(strstr(seekAdList[i], " u ")) color = 1;
2618 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2619 !strstr(seekAdList[i], "bullet") &&
2620 !strstr(seekAdList[i], "blitz") &&
2621 !strstr(seekAdList[i], "standard") ) color = 2;
2622 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2623 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2627 PlotSingleSeekAd (int i)
2633 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2635 char buf[MSG_SIZ], *ext = "";
2636 VariantClass v = StringToVariant(type);
2637 if(strstr(type, "wild")) {
2638 ext = type + 4; // append wild number
2639 if(v == VariantFischeRandom) type = "chess960"; else
2640 if(v == VariantLoadable) type = "setup"; else
2641 type = VariantName(v);
2643 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2644 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2645 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2646 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2647 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2648 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2649 seekNrList[nrOfSeekAds] = nr;
2650 zList[nrOfSeekAds] = 0;
2651 seekAdList[nrOfSeekAds++] = StrSave(buf);
2652 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2657 EraseSeekDot (int i)
2659 int x = xList[i], y = yList[i], d=squareSize/4, k;
2660 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2661 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2662 // now replot every dot that overlapped
2663 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2664 int xx = xList[k], yy = yList[k];
2665 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2666 DrawSeekDot(xx, yy, colorList[k]);
2671 RemoveSeekAd (int nr)
2674 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2676 if(seekAdList[i]) free(seekAdList[i]);
2677 seekAdList[i] = seekAdList[--nrOfSeekAds];
2678 seekNrList[i] = seekNrList[nrOfSeekAds];
2679 ratingList[i] = ratingList[nrOfSeekAds];
2680 colorList[i] = colorList[nrOfSeekAds];
2681 tcList[i] = tcList[nrOfSeekAds];
2682 xList[i] = xList[nrOfSeekAds];
2683 yList[i] = yList[nrOfSeekAds];
2684 zList[i] = zList[nrOfSeekAds];
2685 seekAdList[nrOfSeekAds] = NULL;
2691 MatchSoughtLine (char *line)
2693 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2694 int nr, base, inc, u=0; char dummy;
2696 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2697 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2699 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2700 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2701 // match: compact and save the line
2702 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2712 if(!seekGraphUp) return FALSE;
2713 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2714 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2716 DrawSeekBackground(0, 0, w, h);
2717 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2718 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2719 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2720 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2722 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2725 snprintf(buf, MSG_SIZ, "%d", i);
2726 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2729 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2730 for(i=1; i<100; i+=(i<10?1:5)) {
2731 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2732 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2733 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2735 snprintf(buf, MSG_SIZ, "%d", i);
2736 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2739 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2744 SeekGraphClick (ClickType click, int x, int y, int moving)
2746 static int lastDown = 0, displayed = 0, lastSecond;
2747 if(y < 0) return FALSE;
2748 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2749 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2750 if(!seekGraphUp) return FALSE;
2751 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2752 DrawPosition(TRUE, NULL);
2755 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2756 if(click == Release || moving) return FALSE;
2758 soughtPending = TRUE;
2759 SendToICS(ics_prefix);
2760 SendToICS("sought\n"); // should this be "sought all"?
2761 } else { // issue challenge based on clicked ad
2762 int dist = 10000; int i, closest = 0, second = 0;
2763 for(i=0; i<nrOfSeekAds; i++) {
2764 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2765 if(d < dist) { dist = d; closest = i; }
2766 second += (d - zList[i] < 120); // count in-range ads
2767 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2771 second = (second > 1);
2772 if(displayed != closest || second != lastSecond) {
2773 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2774 lastSecond = second; displayed = closest;
2776 if(click == Press) {
2777 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2780 } // on press 'hit', only show info
2781 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2782 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2783 SendToICS(ics_prefix);
2785 return TRUE; // let incoming board of started game pop down the graph
2786 } else if(click == Release) { // release 'miss' is ignored
2787 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2788 if(moving == 2) { // right up-click
2789 nrOfSeekAds = 0; // refresh graph
2790 soughtPending = TRUE;
2791 SendToICS(ics_prefix);
2792 SendToICS("sought\n"); // should this be "sought all"?
2795 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2796 // press miss or release hit 'pop down' seek graph
2797 seekGraphUp = FALSE;
2798 DrawPosition(TRUE, NULL);
2804 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2806 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2807 #define STARTED_NONE 0
2808 #define STARTED_MOVES 1
2809 #define STARTED_BOARD 2
2810 #define STARTED_OBSERVE 3
2811 #define STARTED_HOLDINGS 4
2812 #define STARTED_CHATTER 5
2813 #define STARTED_COMMENT 6
2814 #define STARTED_MOVES_NOHIDE 7
2816 static int started = STARTED_NONE;
2817 static char parse[20000];
2818 static int parse_pos = 0;
2819 static char buf[BUF_SIZE + 1];
2820 static int firstTime = TRUE, intfSet = FALSE;
2821 static ColorClass prevColor = ColorNormal;
2822 static int savingComment = FALSE;
2823 static int cmatch = 0; // continuation sequence match
2830 int backup; /* [DM] For zippy color lines */
2832 char talker[MSG_SIZ]; // [HGM] chat
2833 int channel, collective=0;
2835 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2837 if (appData.debugMode) {
2839 fprintf(debugFP, "<ICS: ");
2840 show_bytes(debugFP, data, count);
2841 fprintf(debugFP, "\n");
2845 if (appData.debugMode) { int f = forwardMostMove;
2846 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2847 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2848 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2851 /* If last read ended with a partial line that we couldn't parse,
2852 prepend it to the new read and try again. */
2853 if (leftover_len > 0) {
2854 for (i=0; i<leftover_len; i++)
2855 buf[i] = buf[leftover_start + i];
2858 /* copy new characters into the buffer */
2859 bp = buf + leftover_len;
2860 buf_len=leftover_len;
2861 for (i=0; i<count; i++)
2864 if (data[i] == '\r')
2867 // join lines split by ICS?
2868 if (!appData.noJoin)
2871 Joining just consists of finding matches against the
2872 continuation sequence, and discarding that sequence
2873 if found instead of copying it. So, until a match
2874 fails, there's nothing to do since it might be the
2875 complete sequence, and thus, something we don't want
2878 if (data[i] == cont_seq[cmatch])
2881 if (cmatch == strlen(cont_seq))
2883 cmatch = 0; // complete match. just reset the counter
2886 it's possible for the ICS to not include the space
2887 at the end of the last word, making our [correct]
2888 join operation fuse two separate words. the server
2889 does this when the space occurs at the width setting.
2891 if (!buf_len || buf[buf_len-1] != ' ')
2902 match failed, so we have to copy what matched before
2903 falling through and copying this character. In reality,
2904 this will only ever be just the newline character, but
2905 it doesn't hurt to be precise.
2907 strncpy(bp, cont_seq, cmatch);
2919 buf[buf_len] = NULLCHAR;
2920 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2925 while (i < buf_len) {
2926 /* Deal with part of the TELNET option negotiation
2927 protocol. We refuse to do anything beyond the
2928 defaults, except that we allow the WILL ECHO option,
2929 which ICS uses to turn off password echoing when we are
2930 directly connected to it. We reject this option
2931 if localLineEditing mode is on (always on in xboard)
2932 and we are talking to port 23, which might be a real
2933 telnet server that will try to keep WILL ECHO on permanently.
2935 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2936 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2937 unsigned char option;
2939 switch ((unsigned char) buf[++i]) {
2941 if (appData.debugMode)
2942 fprintf(debugFP, "\n<WILL ");
2943 switch (option = (unsigned char) buf[++i]) {
2945 if (appData.debugMode)
2946 fprintf(debugFP, "ECHO ");
2947 /* Reply only if this is a change, according
2948 to the protocol rules. */
2949 if (remoteEchoOption) break;
2950 if (appData.localLineEditing &&
2951 atoi(appData.icsPort) == TN_PORT) {
2952 TelnetRequest(TN_DONT, TN_ECHO);
2955 TelnetRequest(TN_DO, TN_ECHO);
2956 remoteEchoOption = TRUE;
2960 if (appData.debugMode)
2961 fprintf(debugFP, "%d ", option);
2962 /* Whatever this is, we don't want it. */
2963 TelnetRequest(TN_DONT, option);
2968 if (appData.debugMode)
2969 fprintf(debugFP, "\n<WONT ");
2970 switch (option = (unsigned char) buf[++i]) {
2972 if (appData.debugMode)
2973 fprintf(debugFP, "ECHO ");
2974 /* Reply only if this is a change, according
2975 to the protocol rules. */
2976 if (!remoteEchoOption) break;
2978 TelnetRequest(TN_DONT, TN_ECHO);
2979 remoteEchoOption = FALSE;
2982 if (appData.debugMode)
2983 fprintf(debugFP, "%d ", (unsigned char) option);
2984 /* Whatever this is, it must already be turned
2985 off, because we never agree to turn on
2986 anything non-default, so according to the
2987 protocol rules, we don't reply. */
2992 if (appData.debugMode)
2993 fprintf(debugFP, "\n<DO ");
2994 switch (option = (unsigned char) buf[++i]) {
2996 /* Whatever this is, we refuse to do it. */
2997 if (appData.debugMode)
2998 fprintf(debugFP, "%d ", option);
2999 TelnetRequest(TN_WONT, option);
3004 if (appData.debugMode)
3005 fprintf(debugFP, "\n<DONT ");
3006 switch (option = (unsigned char) buf[++i]) {
3008 if (appData.debugMode)
3009 fprintf(debugFP, "%d ", option);
3010 /* Whatever this is, we are already not doing
3011 it, because we never agree to do anything
3012 non-default, so according to the protocol
3013 rules, we don't reply. */
3018 if (appData.debugMode)
3019 fprintf(debugFP, "\n<IAC ");
3020 /* Doubled IAC; pass it through */
3024 if (appData.debugMode)
3025 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3026 /* Drop all other telnet commands on the floor */
3029 if (oldi > next_out)
3030 SendToPlayer(&buf[next_out], oldi - next_out);
3036 /* OK, this at least will *usually* work */
3037 if (!loggedOn && looking_at(buf, &i, "ics%")) {
3041 if (loggedOn && !intfSet) {
3042 if (ics_type == ICS_ICC) {
3043 snprintf(str, MSG_SIZ,
3044 "/set-quietly interface %s\n/set-quietly style 12\n",
3046 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3047 strcat(str, "/set-2 51 1\n/set seek 1\n");
3048 } else if (ics_type == ICS_CHESSNET) {
3049 snprintf(str, MSG_SIZ, "/style 12\n");
3051 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3052 strcat(str, programVersion);
3053 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3054 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3055 strcat(str, "$iset seekremove 1\n$set seek 1\n");
3057 strcat(str, "$iset nohighlight 1\n");
3059 strcat(str, "$iset lock 1\n$style 12\n");
3062 NotifyFrontendLogin();
3066 if (started == STARTED_COMMENT) {
3067 /* Accumulate characters in comment */
3068 parse[parse_pos++] = buf[i];
3069 if (buf[i] == '\n') {
3070 parse[parse_pos] = NULLCHAR;
3071 if(chattingPartner>=0) {
3073 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3074 OutputChatMessage(chattingPartner, mess);
3075 if(collective) { // broadcasted talk also goes to private chatbox of talker
3077 talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3078 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3079 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3080 OutputChatMessage(p, mess);
3084 chattingPartner = -1;
3085 next_out = i+1; // [HGM] suppress printing in ICS window
3087 if(!suppressKibitz) // [HGM] kibitz
3088 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3089 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3090 int nrDigit = 0, nrAlph = 0, j;
3091 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3092 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3093 parse[parse_pos] = NULLCHAR;
3094 // try to be smart: if it does not look like search info, it should go to
3095 // ICS interaction window after all, not to engine-output window.
3096 for(j=0; j<parse_pos; j++) { // count letters and digits
3097 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3098 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3099 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3101 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3102 int depth=0; float score;
3103 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3104 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3105 pvInfoList[forwardMostMove-1].depth = depth;
3106 pvInfoList[forwardMostMove-1].score = 100*score;
3108 OutputKibitz(suppressKibitz, parse);
3111 if(gameMode == IcsObserving) // restore original ICS messages
3112 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3113 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3115 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3116 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3117 SendToPlayer(tmp, strlen(tmp));
3119 next_out = i+1; // [HGM] suppress printing in ICS window
3121 started = STARTED_NONE;
3123 /* Don't match patterns against characters in comment */
3128 if (started == STARTED_CHATTER) {
3129 if (buf[i] != '\n') {
3130 /* Don't match patterns against characters in chatter */
3134 started = STARTED_NONE;
3135 if(suppressKibitz) next_out = i+1;
3138 /* Kludge to deal with rcmd protocol */
3139 if (firstTime && looking_at(buf, &i, "\001*")) {
3140 DisplayFatalError(&buf[1], 0, 1);
3146 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3149 if (appData.debugMode)
3150 fprintf(debugFP, "ics_type %d\n", ics_type);
3153 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3154 ics_type = ICS_FICS;
3156 if (appData.debugMode)
3157 fprintf(debugFP, "ics_type %d\n", ics_type);
3160 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3161 ics_type = ICS_CHESSNET;
3163 if (appData.debugMode)
3164 fprintf(debugFP, "ics_type %d\n", ics_type);
3169 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3170 looking_at(buf, &i, "Logging you in as \"*\"") ||
3171 looking_at(buf, &i, "will be \"*\""))) {
3172 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3176 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3178 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3179 DisplayIcsInteractionTitle(buf);
3180 have_set_title = TRUE;
3183 /* skip finger notes */
3184 if (started == STARTED_NONE &&
3185 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3186 (buf[i] == '1' && buf[i+1] == '0')) &&
3187 buf[i+2] == ':' && buf[i+3] == ' ') {
3188 started = STARTED_CHATTER;
3194 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3195 if(appData.seekGraph) {
3196 if(soughtPending && MatchSoughtLine(buf+i)) {
3197 i = strstr(buf+i, "rated") - buf;
3198 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3199 next_out = leftover_start = i;
3200 started = STARTED_CHATTER;
3201 suppressKibitz = TRUE;
3204 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3205 && looking_at(buf, &i, "* ads displayed")) {
3206 soughtPending = FALSE;
3211 if(appData.autoRefresh) {
3212 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3213 int s = (ics_type == ICS_ICC); // ICC format differs
3215 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3216 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3217 looking_at(buf, &i, "*% "); // eat prompt
3218 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3219 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3220 next_out = i; // suppress
3223 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3224 char *p = star_match[0];
3226 if(seekGraphUp) RemoveSeekAd(atoi(p));
3227 while(*p && *p++ != ' '); // next
3229 looking_at(buf, &i, "*% "); // eat prompt
3230 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3237 /* skip formula vars */
3238 if (started == STARTED_NONE &&
3239 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3240 started = STARTED_CHATTER;
3245 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3246 if (appData.autoKibitz && started == STARTED_NONE &&
3247 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3248 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3249 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3250 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3251 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3252 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3253 suppressKibitz = TRUE;
3254 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3256 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3257 && (gameMode == IcsPlayingWhite)) ||
3258 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3259 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3260 started = STARTED_CHATTER; // own kibitz we simply discard
3262 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3263 parse_pos = 0; parse[0] = NULLCHAR;
3264 savingComment = TRUE;
3265 suppressKibitz = gameMode != IcsObserving ? 2 :
3266 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3270 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3271 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3272 && atoi(star_match[0])) {
3273 // suppress the acknowledgements of our own autoKibitz
3275 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3276 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3277 SendToPlayer(star_match[0], strlen(star_match[0]));
3278 if(looking_at(buf, &i, "*% ")) // eat prompt
3279 suppressKibitz = FALSE;
3283 } // [HGM] kibitz: end of patch
3285 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3287 // [HGM] chat: intercept tells by users for which we have an open chat window
3289 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3290 looking_at(buf, &i, "* whispers:") ||
3291 looking_at(buf, &i, "* kibitzes:") ||
3292 looking_at(buf, &i, "* shouts:") ||
3293 looking_at(buf, &i, "* c-shouts:") ||
3294 looking_at(buf, &i, "--> * ") ||
3295 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3296 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3297 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3298 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3300 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3301 chattingPartner = -1; collective = 0;
3303 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3304 for(p=0; p<MAX_CHAT; p++) {
3305 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3306 talker[0] = '['; strcat(talker, "] ");
3307 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3309 chattingPartner = p; break;
3312 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3313 for(p=0; p<MAX_CHAT; p++) {
3314 if(!strcmp("kibitzes", chatPartner[p])) {
3315 talker[0] = '['; strcat(talker, "] ");
3317 chattingPartner = p; break;
3320 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3321 for(p=0; p<MAX_CHAT; p++) {
3322 if(!strcmp("whispers", chatPartner[p])) {
3323 talker[0] = '['; strcat(talker, "] ");
3325 chattingPartner = p; break;
3328 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3329 if(buf[i-8] == '-' && buf[i-3] == 't')
3330 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3331 if(!strcmp("c-shouts", chatPartner[p])) {
3332 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3334 chattingPartner = p; break;
3337 if(chattingPartner < 0)
3338 for(p=0; p<MAX_CHAT; p++) {
3339 if(!strcmp("shouts", chatPartner[p])) {
3340 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3341 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3342 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3344 chattingPartner = p; break;
3348 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3349 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3350 talker[0] = 0; Colorize(ColorTell, FALSE);
3351 chattingPartner = p; break;
3353 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3354 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3355 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3356 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3357 started = STARTED_COMMENT;
3358 parse_pos = 0; parse[0] = NULLCHAR;
3359 savingComment = 3 + chattingPartner; // counts as TRUE
3360 suppressKibitz = TRUE;
3363 } // [HGM] chat: end of patch
3366 if (appData.zippyTalk || appData.zippyPlay) {
3367 /* [DM] Backup address for color zippy lines */
3369 if (loggedOn == TRUE)
3370 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3371 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3373 } // [DM] 'else { ' deleted
3375 /* Regular tells and says */
3376 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3377 looking_at(buf, &i, "* (your partner) tells you: ") ||
3378 looking_at(buf, &i, "* says: ") ||
3379 /* Don't color "message" or "messages" output */
3380 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3381 looking_at(buf, &i, "*. * at *:*: ") ||
3382 looking_at(buf, &i, "--* (*:*): ") ||
3383 /* Message notifications (same color as tells) */
3384 looking_at(buf, &i, "* has left a message ") ||
3385 looking_at(buf, &i, "* just sent you a message:\n") ||
3386 /* Whispers and kibitzes */
3387 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3388 looking_at(buf, &i, "* kibitzes: ") ||
3390 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3392 if (tkind == 1 && strchr(star_match[0], ':')) {
3393 /* Avoid "tells you:" spoofs in channels */
3396 if (star_match[0][0] == NULLCHAR ||
3397 strchr(star_match[0], ' ') ||
3398 (tkind == 3 && strchr(star_match[1], ' '))) {
3399 /* Reject bogus matches */
3402 if (appData.colorize) {
3403 if (oldi > next_out) {
3404 SendToPlayer(&buf[next_out], oldi - next_out);
3409 Colorize(ColorTell, FALSE);
3410 curColor = ColorTell;
3413 Colorize(ColorKibitz, FALSE);
3414 curColor = ColorKibitz;
3417 p = strrchr(star_match[1], '(');
3424 Colorize(ColorChannel1, FALSE);
3425 curColor = ColorChannel1;
3427 Colorize(ColorChannel, FALSE);
3428 curColor = ColorChannel;
3432 curColor = ColorNormal;
3436 if (started == STARTED_NONE && appData.autoComment &&
3437 (gameMode == IcsObserving ||
3438 gameMode == IcsPlayingWhite ||
3439 gameMode == IcsPlayingBlack)) {
3440 parse_pos = i - oldi;
3441 memcpy(parse, &buf[oldi], parse_pos);
3442 parse[parse_pos] = NULLCHAR;
3443 started = STARTED_COMMENT;
3444 savingComment = TRUE;
3446 started = STARTED_CHATTER;
3447 savingComment = FALSE;
3454 if (looking_at(buf, &i, "* s-shouts: ") ||
3455 looking_at(buf, &i, "* c-shouts: ")) {
3456 if (appData.colorize) {
3457 if (oldi > next_out) {
3458 SendToPlayer(&buf[next_out], oldi - next_out);
3461 Colorize(ColorSShout, FALSE);
3462 curColor = ColorSShout;
3465 started = STARTED_CHATTER;
3469 if (looking_at(buf, &i, "--->")) {
3474 if (looking_at(buf, &i, "* shouts: ") ||
3475 looking_at(buf, &i, "--> ")) {
3476 if (appData.colorize) {
3477 if (oldi > next_out) {
3478 SendToPlayer(&buf[next_out], oldi - next_out);
3481 Colorize(ColorShout, FALSE);
3482 curColor = ColorShout;
3485 started = STARTED_CHATTER;
3489 if (looking_at( buf, &i, "Challenge:")) {
3490 if (appData.colorize) {
3491 if (oldi > next_out) {
3492 SendToPlayer(&buf[next_out], oldi - next_out);
3495 Colorize(ColorChallenge, FALSE);
3496 curColor = ColorChallenge;
3502 if (looking_at(buf, &i, "* offers you") ||
3503 looking_at(buf, &i, "* offers to be") ||
3504 looking_at(buf, &i, "* would like to") ||
3505 looking_at(buf, &i, "* requests to") ||
3506 looking_at(buf, &i, "Your opponent offers") ||
3507 looking_at(buf, &i, "Your opponent requests")) {
3509 if (appData.colorize) {
3510 if (oldi > next_out) {
3511 SendToPlayer(&buf[next_out], oldi - next_out);
3514 Colorize(ColorRequest, FALSE);
3515 curColor = ColorRequest;
3520 if (looking_at(buf, &i, "* (*) seeking")) {
3521 if (appData.colorize) {
3522 if (oldi > next_out) {
3523 SendToPlayer(&buf[next_out], oldi - next_out);
3526 Colorize(ColorSeek, FALSE);
3527 curColor = ColorSeek;
3532 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3534 if (looking_at(buf, &i, "\\ ")) {
3535 if (prevColor != ColorNormal) {
3536 if (oldi > next_out) {
3537 SendToPlayer(&buf[next_out], oldi - next_out);
3540 Colorize(prevColor, TRUE);
3541 curColor = prevColor;
3543 if (savingComment) {
3544 parse_pos = i - oldi;
3545 memcpy(parse, &buf[oldi], parse_pos);
3546 parse[parse_pos] = NULLCHAR;
3547 started = STARTED_COMMENT;
3548 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3549 chattingPartner = savingComment - 3; // kludge to remember the box
3551 started = STARTED_CHATTER;
3556 if (looking_at(buf, &i, "Black Strength :") ||
3557 looking_at(buf, &i, "<<< style 10 board >>>") ||
3558 looking_at(buf, &i, "<10>") ||
3559 looking_at(buf, &i, "#@#")) {
3560 /* Wrong board style */
3562 SendToICS(ics_prefix);
3563 SendToICS("set style 12\n");
3564 SendToICS(ics_prefix);
3565 SendToICS("refresh\n");
3569 if (looking_at(buf, &i, "login:")) {
3570 if (!have_sent_ICS_logon) {
3572 have_sent_ICS_logon = 1;
3573 else // no init script was found
3574 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3575 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3576 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3581 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3582 (looking_at(buf, &i, "\n<12> ") ||
3583 looking_at(buf, &i, "<12> "))) {
3585 if (oldi > next_out) {
3586 SendToPlayer(&buf[next_out], oldi - next_out);
3589 started = STARTED_BOARD;
3594 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3595 looking_at(buf, &i, "<b1> ")) {
3596 if (oldi > next_out) {
3597 SendToPlayer(&buf[next_out], oldi - next_out);
3600 started = STARTED_HOLDINGS;
3605 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3607 /* Header for a move list -- first line */
3609 switch (ics_getting_history) {
3613 case BeginningOfGame:
3614 /* User typed "moves" or "oldmoves" while we
3615 were idle. Pretend we asked for these
3616 moves and soak them up so user can step
3617 through them and/or save them.
3620 gameMode = IcsObserving;
3623 ics_getting_history = H_GOT_UNREQ_HEADER;
3625 case EditGame: /*?*/
3626 case EditPosition: /*?*/
3627 /* Should above feature work in these modes too? */
3628 /* For now it doesn't */
3629 ics_getting_history = H_GOT_UNWANTED_HEADER;
3632 ics_getting_history = H_GOT_UNWANTED_HEADER;
3637 /* Is this the right one? */
3638 if (gameInfo.white && gameInfo.black &&
3639 strcmp(gameInfo.white, star_match[0]) == 0 &&
3640 strcmp(gameInfo.black, star_match[2]) == 0) {
3642 ics_getting_history = H_GOT_REQ_HEADER;
3645 case H_GOT_REQ_HEADER:
3646 case H_GOT_UNREQ_HEADER:
3647 case H_GOT_UNWANTED_HEADER:
3648 case H_GETTING_MOVES:
3649 /* Should not happen */
3650 DisplayError(_("Error gathering move list: two headers"), 0);
3651 ics_getting_history = H_FALSE;
3655 /* Save player ratings into gameInfo if needed */
3656 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3657 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3658 (gameInfo.whiteRating == -1 ||
3659 gameInfo.blackRating == -1)) {
3661 gameInfo.whiteRating = string_to_rating(star_match[1]);
3662 gameInfo.blackRating = string_to_rating(star_match[3]);
3663 if (appData.debugMode)
3664 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3665 gameInfo.whiteRating, gameInfo.blackRating);
3670 if (looking_at(buf, &i,
3671 "* * match, initial time: * minute*, increment: * second")) {
3672 /* Header for a move list -- second line */
3673 /* Initial board will follow if this is a wild game */
3674 if (gameInfo.event != NULL) free(gameInfo.event);
3675 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3676 gameInfo.event = StrSave(str);
3677 /* [HGM] we switched variant. Translate boards if needed. */
3678 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3682 if (looking_at(buf, &i, "Move ")) {
3683 /* Beginning of a move list */
3684 switch (ics_getting_history) {
3686 /* Normally should not happen */
3687 /* Maybe user hit reset while we were parsing */
3690 /* Happens if we are ignoring a move list that is not
3691 * the one we just requested. Common if the user
3692 * tries to observe two games without turning off
3695 case H_GETTING_MOVES:
3696 /* Should not happen */
3697 DisplayError(_("Error gathering move list: nested"), 0);
3698 ics_getting_history = H_FALSE;
3700 case H_GOT_REQ_HEADER:
3701 ics_getting_history = H_GETTING_MOVES;
3702 started = STARTED_MOVES;
3704 if (oldi > next_out) {
3705 SendToPlayer(&buf[next_out], oldi - next_out);
3708 case H_GOT_UNREQ_HEADER:
3709 ics_getting_history = H_GETTING_MOVES;
3710 started = STARTED_MOVES_NOHIDE;
3713 case H_GOT_UNWANTED_HEADER:
3714 ics_getting_history = H_FALSE;
3720 if (looking_at(buf, &i, "% ") ||
3721 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3722 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3723 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3724 soughtPending = FALSE;
3728 if(suppressKibitz) next_out = i;
3729 savingComment = FALSE;
3733 case STARTED_MOVES_NOHIDE:
3734 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3735 parse[parse_pos + i - oldi] = NULLCHAR;
3736 ParseGameHistory(parse);
3738 if (appData.zippyPlay && first.initDone) {
3739 FeedMovesToProgram(&first, forwardMostMove);
3740 if (gameMode == IcsPlayingWhite) {
3741 if (WhiteOnMove(forwardMostMove)) {
3742 if (first.sendTime) {
3743 if (first.useColors) {
3744 SendToProgram("black\n", &first);
3746 SendTimeRemaining(&first, TRUE);
3748 if (first.useColors) {
3749 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3751 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3752 first.maybeThinking = TRUE;
3754 if (first.usePlayother) {
3755 if (first.sendTime) {
3756 SendTimeRemaining(&first, TRUE);
3758 SendToProgram("playother\n", &first);
3764 } else if (gameMode == IcsPlayingBlack) {
3765 if (!WhiteOnMove(forwardMostMove)) {
3766 if (first.sendTime) {
3767 if (first.useColors) {
3768 SendToProgram("white\n", &first);
3770 SendTimeRemaining(&first, FALSE);
3772 if (first.useColors) {
3773 SendToProgram("black\n", &first);
3775 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3776 first.maybeThinking = TRUE;
3778 if (first.usePlayother) {
3779 if (first.sendTime) {
3780 SendTimeRemaining(&first, FALSE);
3782 SendToProgram("playother\n", &first);
3791 if (gameMode == IcsObserving && ics_gamenum == -1) {
3792 /* Moves came from oldmoves or moves command
3793 while we weren't doing anything else.
3795 currentMove = forwardMostMove;
3796 ClearHighlights();/*!!could figure this out*/
3797 flipView = appData.flipView;
3798 DrawPosition(TRUE, boards[currentMove]);
3799 DisplayBothClocks();
3800 snprintf(str, MSG_SIZ, "%s %s %s",
3801 gameInfo.white, _("vs."), gameInfo.black);
3805 /* Moves were history of an active game */
3806 if (gameInfo.resultDetails != NULL) {
3807 free(gameInfo.resultDetails);
3808 gameInfo.resultDetails = NULL;
3811 HistorySet(parseList, backwardMostMove,
3812 forwardMostMove, currentMove-1);
3813 DisplayMove(currentMove - 1);
3814 if (started == STARTED_MOVES) next_out = i;
3815 started = STARTED_NONE;
3816 ics_getting_history = H_FALSE;
3819 case STARTED_OBSERVE:
3820 started = STARTED_NONE;
3821 SendToICS(ics_prefix);
3822 SendToICS("refresh\n");
3828 if(bookHit) { // [HGM] book: simulate book reply
3829 static char bookMove[MSG_SIZ]; // a bit generous?
3831 programStats.nodes = programStats.depth = programStats.time =
3832 programStats.score = programStats.got_only_move = 0;
3833 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3835 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3836 strcat(bookMove, bookHit);
3837 HandleMachineMove(bookMove, &first);
3842 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3843 started == STARTED_HOLDINGS ||
3844 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3845 /* Accumulate characters in move list or board */
3846 parse[parse_pos++] = buf[i];
3849 /* Start of game messages. Mostly we detect start of game
3850 when the first board image arrives. On some versions
3851 of the ICS, though, we need to do a "refresh" after starting
3852 to observe in order to get the current board right away. */
3853 if (looking_at(buf, &i, "Adding game * to observation list")) {
3854 started = STARTED_OBSERVE;
3858 /* Handle auto-observe */
3859 if (appData.autoObserve &&
3860 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3861 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3863 /* Choose the player that was highlighted, if any. */
3864 if (star_match[0][0] == '\033' ||
3865 star_match[1][0] != '\033') {
3866 player = star_match[0];
3868 player = star_match[2];
3870 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3871 ics_prefix, StripHighlightAndTitle(player));
3874 /* Save ratings from notify string */
3875 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3876 player1Rating = string_to_rating(star_match[1]);
3877 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3878 player2Rating = string_to_rating(star_match[3]);
3880 if (appData.debugMode)
3882 "Ratings from 'Game notification:' %s %d, %s %d\n",
3883 player1Name, player1Rating,
3884 player2Name, player2Rating);
3889 /* Deal with automatic examine mode after a game,
3890 and with IcsObserving -> IcsExamining transition */
3891 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3892 looking_at(buf, &i, "has made you an examiner of game *")) {
3894 int gamenum = atoi(star_match[0]);
3895 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3896 gamenum == ics_gamenum) {
3897 /* We were already playing or observing this game;
3898 no need to refetch history */
3899 gameMode = IcsExamining;
3901 pauseExamForwardMostMove = forwardMostMove;
3902 } else if (currentMove < forwardMostMove) {
3903 ForwardInner(forwardMostMove);
3906 /* I don't think this case really can happen */
3907 SendToICS(ics_prefix);
3908 SendToICS("refresh\n");
3913 /* Error messages */
3914 // if (ics_user_moved) {
3915 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3916 if (looking_at(buf, &i, "Illegal move") ||
3917 looking_at(buf, &i, "Not a legal move") ||
3918 looking_at(buf, &i, "Your king is in check") ||
3919 looking_at(buf, &i, "It isn't your turn") ||
3920 looking_at(buf, &i, "It is not your move")) {
3922 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3923 currentMove = forwardMostMove-1;
3924 DisplayMove(currentMove - 1); /* before DMError */
3925 DrawPosition(FALSE, boards[currentMove]);
3926 SwitchClocks(forwardMostMove-1); // [HGM] race
3927 DisplayBothClocks();
3929 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3935 if (looking_at(buf, &i, "still have time") ||
3936 looking_at(buf, &i, "not out of time") ||
3937 looking_at(buf, &i, "either player is out of time") ||
3938 looking_at(buf, &i, "has timeseal; checking")) {
3939 /* We must have called his flag a little too soon */
3940 whiteFlag = blackFlag = FALSE;
3944 if (looking_at(buf, &i, "added * seconds to") ||
3945 looking_at(buf, &i, "seconds were added to")) {
3946 /* Update the clocks */
3947 SendToICS(ics_prefix);
3948 SendToICS("refresh\n");
3952 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3953 ics_clock_paused = TRUE;
3958 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3959 ics_clock_paused = FALSE;
3964 /* Grab player ratings from the Creating: message.
3965 Note we have to check for the special case when
3966 the ICS inserts things like [white] or [black]. */
3967 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3968 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3970 0 player 1 name (not necessarily white)
3972 2 empty, white, or black (IGNORED)
3973 3 player 2 name (not necessarily black)
3976 The names/ratings are sorted out when the game
3977 actually starts (below).
3979 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3980 player1Rating = string_to_rating(star_match[1]);
3981 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3982 player2Rating = string_to_rating(star_match[4]);
3984 if (appData.debugMode)
3986 "Ratings from 'Creating:' %s %d, %s %d\n",
3987 player1Name, player1Rating,
3988 player2Name, player2Rating);
3993 /* Improved generic start/end-of-game messages */
3994 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3995 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3996 /* If tkind == 0: */
3997 /* star_match[0] is the game number */
3998 /* [1] is the white player's name */
3999 /* [2] is the black player's name */
4000 /* For end-of-game: */
4001 /* [3] is the reason for the game end */
4002 /* [4] is a PGN end game-token, preceded by " " */
4003 /* For start-of-game: */
4004 /* [3] begins with "Creating" or "Continuing" */
4005 /* [4] is " *" or empty (don't care). */
4006 int gamenum = atoi(star_match[0]);
4007 char *whitename, *blackname, *why, *endtoken;
4008 ChessMove endtype = EndOfFile;
4011 whitename = star_match[1];
4012 blackname = star_match[2];
4013 why = star_match[3];
4014 endtoken = star_match[4];
4016 whitename = star_match[1];
4017 blackname = star_match[3];
4018 why = star_match[5];
4019 endtoken = star_match[6];
4022 /* Game start messages */
4023 if (strncmp(why, "Creating ", 9) == 0 ||
4024 strncmp(why, "Continuing ", 11) == 0) {
4025 gs_gamenum = gamenum;
4026 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4027 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4028 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4030 if (appData.zippyPlay) {
4031 ZippyGameStart(whitename, blackname);
4034 partnerBoardValid = FALSE; // [HGM] bughouse
4038 /* Game end messages */
4039 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4040 ics_gamenum != gamenum) {
4043 while (endtoken[0] == ' ') endtoken++;
4044 switch (endtoken[0]) {
4047 endtype = GameUnfinished;
4050 endtype = BlackWins;
4053 if (endtoken[1] == '/')
4054 endtype = GameIsDrawn;
4056 endtype = WhiteWins;
4059 GameEnds(endtype, why, GE_ICS);
4061 if (appData.zippyPlay && first.initDone) {
4062 ZippyGameEnd(endtype, why);
4063 if (first.pr == NoProc) {
4064 /* Start the next process early so that we'll
4065 be ready for the next challenge */
4066 StartChessProgram(&first);
4068 /* Send "new" early, in case this command takes
4069 a long time to finish, so that we'll be ready
4070 for the next challenge. */
4071 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4075 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4079 if (looking_at(buf, &i, "Removing game * from observation") ||
4080 looking_at(buf, &i, "no longer observing game *") ||
4081 looking_at(buf, &i, "Game * (*) has no examiners")) {
4082 if (gameMode == IcsObserving &&
4083 atoi(star_match[0]) == ics_gamenum)
4085 /* icsEngineAnalyze */
4086 if (appData.icsEngineAnalyze) {
4093 ics_user_moved = FALSE;
4098 if (looking_at(buf, &i, "no longer examining game *")) {
4099 if (gameMode == IcsExamining &&
4100 atoi(star_match[0]) == ics_gamenum)
4104 ics_user_moved = FALSE;
4109 /* Advance leftover_start past any newlines we find,
4110 so only partial lines can get reparsed */
4111 if (looking_at(buf, &i, "\n")) {
4112 prevColor = curColor;
4113 if (curColor != ColorNormal) {
4114 if (oldi > next_out) {
4115 SendToPlayer(&buf[next_out], oldi - next_out);
4118 Colorize(ColorNormal, FALSE);
4119 curColor = ColorNormal;
4121 if (started == STARTED_BOARD) {
4122 started = STARTED_NONE;
4123 parse[parse_pos] = NULLCHAR;
4124 ParseBoard12(parse);
4127 /* Send premove here */
4128 if (appData.premove) {
4130 if (currentMove == 0 &&
4131 gameMode == IcsPlayingWhite &&
4132 appData.premoveWhite) {
4133 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4134 if (appData.debugMode)
4135 fprintf(debugFP, "Sending premove:\n");
4137 } else if (currentMove == 1 &&
4138 gameMode == IcsPlayingBlack &&
4139 appData.premoveBlack) {
4140 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4141 if (appData.debugMode)
4142 fprintf(debugFP, "Sending premove:\n");
4144 } else if (gotPremove) {
4146 ClearPremoveHighlights();
4147 if (appData.debugMode)
4148 fprintf(debugFP, "Sending premove:\n");
4149 UserMoveEvent(premoveFromX, premoveFromY,
4150 premoveToX, premoveToY,
4155 /* Usually suppress following prompt */
4156 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4157 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4158 if (looking_at(buf, &i, "*% ")) {
4159 savingComment = FALSE;
4164 } else if (started == STARTED_HOLDINGS) {
4166 char new_piece[MSG_SIZ];
4167 started = STARTED_NONE;
4168 parse[parse_pos] = NULLCHAR;
4169 if (appData.debugMode)
4170 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4171 parse, currentMove);
4172 if (sscanf(parse, " game %d", &gamenum) == 1) {
4173 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4174 if (gameInfo.variant == VariantNormal) {
4175 /* [HGM] We seem to switch variant during a game!
4176 * Presumably no holdings were displayed, so we have
4177 * to move the position two files to the right to
4178 * create room for them!
4180 VariantClass newVariant;
4181 switch(gameInfo.boardWidth) { // base guess on board width
4182 case 9: newVariant = VariantShogi; break;
4183 case 10: newVariant = VariantGreat; break;
4184 default: newVariant = VariantCrazyhouse; break;
4186 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4187 /* Get a move list just to see the header, which
4188 will tell us whether this is really bug or zh */
4189 if (ics_getting_history == H_FALSE) {
4190 ics_getting_history = H_REQUESTED;
4191 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4195 new_piece[0] = NULLCHAR;
4196 sscanf(parse, "game %d white [%s black [%s <- %s",
4197 &gamenum, white_holding, black_holding,
4199 white_holding[strlen(white_holding)-1] = NULLCHAR;
4200 black_holding[strlen(black_holding)-1] = NULLCHAR;
4201 /* [HGM] copy holdings to board holdings area */
4202 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4203 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4204 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4206 if (appData.zippyPlay && first.initDone) {
4207 ZippyHoldings(white_holding, black_holding,
4211 if (tinyLayout || smallLayout) {
4212 char wh[16], bh[16];
4213 PackHolding(wh, white_holding);
4214 PackHolding(bh, black_holding);
4215 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4216 gameInfo.white, gameInfo.black);
4218 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4219 gameInfo.white, white_holding, _("vs."),
4220 gameInfo.black, black_holding);
4222 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4223 DrawPosition(FALSE, boards[currentMove]);
4225 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4226 sscanf(parse, "game %d white [%s black [%s <- %s",
4227 &gamenum, white_holding, black_holding,
4229 white_holding[strlen(white_holding)-1] = NULLCHAR;
4230 black_holding[strlen(black_holding)-1] = NULLCHAR;
4231 /* [HGM] copy holdings to partner-board holdings area */
4232 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4233 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4234 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4235 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4236 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4239 /* Suppress following prompt */
4240 if (looking_at(buf, &i, "*% ")) {
4241 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4242 savingComment = FALSE;
4250 i++; /* skip unparsed character and loop back */
4253 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4254 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4255 // SendToPlayer(&buf[next_out], i - next_out);
4256 started != STARTED_HOLDINGS && leftover_start > next_out) {
4257 SendToPlayer(&buf[next_out], leftover_start - next_out);
4261 leftover_len = buf_len - leftover_start;
4262 /* if buffer ends with something we couldn't parse,
4263 reparse it after appending the next read */
4265 } else if (count == 0) {
4266 RemoveInputSource(isr);
4267 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4269 DisplayFatalError(_("Error reading from ICS"), error, 1);
4274 /* Board style 12 looks like this:
4276 <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
4278 * The "<12> " is stripped before it gets to this routine. The two
4279 * trailing 0's (flip state and clock ticking) are later addition, and
4280 * some chess servers may not have them, or may have only the first.
4281 * Additional trailing fields may be added in the future.
4284 #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"
4286 #define RELATION_OBSERVING_PLAYED 0
4287 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4288 #define RELATION_PLAYING_MYMOVE 1
4289 #define RELATION_PLAYING_NOTMYMOVE -1
4290 #define RELATION_EXAMINING 2
4291 #define RELATION_ISOLATED_BOARD -3
4292 #define RELATION_STARTING_POSITION -4 /* FICS only */
4295 ParseBoard12 (char *string)
4299 char *bookHit = NULL; // [HGM] book
4301 GameMode newGameMode;
4302 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4303 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4304 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4305 char to_play, board_chars[200];
4306 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4307 char black[32], white[32];
4309 int prevMove = currentMove;
4312 int fromX, fromY, toX, toY;
4314 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4315 Boolean weird = FALSE, reqFlag = FALSE;
4317 fromX = fromY = toX = toY = -1;
4321 if (appData.debugMode)
4322 fprintf(debugFP, "Parsing board: %s\n", string);
4324 move_str[0] = NULLCHAR;
4325 elapsed_time[0] = NULLCHAR;
4326 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4328 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4329 if(string[i] == ' ') { ranks++; files = 0; }
4331 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4334 for(j = 0; j <i; j++) board_chars[j] = string[j];
4335 board_chars[i] = '\0';
4338 n = sscanf(string, PATTERN, &to_play, &double_push,
4339 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4340 &gamenum, white, black, &relation, &basetime, &increment,
4341 &white_stren, &black_stren, &white_time, &black_time,
4342 &moveNum, str, elapsed_time, move_str, &ics_flip,
4346 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4347 DisplayError(str, 0);
4351 /* Convert the move number to internal form */
4352 moveNum = (moveNum - 1) * 2;
4353 if (to_play == 'B') moveNum++;
4354 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4355 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4361 case RELATION_OBSERVING_PLAYED:
4362 case RELATION_OBSERVING_STATIC:
4363 if (gamenum == -1) {
4364 /* Old ICC buglet */
4365 relation = RELATION_OBSERVING_STATIC;
4367 newGameMode = IcsObserving;
4369 case RELATION_PLAYING_MYMOVE:
4370 case RELATION_PLAYING_NOTMYMOVE:
4372 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4373 IcsPlayingWhite : IcsPlayingBlack;
4374 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4376 case RELATION_EXAMINING:
4377 newGameMode = IcsExamining;
4379 case RELATION_ISOLATED_BOARD:
4381 /* Just display this board. If user was doing something else,
4382 we will forget about it until the next board comes. */
4383 newGameMode = IcsIdle;
4385 case RELATION_STARTING_POSITION:
4386 newGameMode = gameMode;
4390 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4391 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4392 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4393 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4394 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4395 static int lastBgGame = -1;
4397 for (k = 0; k < ranks; k++) {
4398 for (j = 0; j < files; j++)
4399 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4400 if(gameInfo.holdingsWidth > 1) {
4401 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4402 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4405 CopyBoard(partnerBoard, board);
4406 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4407 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4408 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4409 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4410 if(toSqr = strchr(str, '-')) {
4411 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4412 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4413 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4414 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4415 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4416 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4418 DisplayWhiteClock(white_time*fac, to_play == 'W');
4419 DisplayBlackClock(black_time*fac, to_play != 'W');
4420 activePartner = to_play;
4421 if(gamenum != lastBgGame) {
4423 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4426 lastBgGame = gamenum;
4427 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4428 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4429 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4430 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4431 if(!twoBoards) DisplayMessage(partnerStatus, "");
4432 partnerBoardValid = TRUE;
4436 if(appData.dualBoard && appData.bgObserve) {
4437 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4438 SendToICS(ics_prefix), SendToICS("pobserve\n");
4439 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4441 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4446 /* Modify behavior for initial board display on move listing
4449 switch (ics_getting_history) {
4453 case H_GOT_REQ_HEADER:
4454 case H_GOT_UNREQ_HEADER:
4455 /* This is the initial position of the current game */
4456 gamenum = ics_gamenum;
4457 moveNum = 0; /* old ICS bug workaround */
4458 if (to_play == 'B') {
4459 startedFromSetupPosition = TRUE;
4460 blackPlaysFirst = TRUE;
4462 if (forwardMostMove == 0) forwardMostMove = 1;
4463 if (backwardMostMove == 0) backwardMostMove = 1;
4464 if (currentMove == 0) currentMove = 1;
4466 newGameMode = gameMode;
4467 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4469 case H_GOT_UNWANTED_HEADER:
4470 /* This is an initial board that we don't want */
4472 case H_GETTING_MOVES:
4473 /* Should not happen */
4474 DisplayError(_("Error gathering move list: extra board"), 0);
4475 ics_getting_history = H_FALSE;
4479 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4480 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4481 weird && (int)gameInfo.variant < (int)VariantShogi) {
4482 /* [HGM] We seem to have switched variant unexpectedly
4483 * Try to guess new variant from board size
4485 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4486 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4487 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4488 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4489 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4490 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4491 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4492 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4493 /* Get a move list just to see the header, which
4494 will tell us whether this is really bug or zh */
4495 if (ics_getting_history == H_FALSE) {
4496 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4497 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4502 /* Take action if this is the first board of a new game, or of a
4503 different game than is currently being displayed. */
4504 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4505 relation == RELATION_ISOLATED_BOARD) {
4507 /* Forget the old game and get the history (if any) of the new one */
4508 if (gameMode != BeginningOfGame) {
4512 if (appData.autoRaiseBoard) BoardToTop();
4514 if (gamenum == -1) {
4515 newGameMode = IcsIdle;
4516 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4517 appData.getMoveList && !reqFlag) {
4518 /* Need to get game history */
4519 ics_getting_history = H_REQUESTED;
4520 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4524 /* Initially flip the board to have black on the bottom if playing
4525 black or if the ICS flip flag is set, but let the user change
4526 it with the Flip View button. */
4527 flipView = appData.autoFlipView ?
4528 (newGameMode == IcsPlayingBlack) || ics_flip :
4531 /* Done with values from previous mode; copy in new ones */
4532 gameMode = newGameMode;
4534 ics_gamenum = gamenum;
4535 if (gamenum == gs_gamenum) {
4536 int klen = strlen(gs_kind);
4537 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4538 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4539 gameInfo.event = StrSave(str);
4541 gameInfo.event = StrSave("ICS game");
4543 gameInfo.site = StrSave(appData.icsHost);
4544 gameInfo.date = PGNDate();
4545 gameInfo.round = StrSave("-");
4546 gameInfo.white = StrSave(white);
4547 gameInfo.black = StrSave(black);
4548 timeControl = basetime * 60 * 1000;
4550 timeIncrement = increment * 1000;
4551 movesPerSession = 0;
4552 gameInfo.timeControl = TimeControlTagValue();
4553 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4554 if (appData.debugMode) {
4555 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4556 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4557 setbuf(debugFP, NULL);
4560 gameInfo.outOfBook = NULL;
4562 /* Do we have the ratings? */
4563 if (strcmp(player1Name, white) == 0 &&
4564 strcmp(player2Name, black) == 0) {
4565 if (appData.debugMode)
4566 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4567 player1Rating, player2Rating);
4568 gameInfo.whiteRating = player1Rating;
4569 gameInfo.blackRating = player2Rating;
4570 } else if (strcmp(player2Name, white) == 0 &&
4571 strcmp(player1Name, black) == 0) {
4572 if (appData.debugMode)
4573 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4574 player2Rating, player1Rating);
4575 gameInfo.whiteRating = player2Rating;
4576 gameInfo.blackRating = player1Rating;
4578 player1Name[0] = player2Name[0] = NULLCHAR;
4580 /* Silence shouts if requested */
4581 if (appData.quietPlay &&
4582 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4583 SendToICS(ics_prefix);
4584 SendToICS("set shout 0\n");
4588 /* Deal with midgame name changes */
4590 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4591 if (gameInfo.white) free(gameInfo.white);
4592 gameInfo.white = StrSave(white);
4594 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4595 if (gameInfo.black) free(gameInfo.black);
4596 gameInfo.black = StrSave(black);
4600 /* Throw away game result if anything actually changes in examine mode */
4601 if (gameMode == IcsExamining && !newGame) {
4602 gameInfo.result = GameUnfinished;
4603 if (gameInfo.resultDetails != NULL) {
4604 free(gameInfo.resultDetails);
4605 gameInfo.resultDetails = NULL;
4609 /* In pausing && IcsExamining mode, we ignore boards coming
4610 in if they are in a different variation than we are. */
4611 if (pauseExamInvalid) return;
4612 if (pausing && gameMode == IcsExamining) {
4613 if (moveNum <= pauseExamForwardMostMove) {
4614 pauseExamInvalid = TRUE;
4615 forwardMostMove = pauseExamForwardMostMove;
4620 if (appData.debugMode) {
4621 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4623 /* Parse the board */
4624 for (k = 0; k < ranks; k++) {
4625 for (j = 0; j < files; j++)
4626 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4627 if(gameInfo.holdingsWidth > 1) {
4628 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4629 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4632 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4633 board[5][BOARD_RGHT+1] = WhiteAngel;
4634 board[6][BOARD_RGHT+1] = WhiteMarshall;
4635 board[1][0] = BlackMarshall;
4636 board[2][0] = BlackAngel;
4637 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4639 CopyBoard(boards[moveNum], board);
4640 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4642 startedFromSetupPosition =
4643 !CompareBoards(board, initialPosition);
4644 if(startedFromSetupPosition)
4645 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4648 /* [HGM] Set castling rights. Take the outermost Rooks,
4649 to make it also work for FRC opening positions. Note that board12
4650 is really defective for later FRC positions, as it has no way to
4651 indicate which Rook can castle if they are on the same side of King.
4652 For the initial position we grant rights to the outermost Rooks,
4653 and remember thos rights, and we then copy them on positions
4654 later in an FRC game. This means WB might not recognize castlings with
4655 Rooks that have moved back to their original position as illegal,
4656 but in ICS mode that is not its job anyway.
4658 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4659 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4661 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4662 if(board[0][i] == WhiteRook) j = i;
4663 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4664 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4665 if(board[0][i] == WhiteRook) j = i;
4666 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4667 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4668 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4669 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4670 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4671 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4672 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4674 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4675 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4676 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4677 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4678 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4679 if(board[BOARD_HEIGHT-1][k] == bKing)
4680 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4681 if(gameInfo.variant == VariantTwoKings) {
4682 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4683 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4684 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4687 r = boards[moveNum][CASTLING][0] = initialRights[0];
4688 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4689 r = boards[moveNum][CASTLING][1] = initialRights[1];
4690 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4691 r = boards[moveNum][CASTLING][3] = initialRights[3];
4692 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4693 r = boards[moveNum][CASTLING][4] = initialRights[4];
4694 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4695 /* wildcastle kludge: always assume King has rights */
4696 r = boards[moveNum][CASTLING][2] = initialRights[2];
4697 r = boards[moveNum][CASTLING][5] = initialRights[5];
4699 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4700 boards[moveNum][EP_STATUS] = EP_NONE;
4701 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4702 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4703 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4706 if (ics_getting_history == H_GOT_REQ_HEADER ||
4707 ics_getting_history == H_GOT_UNREQ_HEADER) {
4708 /* This was an initial position from a move list, not
4709 the current position */
4713 /* Update currentMove and known move number limits */
4714 newMove = newGame || moveNum > forwardMostMove;
4717 forwardMostMove = backwardMostMove = currentMove = moveNum;
4718 if (gameMode == IcsExamining && moveNum == 0) {
4719 /* Workaround for ICS limitation: we are not told the wild
4720 type when starting to examine a game. But if we ask for
4721 the move list, the move list header will tell us */
4722 ics_getting_history = H_REQUESTED;
4723 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4726 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4727 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4729 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4730 /* [HGM] applied this also to an engine that is silently watching */
4731 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4732 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4733 gameInfo.variant == currentlyInitializedVariant) {
4734 takeback = forwardMostMove - moveNum;
4735 for (i = 0; i < takeback; i++) {
4736 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4737 SendToProgram("undo\n", &first);
4742 forwardMostMove = moveNum;
4743 if (!pausing || currentMove > forwardMostMove)
4744 currentMove = forwardMostMove;
4746 /* New part of history that is not contiguous with old part */
4747 if (pausing && gameMode == IcsExamining) {
4748 pauseExamInvalid = TRUE;
4749 forwardMostMove = pauseExamForwardMostMove;
4752 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4754 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4755 // [HGM] when we will receive the move list we now request, it will be
4756 // fed to the engine from the first move on. So if the engine is not
4757 // in the initial position now, bring it there.
4758 InitChessProgram(&first, 0);
4761 ics_getting_history = H_REQUESTED;
4762 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4765 forwardMostMove = backwardMostMove = currentMove = moveNum;
4768 /* Update the clocks */
4769 if (strchr(elapsed_time, '.')) {
4771 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4772 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4774 /* Time is in seconds */
4775 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4776 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4781 if (appData.zippyPlay && newGame &&
4782 gameMode != IcsObserving && gameMode != IcsIdle &&
4783 gameMode != IcsExamining)
4784 ZippyFirstBoard(moveNum, basetime, increment);
4787 /* Put the move on the move list, first converting
4788 to canonical algebraic form. */
4790 if (appData.debugMode) {
4791 int f = forwardMostMove;
4792 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4793 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4794 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4795 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4796 fprintf(debugFP, "moveNum = %d\n", moveNum);
4797 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4798 setbuf(debugFP, NULL);
4800 if (moveNum <= backwardMostMove) {
4801 /* We don't know what the board looked like before
4803 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4804 strcat(parseList[moveNum - 1], " ");
4805 strcat(parseList[moveNum - 1], elapsed_time);
4806 moveList[moveNum - 1][0] = NULLCHAR;
4807 } else if (strcmp(move_str, "none") == 0) {
4808 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4809 /* Again, we don't know what the board looked like;
4810 this is really the start of the game. */
4811 parseList[moveNum - 1][0] = NULLCHAR;
4812 moveList[moveNum - 1][0] = NULLCHAR;
4813 backwardMostMove = moveNum;
4814 startedFromSetupPosition = TRUE;
4815 fromX = fromY = toX = toY = -1;
4817 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4818 // So we parse the long-algebraic move string in stead of the SAN move
4819 int valid; char buf[MSG_SIZ], *prom;
4821 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4822 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4823 // str looks something like "Q/a1-a2"; kill the slash
4825 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4826 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4827 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4828 strcat(buf, prom); // long move lacks promo specification!
4829 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4830 if(appData.debugMode)
4831 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4832 safeStrCpy(move_str, buf, MSG_SIZ);
4834 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4835 &fromX, &fromY, &toX, &toY, &promoChar)
4836 || ParseOneMove(buf, moveNum - 1, &moveType,
4837 &fromX, &fromY, &toX, &toY, &promoChar);
4838 // end of long SAN patch
4840 (void) CoordsToAlgebraic(boards[moveNum - 1],
4841 PosFlags(moveNum - 1),
4842 fromY, fromX, toY, toX, promoChar,
4843 parseList[moveNum-1]);
4844 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4850 if(!IS_SHOGI(gameInfo.variant))
4851 strcat(parseList[moveNum - 1], "+");
4854 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4855 strcat(parseList[moveNum - 1], "#");
4858 strcat(parseList[moveNum - 1], " ");
4859 strcat(parseList[moveNum - 1], elapsed_time);
4860 /* currentMoveString is set as a side-effect of ParseOneMove */
4861 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4862 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4863 strcat(moveList[moveNum - 1], "\n");
4865 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4866 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4867 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4868 ChessSquare old, new = boards[moveNum][k][j];
4869 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4870 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4871 if(old == new) continue;
4872 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4873 else if(new == WhiteWazir || new == BlackWazir) {
4874 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4875 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4876 else boards[moveNum][k][j] = old; // preserve type of Gold
4877 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4878 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4881 /* Move from ICS was illegal!? Punt. */
4882 if (appData.debugMode) {
4883 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4884 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4886 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4887 strcat(parseList[moveNum - 1], " ");
4888 strcat(parseList[moveNum - 1], elapsed_time);
4889 moveList[moveNum - 1][0] = NULLCHAR;
4890 fromX = fromY = toX = toY = -1;
4893 if (appData.debugMode) {
4894 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4895 setbuf(debugFP, NULL);
4899 /* Send move to chess program (BEFORE animating it). */
4900 if (appData.zippyPlay && !newGame && newMove &&
4901 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4903 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4904 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4905 if (moveList[moveNum - 1][0] == NULLCHAR) {
4906 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4908 DisplayError(str, 0);
4910 if (first.sendTime) {
4911 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4913 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4914 if (firstMove && !bookHit) {
4916 if (first.useColors) {
4917 SendToProgram(gameMode == IcsPlayingWhite ?
4919 "black\ngo\n", &first);
4921 SendToProgram("go\n", &first);
4923 first.maybeThinking = TRUE;
4926 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4927 if (moveList[moveNum - 1][0] == NULLCHAR) {
4928 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4929 DisplayError(str, 0);
4931 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4932 SendMoveToProgram(moveNum - 1, &first);
4939 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4940 /* If move comes from a remote source, animate it. If it
4941 isn't remote, it will have already been animated. */
4942 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4943 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4945 if (!pausing && appData.highlightLastMove) {
4946 SetHighlights(fromX, fromY, toX, toY);
4950 /* Start the clocks */
4951 whiteFlag = blackFlag = FALSE;
4952 appData.clockMode = !(basetime == 0 && increment == 0);
4954 ics_clock_paused = TRUE;
4956 } else if (ticking == 1) {
4957 ics_clock_paused = FALSE;
4959 if (gameMode == IcsIdle ||
4960 relation == RELATION_OBSERVING_STATIC ||
4961 relation == RELATION_EXAMINING ||
4963 DisplayBothClocks();
4967 /* Display opponents and material strengths */
4968 if (gameInfo.variant != VariantBughouse &&
4969 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4970 if (tinyLayout || smallLayout) {
4971 if(gameInfo.variant == VariantNormal)
4972 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4973 gameInfo.white, white_stren, gameInfo.black, black_stren,
4974 basetime, increment);
4976 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4977 gameInfo.white, white_stren, gameInfo.black, black_stren,
4978 basetime, increment, (int) gameInfo.variant);
4980 if(gameInfo.variant == VariantNormal)
4981 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4982 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4983 basetime, increment);
4985 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4986 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4987 basetime, increment, VariantName(gameInfo.variant));
4990 if (appData.debugMode) {
4991 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4996 /* Display the board */
4997 if (!pausing && !appData.noGUI) {
4999 if (appData.premove)
5001 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5002 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5003 ClearPremoveHighlights();
5005 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5006 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5007 DrawPosition(j, boards[currentMove]);
5009 DisplayMove(moveNum - 1);
5010 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5011 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5012 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
5013 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5017 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5019 if(bookHit) { // [HGM] book: simulate book reply
5020 static char bookMove[MSG_SIZ]; // a bit generous?
5022 programStats.nodes = programStats.depth = programStats.time =
5023 programStats.score = programStats.got_only_move = 0;
5024 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5026 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5027 strcat(bookMove, bookHit);
5028 HandleMachineMove(bookMove, &first);
5037 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5038 ics_getting_history = H_REQUESTED;
5039 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5045 SendToBoth (char *msg)
5046 { // to make it easy to keep two engines in step in dual analysis
5047 SendToProgram(msg, &first);
5048 if(second.analyzing) SendToProgram(msg, &second);
5052 AnalysisPeriodicEvent (int force)
5054 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5055 && !force) || !appData.periodicUpdates)
5058 /* Send . command to Crafty to collect stats */
5061 /* Don't send another until we get a response (this makes
5062 us stop sending to old Crafty's which don't understand
5063 the "." command (sending illegal cmds resets node count & time,
5064 which looks bad)) */
5065 programStats.ok_to_send = 0;
5069 ics_update_width (int new_width)
5071 ics_printf("set width %d\n", new_width);
5075 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5079 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5080 if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5081 sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5082 SendToProgram(buf, cps);
5085 // null move in variant where engine does not understand it (for analysis purposes)
5086 SendBoard(cps, moveNum + 1); // send position after move in stead.
5089 if (cps->useUsermove) {
5090 SendToProgram("usermove ", cps);
5094 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5095 int len = space - parseList[moveNum];
5096 memcpy(buf, parseList[moveNum], len);
5098 buf[len] = NULLCHAR;
5100 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5102 SendToProgram(buf, cps);
5104 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5105 AlphaRank(moveList[moveNum], 4);
5106 SendToProgram(moveList[moveNum], cps);
5107 AlphaRank(moveList[moveNum], 4); // and back
5109 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5110 * the engine. It would be nice to have a better way to identify castle
5112 if(appData.fischerCastling && cps->useOOCastle) {
5113 int fromX = moveList[moveNum][0] - AAA;
5114 int fromY = moveList[moveNum][1] - ONE;
5115 int toX = moveList[moveNum][2] - AAA;
5116 int toY = moveList[moveNum][3] - ONE;
5117 if((boards[moveNum][fromY][fromX] == WhiteKing
5118 && boards[moveNum][toY][toX] == WhiteRook)
5119 || (boards[moveNum][fromY][fromX] == BlackKing
5120 && boards[moveNum][toY][toX] == BlackRook)) {
5121 if(toX > fromX) SendToProgram("O-O\n", cps);
5122 else SendToProgram("O-O-O\n", cps);
5124 else SendToProgram(moveList[moveNum], cps);
5126 if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5127 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5128 moveList[moveNum][5], moveList[moveNum][6] - '0',
5129 moveList[moveNum][5], moveList[moveNum][6] - '0',
5130 moveList[moveNum][2], moveList[moveNum][3] - '0');
5131 SendToProgram(buf, cps);
5133 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5134 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5135 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5136 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5137 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5139 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5140 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5141 SendToProgram(buf, cps);
5143 else SendToProgram(moveList[moveNum], cps);
5144 /* End of additions by Tord */
5147 /* [HGM] setting up the opening has brought engine in force mode! */
5148 /* Send 'go' if we are in a mode where machine should play. */
5149 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5150 (gameMode == TwoMachinesPlay ||
5152 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5154 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5155 SendToProgram("go\n", cps);
5156 if (appData.debugMode) {
5157 fprintf(debugFP, "(extra)\n");
5160 setboardSpoiledMachineBlack = 0;
5164 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5166 char user_move[MSG_SIZ];
5169 if(gameInfo.variant == VariantSChess && promoChar) {
5170 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5171 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5172 } else suffix[0] = NULLCHAR;
5176 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5177 (int)moveType, fromX, fromY, toX, toY);
5178 DisplayError(user_move + strlen("say "), 0);
5180 case WhiteKingSideCastle:
5181 case BlackKingSideCastle:
5182 case WhiteQueenSideCastleWild:
5183 case BlackQueenSideCastleWild:
5185 case WhiteHSideCastleFR:
5186 case BlackHSideCastleFR:
5188 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5190 case WhiteQueenSideCastle:
5191 case BlackQueenSideCastle:
5192 case WhiteKingSideCastleWild:
5193 case BlackKingSideCastleWild:
5195 case WhiteASideCastleFR:
5196 case BlackASideCastleFR:
5198 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5200 case WhiteNonPromotion:
5201 case BlackNonPromotion:
5202 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5204 case WhitePromotion:
5205 case BlackPromotion:
5206 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5207 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5208 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5209 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5210 PieceToChar(WhiteFerz));
5211 else if(gameInfo.variant == VariantGreat)
5212 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5213 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5214 PieceToChar(WhiteMan));
5216 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5217 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5223 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5224 ToUpper(PieceToChar((ChessSquare) fromX)),
5225 AAA + toX, ONE + toY);
5227 case IllegalMove: /* could be a variant we don't quite understand */
5228 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5230 case WhiteCapturesEnPassant:
5231 case BlackCapturesEnPassant:
5232 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5233 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5236 SendToICS(user_move);
5237 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5238 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5243 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5244 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5245 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5246 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5247 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5250 if(gameMode != IcsExamining) { // is this ever not the case?
5251 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5253 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5254 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5255 } else { // on FICS we must first go to general examine mode
5256 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5258 if(gameInfo.variant != VariantNormal) {
5259 // try figure out wild number, as xboard names are not always valid on ICS
5260 for(i=1; i<=36; i++) {
5261 snprintf(buf, MSG_SIZ, "wild/%d", i);
5262 if(StringToVariant(buf) == gameInfo.variant) break;
5264 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5265 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5266 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5267 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5268 SendToICS(ics_prefix);
5270 if(startedFromSetupPosition || backwardMostMove != 0) {
5271 fen = PositionToFEN(backwardMostMove, NULL, 1);
5272 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5273 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5275 } else { // FICS: everything has to set by separate bsetup commands
5276 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5277 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5279 if(!WhiteOnMove(backwardMostMove)) {
5280 SendToICS("bsetup tomove black\n");
5282 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5283 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5285 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5286 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5288 i = boards[backwardMostMove][EP_STATUS];
5289 if(i >= 0) { // set e.p.
5290 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5296 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5297 SendToICS("bsetup done\n"); // switch to normal examining.
5299 for(i = backwardMostMove; i<last; i++) {
5301 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5302 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5303 int len = strlen(moveList[i]);
5304 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5305 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5309 SendToICS(ics_prefix);
5310 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5313 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5316 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5318 if (rf == DROP_RANK) {
5319 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5320 sprintf(move, "%c@%c%c\n",
5321 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5323 if (promoChar == 'x' || promoChar == NULLCHAR) {
5324 sprintf(move, "%c%c%c%c\n",
5325 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5326 if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5328 sprintf(move, "%c%c%c%c%c\n",
5329 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5335 ProcessICSInitScript (FILE *f)
5339 while (fgets(buf, MSG_SIZ, f)) {
5340 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5347 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5349 static ClickType lastClickType;
5352 Partner (ChessSquare *p)
5353 { // change piece into promotion partner if one shogi-promotes to the other
5354 int stride = gameInfo.variant == VariantChu ? 22 : 11;
5355 ChessSquare partner;
5356 partner = (*p/stride & 1 ? *p - stride : *p + stride);
5357 if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5365 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5366 static int toggleFlag;
5367 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5368 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5369 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5370 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5371 if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5372 if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5374 if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5375 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5376 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5377 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5378 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5379 if(!step) step = -1;
5380 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5381 !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5382 appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5383 (promoSweep == WhiteLion || promoSweep == BlackLion)));
5385 int victim = boards[currentMove][toY][toX];
5386 boards[currentMove][toY][toX] = promoSweep;
5387 DrawPosition(FALSE, boards[currentMove]);
5388 boards[currentMove][toY][toX] = victim;
5390 ChangeDragPiece(promoSweep);
5394 PromoScroll (int x, int y)
5398 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5399 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5400 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5401 if(!step) return FALSE;
5402 lastX = x; lastY = y;
5403 if((promoSweep < BlackPawn) == flipView) step = -step;
5404 if(step > 0) selectFlag = 1;
5405 if(!selectFlag) Sweep(step);
5410 NextPiece (int step)
5412 ChessSquare piece = boards[currentMove][toY][toX];
5415 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5416 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5417 if(!step) step = -1;
5418 } while(PieceToChar(pieceSweep) == '.');
5419 boards[currentMove][toY][toX] = pieceSweep;
5420 DrawPosition(FALSE, boards[currentMove]);
5421 boards[currentMove][toY][toX] = piece;
5423 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5425 AlphaRank (char *move, int n)
5427 // char *p = move, c; int x, y;
5429 if (appData.debugMode) {
5430 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5434 move[2]>='0' && move[2]<='9' &&
5435 move[3]>='a' && move[3]<='x' ) {
5437 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5438 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5440 if(move[0]>='0' && move[0]<='9' &&
5441 move[1]>='a' && move[1]<='x' &&
5442 move[2]>='0' && move[2]<='9' &&
5443 move[3]>='a' && move[3]<='x' ) {
5444 /* input move, Shogi -> normal */
5445 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5446 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5447 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5448 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5451 move[3]>='0' && move[3]<='9' &&
5452 move[2]>='a' && move[2]<='x' ) {
5454 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5455 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5458 move[0]>='a' && move[0]<='x' &&
5459 move[3]>='0' && move[3]<='9' &&
5460 move[2]>='a' && move[2]<='x' ) {
5461 /* output move, normal -> Shogi */
5462 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5463 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5464 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5465 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5466 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5468 if (appData.debugMode) {
5469 fprintf(debugFP, " out = '%s'\n", move);
5473 char yy_textstr[8000];
5475 /* Parser for moves from gnuchess, ICS, or user typein box */
5477 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5479 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5481 switch (*moveType) {
5482 case WhitePromotion:
5483 case BlackPromotion:
5484 case WhiteNonPromotion:
5485 case BlackNonPromotion:
5488 case WhiteCapturesEnPassant:
5489 case BlackCapturesEnPassant:
5490 case WhiteKingSideCastle:
5491 case WhiteQueenSideCastle:
5492 case BlackKingSideCastle:
5493 case BlackQueenSideCastle:
5494 case WhiteKingSideCastleWild:
5495 case WhiteQueenSideCastleWild:
5496 case BlackKingSideCastleWild:
5497 case BlackQueenSideCastleWild:
5498 /* Code added by Tord: */
5499 case WhiteHSideCastleFR:
5500 case WhiteASideCastleFR:
5501 case BlackHSideCastleFR:
5502 case BlackASideCastleFR:
5503 /* End of code added by Tord */
5504 case IllegalMove: /* bug or odd chess variant */
5505 *fromX = currentMoveString[0] - AAA;
5506 *fromY = currentMoveString[1] - ONE;
5507 *toX = currentMoveString[2] - AAA;
5508 *toY = currentMoveString[3] - ONE;
5509 *promoChar = currentMoveString[4];
5510 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5511 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5512 if (appData.debugMode) {
5513 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5515 *fromX = *fromY = *toX = *toY = 0;
5518 if (appData.testLegality) {
5519 return (*moveType != IllegalMove);
5521 return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5522 // [HGM] lion: if this is a double move we are less critical
5523 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5528 *fromX = *moveType == WhiteDrop ?
5529 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5530 (int) CharToPiece(ToLower(currentMoveString[0]));
5532 *toX = currentMoveString[2] - AAA;
5533 *toY = currentMoveString[3] - ONE;
5534 *promoChar = NULLCHAR;
5538 case ImpossibleMove:
5548 if (appData.debugMode) {
5549 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5552 *fromX = *fromY = *toX = *toY = 0;
5553 *promoChar = NULLCHAR;
5558 Boolean pushed = FALSE;
5559 char *lastParseAttempt;
5562 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5563 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5564 int fromX, fromY, toX, toY; char promoChar;
5569 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5570 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5571 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5574 endPV = forwardMostMove;
5576 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5577 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5578 lastParseAttempt = pv;
5579 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5580 if(!valid && nr == 0 &&
5581 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5582 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5583 // Hande case where played move is different from leading PV move
5584 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5585 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5586 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5587 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5588 endPV += 2; // if position different, keep this
5589 moveList[endPV-1][0] = fromX + AAA;
5590 moveList[endPV-1][1] = fromY + ONE;
5591 moveList[endPV-1][2] = toX + AAA;
5592 moveList[endPV-1][3] = toY + ONE;
5593 parseList[endPV-1][0] = NULLCHAR;
5594 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5597 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5598 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5599 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5600 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5601 valid++; // allow comments in PV
5605 if(endPV+1 > framePtr) break; // no space, truncate
5608 CopyBoard(boards[endPV], boards[endPV-1]);
5609 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5610 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5611 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5612 CoordsToAlgebraic(boards[endPV - 1],
5613 PosFlags(endPV - 1),
5614 fromY, fromX, toY, toX, promoChar,
5615 parseList[endPV - 1]);
5617 if(atEnd == 2) return; // used hidden, for PV conversion
5618 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5619 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5620 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5621 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5622 DrawPosition(TRUE, boards[currentMove]);
5626 MultiPV (ChessProgramState *cps)
5627 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5629 for(i=0; i<cps->nrOptions; i++)
5630 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5635 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5638 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5640 int startPV, multi, lineStart, origIndex = index;
5641 char *p, buf2[MSG_SIZ];
5642 ChessProgramState *cps = (pane ? &second : &first);
5644 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5645 lastX = x; lastY = y;
5646 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5647 lineStart = startPV = index;
5648 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5649 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5651 do{ while(buf[index] && buf[index] != '\n') index++;
5652 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5654 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5655 int n = cps->option[multi].value;
5656 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5657 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5658 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5659 cps->option[multi].value = n;
5662 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5663 ExcludeClick(origIndex - lineStart);
5665 } else if(!strncmp(buf+lineStart, "dep\t", 4)) { // column headers clicked
5666 Collapse(origIndex - lineStart);
5669 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5670 *start = startPV; *end = index-1;
5671 extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5678 static char buf[10*MSG_SIZ];
5679 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5681 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5682 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5683 for(i = forwardMostMove; i<endPV; i++){
5684 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5685 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5688 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5689 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5690 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5696 LoadPV (int x, int y)
5697 { // called on right mouse click to load PV
5698 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5699 lastX = x; lastY = y;
5700 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5708 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5709 if(endPV < 0) return;
5710 if(appData.autoCopyPV) CopyFENToClipboard();
5712 if(extendGame && currentMove > forwardMostMove) {
5713 Boolean saveAnimate = appData.animate;
5715 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5716 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5717 } else storedGames--; // abandon shelved tail of original game
5720 forwardMostMove = currentMove;
5721 currentMove = oldFMM;
5722 appData.animate = FALSE;
5723 ToNrEvent(forwardMostMove);
5724 appData.animate = saveAnimate;
5726 currentMove = forwardMostMove;
5727 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5728 ClearPremoveHighlights();
5729 DrawPosition(TRUE, boards[currentMove]);
5733 MovePV (int x, int y, int h)
5734 { // step through PV based on mouse coordinates (called on mouse move)
5735 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5737 // we must somehow check if right button is still down (might be released off board!)
5738 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5739 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5740 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5742 lastX = x; lastY = y;
5744 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5745 if(endPV < 0) return;
5746 if(y < margin) step = 1; else
5747 if(y > h - margin) step = -1;
5748 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5749 currentMove += step;
5750 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5751 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5752 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5753 DrawPosition(FALSE, boards[currentMove]);
5757 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5758 // All positions will have equal probability, but the current method will not provide a unique
5759 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5765 int piecesLeft[(int)BlackPawn];
5766 int seed, nrOfShuffles;
5769 GetPositionNumber ()
5770 { // sets global variable seed
5773 seed = appData.defaultFrcPosition;
5774 if(seed < 0) { // randomize based on time for negative FRC position numbers
5775 for(i=0; i<50; i++) seed += random();
5776 seed = random() ^ random() >> 8 ^ random() << 8;
5777 if(seed<0) seed = -seed;
5782 put (Board board, int pieceType, int rank, int n, int shade)
5783 // put the piece on the (n-1)-th empty squares of the given shade
5787 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5788 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5789 board[rank][i] = (ChessSquare) pieceType;
5790 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5792 piecesLeft[pieceType]--;
5801 AddOnePiece (Board board, int pieceType, int rank, int shade)
5802 // calculate where the next piece goes, (any empty square), and put it there
5806 i = seed % squaresLeft[shade];
5807 nrOfShuffles *= squaresLeft[shade];
5808 seed /= squaresLeft[shade];
5809 put(board, pieceType, rank, i, shade);
5813 AddTwoPieces (Board board, int pieceType, int rank)
5814 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5816 int i, n=squaresLeft[ANY], j=n-1, k;
5818 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5819 i = seed % k; // pick one
5822 while(i >= j) i -= j--;
5823 j = n - 1 - j; i += j;
5824 put(board, pieceType, rank, j, ANY);
5825 put(board, pieceType, rank, i, ANY);
5829 SetUpShuffle (Board board, int number)
5833 GetPositionNumber(); nrOfShuffles = 1;
5835 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5836 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5837 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5839 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5841 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5842 p = (int) board[0][i];
5843 if(p < (int) BlackPawn) piecesLeft[p] ++;
5844 board[0][i] = EmptySquare;
5847 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5848 // shuffles restricted to allow normal castling put KRR first
5849 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5850 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5851 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5852 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5853 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5854 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5855 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5856 put(board, WhiteRook, 0, 0, ANY);
5857 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5860 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5861 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5862 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5863 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5864 while(piecesLeft[p] >= 2) {
5865 AddOnePiece(board, p, 0, LITE);
5866 AddOnePiece(board, p, 0, DARK);
5868 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5871 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5872 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5873 // but we leave King and Rooks for last, to possibly obey FRC restriction
5874 if(p == (int)WhiteRook) continue;
5875 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5876 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5879 // now everything is placed, except perhaps King (Unicorn) and Rooks
5881 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5882 // Last King gets castling rights
5883 while(piecesLeft[(int)WhiteUnicorn]) {
5884 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5885 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5888 while(piecesLeft[(int)WhiteKing]) {
5889 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5890 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5895 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5896 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5899 // Only Rooks can be left; simply place them all
5900 while(piecesLeft[(int)WhiteRook]) {
5901 i = put(board, WhiteRook, 0, 0, ANY);
5902 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5905 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5907 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5910 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5911 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5914 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5918 SetCharTable (char *table, const char * map)
5919 /* [HGM] moved here from winboard.c because of its general usefulness */
5920 /* Basically a safe strcpy that uses the last character as King */
5922 int result = FALSE; int NrPieces;
5924 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5925 && NrPieces >= 12 && !(NrPieces&1)) {
5926 int i; /* [HGM] Accept even length from 12 to 34 */
5928 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5929 for( i=0; i<NrPieces/2-1; i++ ) {
5931 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5933 table[(int) WhiteKing] = map[NrPieces/2-1];
5934 table[(int) BlackKing] = map[NrPieces-1];
5943 Prelude (Board board)
5944 { // [HGM] superchess: random selection of exo-pieces
5945 int i, j, k; ChessSquare p;
5946 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5948 GetPositionNumber(); // use FRC position number
5950 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5951 SetCharTable(pieceToChar, appData.pieceToCharTable);
5952 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5953 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5956 j = seed%4; seed /= 4;
5957 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5958 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5959 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5960 j = seed%3 + (seed%3 >= j); seed /= 3;
5961 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5962 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5963 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5964 j = seed%3; seed /= 3;
5965 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5966 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5967 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5968 j = seed%2 + (seed%2 >= j); seed /= 2;
5969 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5970 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5971 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5972 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5973 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5974 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5975 put(board, exoPieces[0], 0, 0, ANY);
5976 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5980 InitPosition (int redraw)
5982 ChessSquare (* pieces)[BOARD_FILES];
5983 int i, j, pawnRow=1, pieceRows=1, overrule,
5984 oldx = gameInfo.boardWidth,
5985 oldy = gameInfo.boardHeight,
5986 oldh = gameInfo.holdingsWidth;
5989 if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5991 /* [AS] Initialize pv info list [HGM] and game status */
5993 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5994 pvInfoList[i].depth = 0;
5995 boards[i][EP_STATUS] = EP_NONE;
5996 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5999 initialRulePlies = 0; /* 50-move counter start */
6001 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6002 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6006 /* [HGM] logic here is completely changed. In stead of full positions */
6007 /* the initialized data only consist of the two backranks. The switch */
6008 /* selects which one we will use, which is than copied to the Board */
6009 /* initialPosition, which for the rest is initialized by Pawns and */
6010 /* empty squares. This initial position is then copied to boards[0], */
6011 /* possibly after shuffling, so that it remains available. */
6013 gameInfo.holdingsWidth = 0; /* default board sizes */
6014 gameInfo.boardWidth = 8;
6015 gameInfo.boardHeight = 8;
6016 gameInfo.holdingsSize = 0;
6017 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6018 for(i=0; i<BOARD_FILES-2; i++)
6019 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6020 initialPosition[EP_STATUS] = EP_NONE;
6021 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6022 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6023 SetCharTable(pieceNickName, appData.pieceNickNames);
6024 else SetCharTable(pieceNickName, "............");
6027 switch (gameInfo.variant) {
6028 case VariantFischeRandom:
6029 shuffleOpenings = TRUE;
6030 appData.fischerCastling = TRUE;
6033 case VariantShatranj:
6034 pieces = ShatranjArray;
6035 nrCastlingRights = 0;
6036 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6039 pieces = makrukArray;
6040 nrCastlingRights = 0;
6041 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6044 pieces = aseanArray;
6045 nrCastlingRights = 0;
6046 SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6048 case VariantTwoKings:
6049 pieces = twoKingsArray;
6052 pieces = GrandArray;
6053 nrCastlingRights = 0;
6054 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6055 gameInfo.boardWidth = 10;
6056 gameInfo.boardHeight = 10;
6057 gameInfo.holdingsSize = 7;
6059 case VariantCapaRandom:
6060 shuffleOpenings = TRUE;
6061 appData.fischerCastling = TRUE;
6062 case VariantCapablanca:
6063 pieces = CapablancaArray;
6064 gameInfo.boardWidth = 10;
6065 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6068 pieces = GothicArray;
6069 gameInfo.boardWidth = 10;
6070 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6073 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6074 gameInfo.holdingsSize = 7;
6075 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6078 pieces = JanusArray;
6079 gameInfo.boardWidth = 10;
6080 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6081 nrCastlingRights = 6;
6082 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6083 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6084 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6085 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6086 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6087 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6090 pieces = FalconArray;
6091 gameInfo.boardWidth = 10;
6092 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6094 case VariantXiangqi:
6095 pieces = XiangqiArray;
6096 gameInfo.boardWidth = 9;
6097 gameInfo.boardHeight = 10;
6098 nrCastlingRights = 0;
6099 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6102 pieces = ShogiArray;
6103 gameInfo.boardWidth = 9;
6104 gameInfo.boardHeight = 9;
6105 gameInfo.holdingsSize = 7;
6106 nrCastlingRights = 0;
6107 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6110 pieces = ChuArray; pieceRows = 3;
6111 gameInfo.boardWidth = 12;
6112 gameInfo.boardHeight = 12;
6113 nrCastlingRights = 0;
6114 SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6115 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6117 case VariantCourier:
6118 pieces = CourierArray;
6119 gameInfo.boardWidth = 12;
6120 nrCastlingRights = 0;
6121 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6123 case VariantKnightmate:
6124 pieces = KnightmateArray;
6125 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6127 case VariantSpartan:
6128 pieces = SpartanArray;
6129 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6133 SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6135 case VariantChuChess:
6136 pieces = ChuChessArray;
6137 gameInfo.boardWidth = 10;
6138 gameInfo.boardHeight = 10;
6139 SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6142 pieces = fairyArray;
6143 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6146 pieces = GreatArray;
6147 gameInfo.boardWidth = 10;
6148 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6149 gameInfo.holdingsSize = 8;
6153 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6154 gameInfo.holdingsSize = 8;
6155 startedFromSetupPosition = TRUE;
6157 case VariantCrazyhouse:
6158 case VariantBughouse:
6160 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6161 gameInfo.holdingsSize = 5;
6163 case VariantWildCastle:
6165 /* !!?shuffle with kings guaranteed to be on d or e file */
6166 shuffleOpenings = 1;
6168 case VariantNoCastle:
6170 nrCastlingRights = 0;
6171 /* !!?unconstrained back-rank shuffle */
6172 shuffleOpenings = 1;
6177 if(appData.NrFiles >= 0) {
6178 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6179 gameInfo.boardWidth = appData.NrFiles;
6181 if(appData.NrRanks >= 0) {
6182 gameInfo.boardHeight = appData.NrRanks;
6184 if(appData.holdingsSize >= 0) {
6185 i = appData.holdingsSize;
6186 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6187 gameInfo.holdingsSize = i;
6189 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6190 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6191 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6193 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6194 if(pawnRow < 1) pawnRow = 1;
6195 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6196 gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6197 if(gameInfo.variant == VariantChu) pawnRow = 3;
6199 /* User pieceToChar list overrules defaults */
6200 if(appData.pieceToCharTable != NULL)
6201 SetCharTable(pieceToChar, appData.pieceToCharTable);
6203 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6205 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6206 s = (ChessSquare) 0; /* account holding counts in guard band */
6207 for( i=0; i<BOARD_HEIGHT; i++ )
6208 initialPosition[i][j] = s;
6210 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6211 initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6212 initialPosition[pawnRow][j] = WhitePawn;
6213 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6214 if(gameInfo.variant == VariantXiangqi) {
6216 initialPosition[pawnRow][j] =
6217 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6218 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6219 initialPosition[2][j] = WhiteCannon;
6220 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6224 if(gameInfo.variant == VariantChu) {
6225 if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6226 initialPosition[pawnRow+1][j] = WhiteCobra,
6227 initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6228 for(i=1; i<pieceRows; i++) {
6229 initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6230 initialPosition[BOARD_HEIGHT-1-i][j] = pieces[2*i+1][j-gameInfo.holdingsWidth];
6233 if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6234 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6235 initialPosition[0][j] = WhiteRook;
6236 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6239 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] = pieces[1][j-gameInfo.holdingsWidth];
6241 if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6242 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6245 initialPosition[1][j] = WhiteBishop;
6246 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6248 initialPosition[1][j] = WhiteRook;
6249 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6252 if( nrCastlingRights == -1) {
6253 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6254 /* This sets default castling rights from none to normal corners */
6255 /* Variants with other castling rights must set them themselves above */
6256 nrCastlingRights = 6;
6258 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6259 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6260 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6261 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6262 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6263 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6266 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6267 if(gameInfo.variant == VariantGreat) { // promotion commoners
6268 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6269 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6270 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6271 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6273 if( gameInfo.variant == VariantSChess ) {
6274 initialPosition[1][0] = BlackMarshall;
6275 initialPosition[2][0] = BlackAngel;
6276 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6277 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6278 initialPosition[1][1] = initialPosition[2][1] =
6279 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6281 if (appData.debugMode) {
6282 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6284 if(shuffleOpenings) {
6285 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6286 startedFromSetupPosition = TRUE;
6288 if(startedFromPositionFile) {
6289 /* [HGM] loadPos: use PositionFile for every new game */
6290 CopyBoard(initialPosition, filePosition);
6291 for(i=0; i<nrCastlingRights; i++)
6292 initialRights[i] = filePosition[CASTLING][i];
6293 startedFromSetupPosition = TRUE;
6296 CopyBoard(boards[0], initialPosition);
6298 if(oldx != gameInfo.boardWidth ||
6299 oldy != gameInfo.boardHeight ||
6300 oldv != gameInfo.variant ||
6301 oldh != gameInfo.holdingsWidth
6303 InitDrawingSizes(-2 ,0);
6305 oldv = gameInfo.variant;
6307 DrawPosition(TRUE, boards[currentMove]);
6311 SendBoard (ChessProgramState *cps, int moveNum)
6313 char message[MSG_SIZ];
6315 if (cps->useSetboard) {
6316 char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6317 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6318 SendToProgram(message, cps);
6323 int i, j, left=0, right=BOARD_WIDTH;
6324 /* Kludge to set black to move, avoiding the troublesome and now
6325 * deprecated "black" command.
6327 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6328 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6330 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6332 SendToProgram("edit\n", cps);
6333 SendToProgram("#\n", cps);
6334 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6335 bp = &boards[moveNum][i][left];
6336 for (j = left; j < right; j++, bp++) {
6337 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6338 if ((int) *bp < (int) BlackPawn) {
6339 if(j == BOARD_RGHT+1)
6340 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6341 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6342 if(message[0] == '+' || message[0] == '~') {
6343 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6344 PieceToChar((ChessSquare)(DEMOTED *bp)),
6347 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6348 message[1] = BOARD_RGHT - 1 - j + '1';
6349 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6351 SendToProgram(message, cps);
6356 SendToProgram("c\n", cps);
6357 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6358 bp = &boards[moveNum][i][left];
6359 for (j = left; j < right; j++, bp++) {
6360 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6361 if (((int) *bp != (int) EmptySquare)
6362 && ((int) *bp >= (int) BlackPawn)) {
6363 if(j == BOARD_LEFT-2)
6364 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6365 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6367 if(message[0] == '+' || message[0] == '~') {
6368 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6369 PieceToChar((ChessSquare)(DEMOTED *bp)),
6372 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6373 message[1] = BOARD_RGHT - 1 - j + '1';
6374 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6376 SendToProgram(message, cps);
6381 SendToProgram(".\n", cps);
6383 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6386 char exclusionHeader[MSG_SIZ];
6387 int exCnt, excludePtr;
6388 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6389 static Exclusion excluTab[200];
6390 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6396 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6397 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6403 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6404 excludePtr = 24; exCnt = 0;
6409 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6410 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6411 char buf[2*MOVE_LEN], *p;
6412 Exclusion *e = excluTab;
6414 for(i=0; i<exCnt; i++)
6415 if(e[i].ff == fromX && e[i].fr == fromY &&
6416 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6417 if(i == exCnt) { // was not in exclude list; add it
6418 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6419 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6420 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6423 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6424 excludePtr++; e[i].mark = excludePtr++;
6425 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6428 exclusionHeader[e[i].mark] = state;
6432 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6433 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6437 if((signed char)promoChar == -1) { // kludge to indicate best move
6438 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6439 return 1; // if unparsable, abort
6441 // update exclusion map (resolving toggle by consulting existing state)
6442 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6444 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6445 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6446 excludeMap[k] |= 1<<j;
6447 else excludeMap[k] &= ~(1<<j);
6449 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6451 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6452 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6454 return (state == '+');
6458 ExcludeClick (int index)
6461 Exclusion *e = excluTab;
6462 if(index < 25) { // none, best or tail clicked
6463 if(index < 13) { // none: include all
6464 WriteMap(0); // clear map
6465 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6466 SendToBoth("include all\n"); // and inform engine
6467 } else if(index > 18) { // tail
6468 if(exclusionHeader[19] == '-') { // tail was excluded
6469 SendToBoth("include all\n");
6470 WriteMap(0); // clear map completely
6471 // now re-exclude selected moves
6472 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6473 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6474 } else { // tail was included or in mixed state
6475 SendToBoth("exclude all\n");
6476 WriteMap(0xFF); // fill map completely
6477 // now re-include selected moves
6478 j = 0; // count them
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, '+'), j++;
6481 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6484 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6487 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6488 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6489 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6496 DefaultPromoChoice (int white)
6499 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6500 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6501 result = WhiteFerz; // no choice
6502 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6503 result= WhiteKing; // in Suicide Q is the last thing we want
6504 else if(gameInfo.variant == VariantSpartan)
6505 result = white ? WhiteQueen : WhiteAngel;
6506 else result = WhiteQueen;
6507 if(!white) result = WHITE_TO_BLACK result;
6511 static int autoQueen; // [HGM] oneclick
6514 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6516 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6517 /* [HGM] add Shogi promotions */
6518 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6519 ChessSquare piece, partner;
6523 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6524 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6526 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6527 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6530 piece = boards[currentMove][fromY][fromX];
6531 if(gameInfo.variant == VariantChu) {
6532 int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6533 promotionZoneSize = BOARD_HEIGHT/3;
6534 highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6535 } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6536 promotionZoneSize = BOARD_HEIGHT/3;
6537 highestPromotingPiece = (int)WhiteAlfil;
6538 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6539 promotionZoneSize = 3;
6542 // Treat Lance as Pawn when it is not representing Amazon or Lance
6543 if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6544 if(piece == WhiteLance) piece = WhitePawn; else
6545 if(piece == BlackLance) piece = BlackPawn;
6548 // next weed out all moves that do not touch the promotion zone at all
6549 if((int)piece >= BlackPawn) {
6550 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6552 if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6553 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6555 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6556 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6557 if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6561 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6563 // weed out mandatory Shogi promotions
6564 if(gameInfo.variant == VariantShogi) {
6565 if(piece >= BlackPawn) {
6566 if(toY == 0 && piece == BlackPawn ||
6567 toY == 0 && piece == BlackQueen ||
6568 toY <= 1 && piece == BlackKnight) {
6573 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6574 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6575 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6582 // weed out obviously illegal Pawn moves
6583 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6584 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6585 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6586 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6587 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6588 // note we are not allowed to test for valid (non-)capture, due to premove
6591 // we either have a choice what to promote to, or (in Shogi) whether to promote
6592 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6593 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6594 ChessSquare p=BlackFerz; // no choice
6595 while(p < EmptySquare) { //but make sure we use piece that exists
6596 *promoChoice = PieceToChar(p++);
6597 if(*promoChoice != '.') break;
6601 // no sense asking what we must promote to if it is going to explode...
6602 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6603 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6606 // give caller the default choice even if we will not make it
6607 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6608 partner = piece; // pieces can promote if the pieceToCharTable says so
6609 if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6610 else if(Partner(&partner)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6611 if( sweepSelect && gameInfo.variant != VariantGreat
6612 && gameInfo.variant != VariantGrand
6613 && gameInfo.variant != VariantSuper) return FALSE;
6614 if(autoQueen) return FALSE; // predetermined
6616 // suppress promotion popup on illegal moves that are not premoves
6617 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6618 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6619 if(appData.testLegality && !premove) {
6620 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6621 fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6622 if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6623 if(moveType != WhitePromotion && moveType != BlackPromotion)
6631 InPalace (int row, int column)
6632 { /* [HGM] for Xiangqi */
6633 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6634 column < (BOARD_WIDTH + 4)/2 &&
6635 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6640 PieceForSquare (int x, int y)
6642 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6645 return boards[currentMove][y][x];
6649 OKToStartUserMove (int x, int y)
6651 ChessSquare from_piece;
6654 if (matchMode) return FALSE;
6655 if (gameMode == EditPosition) return TRUE;
6657 if (x >= 0 && y >= 0)
6658 from_piece = boards[currentMove][y][x];
6660 from_piece = EmptySquare;
6662 if (from_piece == EmptySquare) return FALSE;
6664 white_piece = (int)from_piece >= (int)WhitePawn &&
6665 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6669 case TwoMachinesPlay:
6677 case MachinePlaysWhite:
6678 case IcsPlayingBlack:
6679 if (appData.zippyPlay) return FALSE;
6681 DisplayMoveError(_("You are playing Black"));
6686 case MachinePlaysBlack:
6687 case IcsPlayingWhite:
6688 if (appData.zippyPlay) return FALSE;
6690 DisplayMoveError(_("You are playing White"));
6695 case PlayFromGameFile:
6696 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6698 if (!white_piece && WhiteOnMove(currentMove)) {
6699 DisplayMoveError(_("It is White's turn"));
6702 if (white_piece && !WhiteOnMove(currentMove)) {
6703 DisplayMoveError(_("It is Black's turn"));
6706 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6707 /* Editing correspondence game history */
6708 /* Could disallow this or prompt for confirmation */
6713 case BeginningOfGame:
6714 if (appData.icsActive) return FALSE;
6715 if (!appData.noChessProgram) {
6717 DisplayMoveError(_("You are playing White"));
6724 if (!white_piece && WhiteOnMove(currentMove)) {
6725 DisplayMoveError(_("It is White's turn"));
6728 if (white_piece && !WhiteOnMove(currentMove)) {
6729 DisplayMoveError(_("It is Black's turn"));
6738 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6739 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6740 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6741 && gameMode != AnalyzeFile && gameMode != Training) {
6742 DisplayMoveError(_("Displayed position is not current"));
6749 OnlyMove (int *x, int *y, Boolean captures)
6751 DisambiguateClosure cl;
6752 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6754 case MachinePlaysBlack:
6755 case IcsPlayingWhite:
6756 case BeginningOfGame:
6757 if(!WhiteOnMove(currentMove)) return FALSE;
6759 case MachinePlaysWhite:
6760 case IcsPlayingBlack:
6761 if(WhiteOnMove(currentMove)) return FALSE;
6768 cl.pieceIn = EmptySquare;
6773 cl.promoCharIn = NULLCHAR;
6774 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6775 if( cl.kind == NormalMove ||
6776 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6777 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6778 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6785 if(cl.kind != ImpossibleMove) return FALSE;
6786 cl.pieceIn = EmptySquare;
6791 cl.promoCharIn = NULLCHAR;
6792 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6793 if( cl.kind == NormalMove ||
6794 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6795 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6796 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6801 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6807 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6808 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6809 int lastLoadGameUseList = FALSE;
6810 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6811 ChessMove lastLoadGameStart = EndOfFile;
6815 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6819 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6821 /* Check if the user is playing in turn. This is complicated because we
6822 let the user "pick up" a piece before it is his turn. So the piece he
6823 tried to pick up may have been captured by the time he puts it down!
6824 Therefore we use the color the user is supposed to be playing in this
6825 test, not the color of the piece that is currently on the starting
6826 square---except in EditGame mode, where the user is playing both
6827 sides; fortunately there the capture race can't happen. (It can
6828 now happen in IcsExamining mode, but that's just too bad. The user
6829 will get a somewhat confusing message in that case.)
6834 case TwoMachinesPlay:
6838 /* We switched into a game mode where moves are not accepted,
6839 perhaps while the mouse button was down. */
6842 case MachinePlaysWhite:
6843 /* User is moving for Black */
6844 if (WhiteOnMove(currentMove)) {
6845 DisplayMoveError(_("It is White's turn"));
6850 case MachinePlaysBlack:
6851 /* User is moving for White */
6852 if (!WhiteOnMove(currentMove)) {
6853 DisplayMoveError(_("It is Black's turn"));
6858 case PlayFromGameFile:
6859 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6862 case BeginningOfGame:
6865 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6866 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6867 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6868 /* User is moving for Black */
6869 if (WhiteOnMove(currentMove)) {
6870 DisplayMoveError(_("It is White's turn"));
6874 /* User is moving for White */
6875 if (!WhiteOnMove(currentMove)) {
6876 DisplayMoveError(_("It is Black's turn"));
6882 case IcsPlayingBlack:
6883 /* User is moving for Black */
6884 if (WhiteOnMove(currentMove)) {
6885 if (!appData.premove) {
6886 DisplayMoveError(_("It is White's turn"));
6887 } else if (toX >= 0 && toY >= 0) {
6890 premoveFromX = fromX;
6891 premoveFromY = fromY;
6892 premovePromoChar = promoChar;
6894 if (appData.debugMode)
6895 fprintf(debugFP, "Got premove: fromX %d,"
6896 "fromY %d, toX %d, toY %d\n",
6897 fromX, fromY, toX, toY);
6903 case IcsPlayingWhite:
6904 /* User is moving for White */
6905 if (!WhiteOnMove(currentMove)) {
6906 if (!appData.premove) {
6907 DisplayMoveError(_("It is Black's turn"));
6908 } else if (toX >= 0 && toY >= 0) {
6911 premoveFromX = fromX;
6912 premoveFromY = fromY;
6913 premovePromoChar = promoChar;
6915 if (appData.debugMode)
6916 fprintf(debugFP, "Got premove: fromX %d,"
6917 "fromY %d, toX %d, toY %d\n",
6918 fromX, fromY, toX, toY);
6928 /* EditPosition, empty square, or different color piece;
6929 click-click move is possible */
6930 if (toX == -2 || toY == -2) {
6931 boards[0][fromY][fromX] = EmptySquare;
6932 DrawPosition(FALSE, boards[currentMove]);
6934 } else if (toX >= 0 && toY >= 0) {
6935 boards[0][toY][toX] = boards[0][fromY][fromX];
6936 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6937 if(boards[0][fromY][0] != EmptySquare) {
6938 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6939 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6942 if(fromX == BOARD_RGHT+1) {
6943 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6944 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6945 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6948 boards[0][fromY][fromX] = gatingPiece;
6949 DrawPosition(FALSE, boards[currentMove]);
6955 if(toX < 0 || toY < 0) return;
6956 pup = boards[currentMove][toY][toX];
6958 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6959 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6960 if( pup != EmptySquare ) return;
6961 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6962 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6963 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6964 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6965 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6966 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6967 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6971 /* [HGM] always test for legality, to get promotion info */
6972 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6973 fromY, fromX, toY, toX, promoChar);
6975 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6977 /* [HGM] but possibly ignore an IllegalMove result */
6978 if (appData.testLegality) {
6979 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6980 DisplayMoveError(_("Illegal move"));
6985 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6986 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6987 ClearPremoveHighlights(); // was included
6988 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
6992 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6995 /* Common tail of UserMoveEvent and DropMenuEvent */
6997 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7001 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7002 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7003 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7004 if(WhiteOnMove(currentMove)) {
7005 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7007 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7011 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7012 move type in caller when we know the move is a legal promotion */
7013 if(moveType == NormalMove && promoChar)
7014 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7016 /* [HGM] <popupFix> The following if has been moved here from
7017 UserMoveEvent(). Because it seemed to belong here (why not allow
7018 piece drops in training games?), and because it can only be
7019 performed after it is known to what we promote. */
7020 if (gameMode == Training) {
7021 /* compare the move played on the board to the next move in the
7022 * game. If they match, display the move and the opponent's response.
7023 * If they don't match, display an error message.
7027 CopyBoard(testBoard, boards[currentMove]);
7028 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7030 if (CompareBoards(testBoard, boards[currentMove+1])) {
7031 ForwardInner(currentMove+1);
7033 /* Autoplay the opponent's response.
7034 * if appData.animate was TRUE when Training mode was entered,
7035 * the response will be animated.
7037 saveAnimate = appData.animate;
7038 appData.animate = animateTraining;
7039 ForwardInner(currentMove+1);
7040 appData.animate = saveAnimate;
7042 /* check for the end of the game */
7043 if (currentMove >= forwardMostMove) {
7044 gameMode = PlayFromGameFile;
7046 SetTrainingModeOff();
7047 DisplayInformation(_("End of game"));
7050 DisplayError(_("Incorrect move"), 0);
7055 /* Ok, now we know that the move is good, so we can kill
7056 the previous line in Analysis Mode */
7057 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7058 && currentMove < forwardMostMove) {
7059 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7060 else forwardMostMove = currentMove;
7065 /* If we need the chess program but it's dead, restart it */
7066 ResurrectChessProgram();
7068 /* A user move restarts a paused game*/
7072 thinkOutput[0] = NULLCHAR;
7074 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7076 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7077 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7081 if (gameMode == BeginningOfGame) {
7082 if (appData.noChessProgram) {
7083 gameMode = EditGame;
7087 gameMode = MachinePlaysBlack;
7090 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7092 if (first.sendName) {
7093 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7094 SendToProgram(buf, &first);
7101 /* Relay move to ICS or chess engine */
7102 if (appData.icsActive) {
7103 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7104 gameMode == IcsExamining) {
7105 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7106 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7108 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7110 // also send plain move, in case ICS does not understand atomic claims
7111 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7115 if (first.sendTime && (gameMode == BeginningOfGame ||
7116 gameMode == MachinePlaysWhite ||
7117 gameMode == MachinePlaysBlack)) {
7118 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7120 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7121 // [HGM] book: if program might be playing, let it use book
7122 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7123 first.maybeThinking = TRUE;
7124 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7125 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7126 SendBoard(&first, currentMove+1);
7127 if(second.analyzing) {
7128 if(!second.useSetboard) SendToProgram("undo\n", &second);
7129 SendBoard(&second, currentMove+1);
7132 SendMoveToProgram(forwardMostMove-1, &first);
7133 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7135 if (currentMove == cmailOldMove + 1) {
7136 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7140 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7144 if(appData.testLegality)
7145 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7151 if (WhiteOnMove(currentMove)) {
7152 GameEnds(BlackWins, "Black mates", GE_PLAYER);
7154 GameEnds(WhiteWins, "White mates", GE_PLAYER);
7158 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7163 case MachinePlaysBlack:
7164 case MachinePlaysWhite:
7165 /* disable certain menu options while machine is thinking */
7166 SetMachineThinkingEnables();
7173 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7174 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7176 if(bookHit) { // [HGM] book: simulate book reply
7177 static char bookMove[MSG_SIZ]; // a bit generous?
7179 programStats.nodes = programStats.depth = programStats.time =
7180 programStats.score = programStats.got_only_move = 0;
7181 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7183 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7184 strcat(bookMove, bookHit);
7185 HandleMachineMove(bookMove, &first);
7191 MarkByFEN(char *fen)
7194 if(!appData.markers || !appData.highlightDragging) return;
7195 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7196 r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7200 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7201 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7202 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7203 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7204 if(*fen == 'T') marker[r][f++] = 0; else
7205 if(*fen == 'Y') marker[r][f++] = 1; else
7206 if(*fen == 'G') marker[r][f++] = 3; else
7207 if(*fen == 'B') marker[r][f++] = 4; else
7208 if(*fen == 'C') marker[r][f++] = 5; else
7209 if(*fen == 'M') marker[r][f++] = 6; else
7210 if(*fen == 'W') marker[r][f++] = 7; else
7211 if(*fen == 'D') marker[r][f++] = 8; else
7212 if(*fen == 'R') marker[r][f++] = 2; else {
7213 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7216 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7220 DrawPosition(TRUE, NULL);
7223 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7226 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7228 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7229 Markers *m = (Markers *) closure;
7230 if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7231 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7232 || kind == WhiteCapturesEnPassant
7233 || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7234 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7237 static int hoverSavedValid;
7240 MarkTargetSquares (int clear)
7243 if(clear) { // no reason to ever suppress clearing
7244 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7245 hoverSavedValid = 0;
7246 if(!sum) return; // nothing was cleared,no redraw needed
7249 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7250 !appData.testLegality || gameMode == EditPosition) return;
7251 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7252 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7253 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7255 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7258 DrawPosition(FALSE, NULL);
7262 Explode (Board board, int fromX, int fromY, int toX, int toY)
7264 if(gameInfo.variant == VariantAtomic &&
7265 (board[toY][toX] != EmptySquare || // capture?
7266 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7267 board[fromY][fromX] == BlackPawn )
7269 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7275 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7278 CanPromote (ChessSquare piece, int y)
7280 int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7281 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7282 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7283 if(IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantXiangqi ||
7284 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7285 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7286 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) return FALSE;
7287 return (piece == BlackPawn && y <= zone ||
7288 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7289 piece == BlackLance && y == 1 ||
7290 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7294 HoverEvent (int xPix, int yPix, int x, int y)
7296 static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7298 if(!first.highlight) return;
7299 if(fromX != oldFromX || fromY != oldFromY) oldX = oldY = -1; // kludge to fake entry on from-click
7300 if(x == oldX && y == oldY) return; // only do something if we enter new square
7301 oldFromX = fromX; oldFromY = fromY;
7302 if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7303 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7304 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7305 hoverSavedValid = 1;
7306 } else if(oldX != x || oldY != y) {
7307 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7308 if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7309 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7310 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7311 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7313 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7314 SendToProgram(buf, &first);
7317 // SetHighlights(fromX, fromY, x, y);
7321 void ReportClick(char *action, int x, int y)
7323 char buf[MSG_SIZ]; // Inform engine of what user does
7325 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7326 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7327 if(!first.highlight || gameMode == EditPosition) return;
7328 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7329 SendToProgram(buf, &first);
7333 LeftClick (ClickType clickType, int xPix, int yPix)
7336 Boolean saveAnimate;
7337 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7338 char promoChoice = NULLCHAR;
7340 static TimeMark lastClickTime, prevClickTime;
7342 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7344 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7346 if (clickType == Press) ErrorPopDown();
7347 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7349 x = EventToSquare(xPix, BOARD_WIDTH);
7350 y = EventToSquare(yPix, BOARD_HEIGHT);
7351 if (!flipView && y >= 0) {
7352 y = BOARD_HEIGHT - 1 - y;
7354 if (flipView && x >= 0) {
7355 x = BOARD_WIDTH - 1 - x;
7358 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7359 defaultPromoChoice = promoSweep;
7360 promoSweep = EmptySquare; // terminate sweep
7361 promoDefaultAltered = TRUE;
7362 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7365 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7366 if(clickType == Release) return; // ignore upclick of click-click destination
7367 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7368 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7369 if(gameInfo.holdingsWidth &&
7370 (WhiteOnMove(currentMove)
7371 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7372 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7373 // click in right holdings, for determining promotion piece
7374 ChessSquare p = boards[currentMove][y][x];
7375 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7376 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7377 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7378 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7383 DrawPosition(FALSE, boards[currentMove]);
7387 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7388 if(clickType == Press
7389 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7390 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7391 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7394 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7395 // could be static click on premove from-square: abort premove
7397 ClearPremoveHighlights();
7400 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7401 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7403 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7404 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7405 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7406 defaultPromoChoice = DefaultPromoChoice(side);
7409 autoQueen = appData.alwaysPromoteToQueen;
7413 gatingPiece = EmptySquare;
7414 if (clickType != Press) {
7415 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7416 DragPieceEnd(xPix, yPix); dragging = 0;
7417 DrawPosition(FALSE, NULL);
7421 doubleClick = FALSE;
7422 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7423 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7425 fromX = x; fromY = y; toX = toY = killX = killY = -1;
7426 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7427 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7428 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7430 if (OKToStartUserMove(fromX, fromY)) {
7432 ReportClick("lift", x, y);
7433 MarkTargetSquares(0);
7434 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7435 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7436 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7437 promoSweep = defaultPromoChoice;
7438 selectFlag = 0; lastX = xPix; lastY = yPix;
7439 Sweep(0); // Pawn that is going to promote: preview promotion piece
7440 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7442 if (appData.highlightDragging) {
7443 SetHighlights(fromX, fromY, -1, -1);
7447 } else fromX = fromY = -1;
7453 if (clickType == Press && gameMode != EditPosition) {
7458 // ignore off-board to clicks
7459 if(y < 0 || x < 0) return;
7461 /* Check if clicking again on the same color piece */
7462 fromP = boards[currentMove][fromY][fromX];
7463 toP = boards[currentMove][y][x];
7464 frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7465 if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7466 ((WhitePawn <= fromP && fromP <= WhiteKing &&
7467 WhitePawn <= toP && toP <= WhiteKing &&
7468 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7469 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7470 (BlackPawn <= fromP && fromP <= BlackKing &&
7471 BlackPawn <= toP && toP <= BlackKing &&
7472 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7473 !(fromP == BlackKing && toP == BlackRook && frc)))) {
7474 /* Clicked again on same color piece -- changed his mind */
7475 second = (x == fromX && y == fromY);
7477 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7478 second = FALSE; // first double-click rather than scond click
7479 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7481 promoDefaultAltered = FALSE;
7482 MarkTargetSquares(1);
7483 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7484 if (appData.highlightDragging) {
7485 SetHighlights(x, y, -1, -1);
7489 if (OKToStartUserMove(x, y)) {
7490 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7491 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7492 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7493 gatingPiece = boards[currentMove][fromY][fromX];
7494 else gatingPiece = doubleClick ? fromP : EmptySquare;
7496 fromY = y; dragging = 1;
7497 ReportClick("lift", x, y);
7498 MarkTargetSquares(0);
7499 DragPieceBegin(xPix, yPix, FALSE);
7500 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7501 promoSweep = defaultPromoChoice;
7502 selectFlag = 0; lastX = xPix; lastY = yPix;
7503 Sweep(0); // Pawn that is going to promote: preview promotion piece
7507 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7510 // ignore clicks on holdings
7511 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7514 if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7515 DragPieceEnd(xPix, yPix); dragging = 0;
7517 // a deferred attempt to click-click move an empty square on top of a piece
7518 boards[currentMove][y][x] = EmptySquare;
7520 DrawPosition(FALSE, boards[currentMove]);
7521 fromX = fromY = -1; clearFlag = 0;
7524 if (appData.animateDragging) {
7525 /* Undo animation damage if any */
7526 DrawPosition(FALSE, NULL);
7528 if (second || sweepSelecting) {
7529 /* Second up/down in same square; just abort move */
7530 if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7531 second = sweepSelecting = 0;
7533 gatingPiece = EmptySquare;
7534 MarkTargetSquares(1);
7537 ClearPremoveHighlights();
7539 /* First upclick in same square; start click-click mode */
7540 SetHighlights(x, y, -1, -1);
7547 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7548 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7549 DisplayMessage(_("only marked squares are legal"),"");
7550 DrawPosition(TRUE, NULL);
7551 return; // ignore to-click
7554 /* we now have a different from- and (possibly off-board) to-square */
7555 /* Completed move */
7556 if(!sweepSelecting) {
7561 piece = boards[currentMove][fromY][fromX];
7563 saveAnimate = appData.animate;
7564 if (clickType == Press) {
7565 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7566 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7567 // must be Edit Position mode with empty-square selected
7568 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7569 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7572 if(dragging == 2) { // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7575 if(x == killX && y == killY) { // second click on this square, which was selected as first-leg target
7576 killX = killY = -1; // this informs us no second leg is coming, so treat as to-click without intermediate
7578 if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7579 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7580 if(appData.sweepSelect) {
7581 promoSweep = defaultPromoChoice;
7582 if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7583 selectFlag = 0; lastX = xPix; lastY = yPix;
7584 Sweep(0); // Pawn that is going to promote: preview promotion piece
7586 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7587 MarkTargetSquares(1);
7589 return; // promo popup appears on up-click
7591 /* Finish clickclick move */
7592 if (appData.animate || appData.highlightLastMove) {
7593 SetHighlights(fromX, fromY, toX, toY);
7597 } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7598 sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7599 if (appData.animate || appData.highlightLastMove) {
7600 SetHighlights(fromX, fromY, toX, toY);
7606 // [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
7607 /* Finish drag move */
7608 if (appData.highlightLastMove) {
7609 SetHighlights(fromX, fromY, toX, toY);
7614 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7615 if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7616 dragging *= 2; // flag button-less dragging if we are dragging
7617 MarkTargetSquares(1);
7618 if(x == killX && y == killY) killX = killY = -1; else {
7619 killX = x; killY = y; //remeber this square as intermediate
7620 ReportClick("put", x, y); // and inform engine
7621 ReportClick("lift", x, y);
7622 MarkTargetSquares(0);
7626 DragPieceEnd(xPix, yPix); dragging = 0;
7627 /* Don't animate move and drag both */
7628 appData.animate = FALSE;
7631 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7632 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7633 ChessSquare piece = boards[currentMove][fromY][fromX];
7634 if(gameMode == EditPosition && piece != EmptySquare &&
7635 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7638 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7639 n = PieceToNumber(piece - (int)BlackPawn);
7640 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7641 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7642 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7644 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7645 n = PieceToNumber(piece);
7646 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7647 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7648 boards[currentMove][n][BOARD_WIDTH-2]++;
7650 boards[currentMove][fromY][fromX] = EmptySquare;
7654 MarkTargetSquares(1);
7655 DrawPosition(TRUE, boards[currentMove]);
7659 // off-board moves should not be highlighted
7660 if(x < 0 || y < 0) ClearHighlights();
7661 else ReportClick("put", x, y);
7663 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7665 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7666 SetHighlights(fromX, fromY, toX, toY);
7667 MarkTargetSquares(1);
7668 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7669 // [HGM] super: promotion to captured piece selected from holdings
7670 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7671 promotionChoice = TRUE;
7672 // kludge follows to temporarily execute move on display, without promoting yet
7673 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7674 boards[currentMove][toY][toX] = p;
7675 DrawPosition(FALSE, boards[currentMove]);
7676 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7677 boards[currentMove][toY][toX] = q;
7678 DisplayMessage("Click in holdings to choose piece", "");
7681 PromotionPopUp(promoChoice);
7683 int oldMove = currentMove;
7684 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7685 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7686 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7687 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7688 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7689 DrawPosition(TRUE, boards[currentMove]);
7690 MarkTargetSquares(1);
7693 appData.animate = saveAnimate;
7694 if (appData.animate || appData.animateDragging) {
7695 /* Undo animation damage if needed */
7696 DrawPosition(FALSE, NULL);
7701 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7702 { // front-end-free part taken out of PieceMenuPopup
7703 int whichMenu; int xSqr, ySqr;
7705 if(seekGraphUp) { // [HGM] seekgraph
7706 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7707 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7711 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7712 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7713 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7714 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7715 if(action == Press) {
7716 originalFlip = flipView;
7717 flipView = !flipView; // temporarily flip board to see game from partners perspective
7718 DrawPosition(TRUE, partnerBoard);
7719 DisplayMessage(partnerStatus, "");
7721 } else if(action == Release) {
7722 flipView = originalFlip;
7723 DrawPosition(TRUE, boards[currentMove]);
7729 xSqr = EventToSquare(x, BOARD_WIDTH);
7730 ySqr = EventToSquare(y, BOARD_HEIGHT);
7731 if (action == Release) {
7732 if(pieceSweep != EmptySquare) {
7733 EditPositionMenuEvent(pieceSweep, toX, toY);
7734 pieceSweep = EmptySquare;
7735 } else UnLoadPV(); // [HGM] pv
7737 if (action != Press) return -2; // return code to be ignored
7740 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7742 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7743 if (xSqr < 0 || ySqr < 0) return -1;
7744 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7745 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7746 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7747 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7751 if(!appData.icsEngineAnalyze) return -1;
7752 case IcsPlayingWhite:
7753 case IcsPlayingBlack:
7754 if(!appData.zippyPlay) goto noZip;
7757 case MachinePlaysWhite:
7758 case MachinePlaysBlack:
7759 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7760 if (!appData.dropMenu) {
7762 return 2; // flag front-end to grab mouse events
7764 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7765 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7768 if (xSqr < 0 || ySqr < 0) return -1;
7769 if (!appData.dropMenu || appData.testLegality &&
7770 gameInfo.variant != VariantBughouse &&
7771 gameInfo.variant != VariantCrazyhouse) return -1;
7772 whichMenu = 1; // drop menu
7778 if (((*fromX = xSqr) < 0) ||
7779 ((*fromY = ySqr) < 0)) {
7780 *fromX = *fromY = -1;
7784 *fromX = BOARD_WIDTH - 1 - *fromX;
7786 *fromY = BOARD_HEIGHT - 1 - *fromY;
7792 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7794 // char * hint = lastHint;
7795 FrontEndProgramStats stats;
7797 stats.which = cps == &first ? 0 : 1;
7798 stats.depth = cpstats->depth;
7799 stats.nodes = cpstats->nodes;
7800 stats.score = cpstats->score;
7801 stats.time = cpstats->time;
7802 stats.pv = cpstats->movelist;
7803 stats.hint = lastHint;
7804 stats.an_move_index = 0;
7805 stats.an_move_count = 0;
7807 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7808 stats.hint = cpstats->move_name;
7809 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7810 stats.an_move_count = cpstats->nr_moves;
7813 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
7815 SetProgramStats( &stats );
7819 ClearEngineOutputPane (int which)
7821 static FrontEndProgramStats dummyStats;
7822 dummyStats.which = which;
7823 dummyStats.pv = "#";
7824 SetProgramStats( &dummyStats );
7827 #define MAXPLAYERS 500
7830 TourneyStandings (int display)
7832 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7833 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7834 char result, *p, *names[MAXPLAYERS];
7836 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7837 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7838 names[0] = p = strdup(appData.participants);
7839 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7841 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7843 while(result = appData.results[nr]) {
7844 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7845 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7846 wScore = bScore = 0;
7848 case '+': wScore = 2; break;
7849 case '-': bScore = 2; break;
7850 case '=': wScore = bScore = 1; break;
7852 case '*': return strdup("busy"); // tourney not finished
7860 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7861 for(w=0; w<nPlayers; w++) {
7863 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7864 ranking[w] = b; points[w] = bScore; score[b] = -2;
7866 p = malloc(nPlayers*34+1);
7867 for(w=0; w<nPlayers && w<display; w++)
7868 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7874 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7875 { // count all piece types
7877 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7878 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7879 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7882 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7883 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7884 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7885 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7886 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7887 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7892 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7894 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7895 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7897 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7898 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7899 if(myPawns == 2 && nMine == 3) // KPP
7900 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7901 if(myPawns == 1 && nMine == 2) // KP
7902 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7903 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7904 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7905 if(myPawns) return FALSE;
7906 if(pCnt[WhiteRook+side])
7907 return pCnt[BlackRook-side] ||
7908 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7909 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7910 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7911 if(pCnt[WhiteCannon+side]) {
7912 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7913 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7915 if(pCnt[WhiteKnight+side])
7916 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7921 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7923 VariantClass v = gameInfo.variant;
7925 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7926 if(v == VariantShatranj) return TRUE; // always winnable through baring
7927 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7928 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7930 if(v == VariantXiangqi) {
7931 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7933 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7934 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7935 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7936 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7937 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7938 if(stale) // we have at least one last-rank P plus perhaps C
7939 return majors // KPKX
7940 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7942 return pCnt[WhiteFerz+side] // KCAK
7943 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7944 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7945 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7947 } else if(v == VariantKnightmate) {
7948 if(nMine == 1) return FALSE;
7949 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7950 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7951 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7953 if(nMine == 1) return FALSE; // bare King
7954 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
7955 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7956 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7957 // by now we have King + 1 piece (or multiple Bishops on the same color)
7958 if(pCnt[WhiteKnight+side])
7959 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7960 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7961 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7963 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7964 if(pCnt[WhiteAlfil+side])
7965 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7966 if(pCnt[WhiteWazir+side])
7967 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7974 CompareWithRights (Board b1, Board b2)
7977 if(!CompareBoards(b1, b2)) return FALSE;
7978 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7979 /* compare castling rights */
7980 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7981 rights++; /* King lost rights, while rook still had them */
7982 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7983 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7984 rights++; /* but at least one rook lost them */
7986 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7988 if( b1[CASTLING][5] != NoRights ) {
7989 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7996 Adjudicate (ChessProgramState *cps)
7997 { // [HGM] some adjudications useful with buggy engines
7998 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7999 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8000 // Actually ending the game is now based on the additional internal condition canAdjudicate.
8001 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8002 int k, drop, count = 0; static int bare = 1;
8003 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8004 Boolean canAdjudicate = !appData.icsActive;
8006 // most tests only when we understand the game, i.e. legality-checking on
8007 if( appData.testLegality )
8008 { /* [HGM] Some more adjudications for obstinate engines */
8009 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8010 static int moveCount = 6;
8012 char *reason = NULL;
8014 /* Count what is on board. */
8015 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8017 /* Some material-based adjudications that have to be made before stalemate test */
8018 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8019 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8020 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8021 if(canAdjudicate && appData.checkMates) {
8023 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8024 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8025 "Xboard adjudication: King destroyed", GE_XBOARD );
8030 /* Bare King in Shatranj (loses) or Losers (wins) */
8031 if( nrW == 1 || nrB == 1) {
8032 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8033 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
8034 if(canAdjudicate && appData.checkMates) {
8036 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8037 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8038 "Xboard adjudication: Bare king", GE_XBOARD );
8042 if( gameInfo.variant == VariantShatranj && --bare < 0)
8044 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8045 if(canAdjudicate && appData.checkMates) {
8046 /* but only adjudicate if adjudication enabled */
8048 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8049 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8050 "Xboard adjudication: Bare king", GE_XBOARD );
8057 // don't wait for engine to announce game end if we can judge ourselves
8058 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8060 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8061 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
8062 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8063 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8066 reason = "Xboard adjudication: 3rd check";
8067 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8078 reason = "Xboard adjudication: Stalemate";
8079 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8080 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
8081 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8082 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
8083 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8084 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8085 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8086 EP_CHECKMATE : EP_WINS);
8087 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8088 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8092 reason = "Xboard adjudication: Checkmate";
8093 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8094 if(gameInfo.variant == VariantShogi) {
8095 if(forwardMostMove > backwardMostMove
8096 && moveList[forwardMostMove-1][1] == '@'
8097 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8098 reason = "XBoard adjudication: pawn-drop mate";
8099 boards[forwardMostMove][EP_STATUS] = EP_WINS;
8105 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8107 result = GameIsDrawn; break;
8109 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8111 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8115 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8117 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8118 GameEnds( result, reason, GE_XBOARD );
8122 /* Next absolutely insufficient mating material. */
8123 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8124 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8125 { /* includes KBK, KNK, KK of KBKB with like Bishops */
8127 /* always flag draws, for judging claims */
8128 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8130 if(canAdjudicate && appData.materialDraws) {
8131 /* but only adjudicate them if adjudication enabled */
8132 if(engineOpponent) {
8133 SendToProgram("force\n", engineOpponent); // suppress reply
8134 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8136 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8141 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8142 if(gameInfo.variant == VariantXiangqi ?
8143 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8145 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8146 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
8147 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
8148 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8150 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8151 { /* if the first 3 moves do not show a tactical win, declare draw */
8152 if(engineOpponent) {
8153 SendToProgram("force\n", engineOpponent); // suppress reply
8154 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8156 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8159 } else moveCount = 6;
8162 // Repetition draws and 50-move rule can be applied independently of legality testing
8164 /* Check for rep-draws */
8166 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8167 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8168 for(k = forwardMostMove-2;
8169 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8170 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8171 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8174 if(CompareBoards(boards[k], boards[forwardMostMove])) {
8175 /* compare castling rights */
8176 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8177 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8178 rights++; /* King lost rights, while rook still had them */
8179 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8180 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8181 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8182 rights++; /* but at least one rook lost them */
8184 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8185 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8187 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8188 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8189 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8192 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8193 && appData.drawRepeats > 1) {
8194 /* adjudicate after user-specified nr of repeats */
8195 int result = GameIsDrawn;
8196 char *details = "XBoard adjudication: repetition draw";
8197 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8198 // [HGM] xiangqi: check for forbidden perpetuals
8199 int m, ourPerpetual = 1, hisPerpetual = 1;
8200 for(m=forwardMostMove; m>k; m-=2) {
8201 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8202 ourPerpetual = 0; // the current mover did not always check
8203 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8204 hisPerpetual = 0; // the opponent did not always check
8206 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8207 ourPerpetual, hisPerpetual);
8208 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8209 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8210 details = "Xboard adjudication: perpetual checking";
8212 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8213 break; // (or we would have caught him before). Abort repetition-checking loop.
8215 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8216 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8218 details = "Xboard adjudication: repetition";
8220 } else // it must be XQ
8221 // Now check for perpetual chases
8222 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8223 hisPerpetual = PerpetualChase(k, forwardMostMove);
8224 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8225 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8226 static char resdet[MSG_SIZ];
8227 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8229 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8231 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8232 break; // Abort repetition-checking loop.
8234 // if neither of us is checking or chasing all the time, or both are, it is draw
8236 if(engineOpponent) {
8237 SendToProgram("force\n", engineOpponent); // suppress reply
8238 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8240 GameEnds( result, details, GE_XBOARD );
8243 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8244 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8248 /* Now we test for 50-move draws. Determine ply count */
8249 count = forwardMostMove;
8250 /* look for last irreversble move */
8251 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8253 /* if we hit starting position, add initial plies */
8254 if( count == backwardMostMove )
8255 count -= initialRulePlies;
8256 count = forwardMostMove - count;
8257 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8258 // adjust reversible move counter for checks in Xiangqi
8259 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8260 if(i < backwardMostMove) i = backwardMostMove;
8261 while(i <= forwardMostMove) {
8262 lastCheck = inCheck; // check evasion does not count
8263 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8264 if(inCheck || lastCheck) count--; // check does not count
8269 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8270 /* this is used to judge if draw claims are legal */
8271 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8272 if(engineOpponent) {
8273 SendToProgram("force\n", engineOpponent); // suppress reply
8274 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8276 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8280 /* if draw offer is pending, treat it as a draw claim
8281 * when draw condition present, to allow engines a way to
8282 * claim draws before making their move to avoid a race
8283 * condition occurring after their move
8285 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8287 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8288 p = "Draw claim: 50-move rule";
8289 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8290 p = "Draw claim: 3-fold repetition";
8291 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8292 p = "Draw claim: insufficient mating material";
8293 if( p != NULL && canAdjudicate) {
8294 if(engineOpponent) {
8295 SendToProgram("force\n", engineOpponent); // suppress reply
8296 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8298 GameEnds( GameIsDrawn, p, GE_XBOARD );
8303 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8304 if(engineOpponent) {
8305 SendToProgram("force\n", engineOpponent); // suppress reply
8306 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8308 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8314 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8315 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8316 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8321 int pieces[10], squares[10], cnt=0, r, f, res;
8323 static PPROBE_EGBB probeBB;
8324 if(!appData.testLegality) return 10;
8325 if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8326 if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8327 if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8328 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8329 ChessSquare piece = boards[forwardMostMove][r][f];
8330 int black = (piece >= BlackPawn);
8331 int type = piece - black*BlackPawn;
8332 if(piece == EmptySquare) continue;
8333 if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8334 if(type == WhiteKing) type = WhiteQueen + 1;
8335 type = egbbCode[type];
8336 squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8337 pieces[cnt] = type + black*6;
8338 if(++cnt > 5) return 11;
8340 pieces[cnt] = squares[cnt] = 0;
8342 if(loaded == 2) return 13; // loading failed before
8344 loaded = 2; // prepare for failure
8345 char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8348 if(!path) return 13; // no egbb installed
8349 strncpy(buf, path + 8, MSG_SIZ);
8350 if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8351 snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8352 lib = LoadLibrary(buf);
8353 if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8354 loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8355 probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8356 if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8357 p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8358 loaded = 1; // success!
8360 res = probeBB(forwardMostMove & 1, pieces, squares);
8361 return res > 0 ? 1 : res < 0 ? -1 : 0;
8365 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8366 { // [HGM] book: this routine intercepts moves to simulate book replies
8367 char *bookHit = NULL;
8369 if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8371 snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8372 SendToProgram(buf, cps);
8374 //first determine if the incoming move brings opponent into his book
8375 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8376 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8377 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8378 if(bookHit != NULL && !cps->bookSuspend) {
8379 // make sure opponent is not going to reply after receiving move to book position
8380 SendToProgram("force\n", cps);
8381 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8383 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8384 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8385 // now arrange restart after book miss
8387 // after a book hit we never send 'go', and the code after the call to this routine
8388 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8389 char buf[MSG_SIZ], *move = bookHit;
8391 int fromX, fromY, toX, toY;
8395 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8396 &fromX, &fromY, &toX, &toY, &promoChar)) {
8397 (void) CoordsToAlgebraic(boards[forwardMostMove],
8398 PosFlags(forwardMostMove),
8399 fromY, fromX, toY, toX, promoChar, move);
8401 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8405 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8406 SendToProgram(buf, cps);
8407 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8408 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8409 SendToProgram("go\n", cps);
8410 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8411 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8412 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8413 SendToProgram("go\n", cps);
8414 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8416 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8420 LoadError (char *errmess, ChessProgramState *cps)
8421 { // unloads engine and switches back to -ncp mode if it was first
8422 if(cps->initDone) return FALSE;
8423 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8424 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8427 appData.noChessProgram = TRUE;
8428 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8429 gameMode = BeginningOfGame; ModeHighlight();
8432 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8433 DisplayMessage("", ""); // erase waiting message
8434 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8439 ChessProgramState *savedState;
8441 DeferredBookMove (void)
8443 if(savedState->lastPing != savedState->lastPong)
8444 ScheduleDelayedEvent(DeferredBookMove, 10);
8446 HandleMachineMove(savedMessage, savedState);
8449 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8450 static ChessProgramState *stalledEngine;
8451 static char stashedInputMove[MSG_SIZ];
8454 HandleMachineMove (char *message, ChessProgramState *cps)
8456 static char firstLeg[20];
8457 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8458 char realname[MSG_SIZ];
8459 int fromX, fromY, toX, toY;
8461 char promoChar, roar;
8463 int machineWhite, oldError;
8466 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8467 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8468 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8469 DisplayError(_("Invalid pairing from pairing engine"), 0);
8472 pairingReceived = 1;
8474 return; // Skim the pairing messages here.
8477 oldError = cps->userError; cps->userError = 0;
8479 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8481 * Kludge to ignore BEL characters
8483 while (*message == '\007') message++;
8486 * [HGM] engine debug message: ignore lines starting with '#' character
8488 if(cps->debug && *message == '#') return;
8491 * Look for book output
8493 if (cps == &first && bookRequested) {
8494 if (message[0] == '\t' || message[0] == ' ') {
8495 /* Part of the book output is here; append it */
8496 strcat(bookOutput, message);
8497 strcat(bookOutput, " \n");
8499 } else if (bookOutput[0] != NULLCHAR) {
8500 /* All of book output has arrived; display it */
8501 char *p = bookOutput;
8502 while (*p != NULLCHAR) {
8503 if (*p == '\t') *p = ' ';
8506 DisplayInformation(bookOutput);
8507 bookRequested = FALSE;
8508 /* Fall through to parse the current output */
8513 * Look for machine move.
8515 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8516 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8518 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8519 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8520 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8521 stalledEngine = cps;
8522 if(appData.ponderNextMove) { // bring opponent out of ponder
8523 if(gameMode == TwoMachinesPlay) {
8524 if(cps->other->pause)
8525 PauseEngine(cps->other);
8527 SendToProgram("easy\n", cps->other);
8534 /* This method is only useful on engines that support ping */
8535 if (cps->lastPing != cps->lastPong) {
8536 if (gameMode == BeginningOfGame) {
8537 /* Extra move from before last new; ignore */
8538 if (appData.debugMode) {
8539 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8542 if (appData.debugMode) {
8543 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8544 cps->which, gameMode);
8547 SendToProgram("undo\n", cps);
8553 case BeginningOfGame:
8554 /* Extra move from before last reset; ignore */
8555 if (appData.debugMode) {
8556 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8563 /* Extra move after we tried to stop. The mode test is
8564 not a reliable way of detecting this problem, but it's
8565 the best we can do on engines that don't support ping.
8567 if (appData.debugMode) {
8568 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8569 cps->which, gameMode);
8571 SendToProgram("undo\n", cps);
8574 case MachinePlaysWhite:
8575 case IcsPlayingWhite:
8576 machineWhite = TRUE;
8579 case MachinePlaysBlack:
8580 case IcsPlayingBlack:
8581 machineWhite = FALSE;
8584 case TwoMachinesPlay:
8585 machineWhite = (cps->twoMachinesColor[0] == 'w');
8588 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8589 if (appData.debugMode) {
8591 "Ignoring move out of turn by %s, gameMode %d"
8592 ", forwardMost %d\n",
8593 cps->which, gameMode, forwardMostMove);
8598 if(cps->alphaRank) AlphaRank(machineMove, 4);
8600 // [HGM] lion: (some very limited) support for Alien protocol
8602 if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8603 safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8605 } else if(firstLeg[0]) { // there was a previous leg;
8606 // only support case where same piece makes two step (and don't even test that!)
8607 char buf[20], *p = machineMove+1, *q = buf+1, f;
8608 safeStrCpy(buf, machineMove, 20);
8609 while(isdigit(*q)) q++; // find start of to-square
8610 safeStrCpy(machineMove, firstLeg, 20);
8611 while(isdigit(*p)) p++;
8612 safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8613 sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8614 firstLeg[0] = NULLCHAR;
8617 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8618 &fromX, &fromY, &toX, &toY, &promoChar)) {
8619 /* Machine move could not be parsed; ignore it. */
8620 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8621 machineMove, _(cps->which));
8622 DisplayMoveError(buf1);
8623 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8624 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8625 if (gameMode == TwoMachinesPlay) {
8626 GameEnds(machineWhite ? BlackWins : WhiteWins,
8632 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8633 /* So we have to redo legality test with true e.p. status here, */
8634 /* to make sure an illegal e.p. capture does not slip through, */
8635 /* to cause a forfeit on a justified illegal-move complaint */
8636 /* of the opponent. */
8637 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8639 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8640 fromY, fromX, toY, toX, promoChar);
8641 if(moveType == IllegalMove) {
8642 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8643 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8644 GameEnds(machineWhite ? BlackWins : WhiteWins,
8647 } else if(!appData.fischerCastling)
8648 /* [HGM] Kludge to handle engines that send FRC-style castling
8649 when they shouldn't (like TSCP-Gothic) */
8651 case WhiteASideCastleFR:
8652 case BlackASideCastleFR:
8654 currentMoveString[2]++;
8656 case WhiteHSideCastleFR:
8657 case BlackHSideCastleFR:
8659 currentMoveString[2]--;
8661 default: ; // nothing to do, but suppresses warning of pedantic compilers
8664 hintRequested = FALSE;
8665 lastHint[0] = NULLCHAR;
8666 bookRequested = FALSE;
8667 /* Program may be pondering now */
8668 cps->maybeThinking = TRUE;
8669 if (cps->sendTime == 2) cps->sendTime = 1;
8670 if (cps->offeredDraw) cps->offeredDraw--;
8672 /* [AS] Save move info*/
8673 pvInfoList[ forwardMostMove ].score = programStats.score;
8674 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8675 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8677 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8679 /* Test suites abort the 'game' after one move */
8680 if(*appData.finger) {
8682 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8683 if(!f) f = fopen(appData.finger, "w");
8684 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8685 else { DisplayFatalError("Bad output file", errno, 0); return; }
8687 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8690 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8691 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8694 while( count < adjudicateLossPlies ) {
8695 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8698 score = -score; /* Flip score for winning side */
8701 if( score > adjudicateLossThreshold ) {
8708 if( count >= adjudicateLossPlies ) {
8709 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8711 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8712 "Xboard adjudication",
8719 if(Adjudicate(cps)) {
8720 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8721 return; // [HGM] adjudicate: for all automatic game ends
8725 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8727 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8728 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8730 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8732 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8734 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8735 char buf[3*MSG_SIZ];
8737 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8738 programStats.score / 100.,
8740 programStats.time / 100.,
8741 (unsigned int)programStats.nodes,
8742 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8743 programStats.movelist);
8745 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8750 /* [AS] Clear stats for next move */
8751 ClearProgramStats();
8752 thinkOutput[0] = NULLCHAR;
8753 hiddenThinkOutputState = 0;
8756 if (gameMode == TwoMachinesPlay) {
8757 /* [HGM] relaying draw offers moved to after reception of move */
8758 /* and interpreting offer as claim if it brings draw condition */
8759 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8760 SendToProgram("draw\n", cps->other);
8762 if (cps->other->sendTime) {
8763 SendTimeRemaining(cps->other,
8764 cps->other->twoMachinesColor[0] == 'w');
8766 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8767 if (firstMove && !bookHit) {
8769 if (cps->other->useColors) {
8770 SendToProgram(cps->other->twoMachinesColor, cps->other);
8772 SendToProgram("go\n", cps->other);
8774 cps->other->maybeThinking = TRUE;
8777 roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8779 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8781 if (!pausing && appData.ringBellAfterMoves) {
8782 if(!roar) RingBell();
8786 * Reenable menu items that were disabled while
8787 * machine was thinking
8789 if (gameMode != TwoMachinesPlay)
8790 SetUserThinkingEnables();
8792 // [HGM] book: after book hit opponent has received move and is now in force mode
8793 // force the book reply into it, and then fake that it outputted this move by jumping
8794 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8796 static char bookMove[MSG_SIZ]; // a bit generous?
8798 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8799 strcat(bookMove, bookHit);
8802 programStats.nodes = programStats.depth = programStats.time =
8803 programStats.score = programStats.got_only_move = 0;
8804 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8806 if(cps->lastPing != cps->lastPong) {
8807 savedMessage = message; // args for deferred call
8809 ScheduleDelayedEvent(DeferredBookMove, 10);
8818 /* Set special modes for chess engines. Later something general
8819 * could be added here; for now there is just one kludge feature,
8820 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8821 * when "xboard" is given as an interactive command.
8823 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8824 cps->useSigint = FALSE;
8825 cps->useSigterm = FALSE;
8827 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8828 ParseFeatures(message+8, cps);
8829 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8832 if (!strncmp(message, "setup ", 6) &&
8833 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8834 NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8835 ) { // [HGM] allow first engine to define opening position
8836 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8837 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8839 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8840 if(startedFromSetupPosition) return;
8841 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8843 while(message[s] && message[s++] != ' ');
8844 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8845 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8846 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8847 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
8848 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8849 if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8852 ParseFEN(boards[0], &dummy, message+s, FALSE);
8853 DrawPosition(TRUE, boards[0]);
8854 startedFromSetupPosition = TRUE;
8857 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8858 * want this, I was asked to put it in, and obliged.
8860 if (!strncmp(message, "setboard ", 9)) {
8861 Board initial_position;
8863 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8865 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8866 DisplayError(_("Bad FEN received from engine"), 0);
8870 CopyBoard(boards[0], initial_position);
8871 initialRulePlies = FENrulePlies;
8872 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8873 else gameMode = MachinePlaysBlack;
8874 DrawPosition(FALSE, boards[currentMove]);
8880 * Look for communication commands
8882 if (!strncmp(message, "telluser ", 9)) {
8883 if(message[9] == '\\' && message[10] == '\\')
8884 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8886 DisplayNote(message + 9);
8889 if (!strncmp(message, "tellusererror ", 14)) {
8891 if(message[14] == '\\' && message[15] == '\\')
8892 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8894 DisplayError(message + 14, 0);
8897 if (!strncmp(message, "tellopponent ", 13)) {
8898 if (appData.icsActive) {
8900 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8904 DisplayNote(message + 13);
8908 if (!strncmp(message, "tellothers ", 11)) {
8909 if (appData.icsActive) {
8911 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8914 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8917 if (!strncmp(message, "tellall ", 8)) {
8918 if (appData.icsActive) {
8920 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8924 DisplayNote(message + 8);
8928 if (strncmp(message, "warning", 7) == 0) {
8929 /* Undocumented feature, use tellusererror in new code */
8930 DisplayError(message, 0);
8933 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8934 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8935 strcat(realname, " query");
8936 AskQuestion(realname, buf2, buf1, cps->pr);
8939 /* Commands from the engine directly to ICS. We don't allow these to be
8940 * sent until we are logged on. Crafty kibitzes have been known to
8941 * interfere with the login process.
8944 if (!strncmp(message, "tellics ", 8)) {
8945 SendToICS(message + 8);
8949 if (!strncmp(message, "tellicsnoalias ", 15)) {
8950 SendToICS(ics_prefix);
8951 SendToICS(message + 15);
8955 /* The following are for backward compatibility only */
8956 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8957 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8958 SendToICS(ics_prefix);
8964 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8965 if(initPing == cps->lastPong) {
8966 if(gameInfo.variant == VariantUnknown) {
8967 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8968 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8969 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8975 if(!strncmp(message, "highlight ", 10)) {
8976 if(appData.testLegality && appData.markers) return;
8977 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8980 if(!strncmp(message, "click ", 6)) {
8981 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8982 if(appData.testLegality || !appData.oneClick) return;
8983 sscanf(message+6, "%c%d%c", &f, &y, &c);
8984 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8985 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8986 x = x*squareSize + (x+1)*lineGap + squareSize/2;
8987 y = y*squareSize + (y+1)*lineGap + squareSize/2;
8988 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8989 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8990 LeftClick(Release, lastLeftX, lastLeftY);
8991 controlKey = (c == ',');
8992 LeftClick(Press, x, y);
8993 LeftClick(Release, x, y);
8994 first.highlight = f;
8998 * If the move is illegal, cancel it and redraw the board.
8999 * Also deal with other error cases. Matching is rather loose
9000 * here to accommodate engines written before the spec.
9002 if (strncmp(message + 1, "llegal move", 11) == 0 ||
9003 strncmp(message, "Error", 5) == 0) {
9004 if (StrStr(message, "name") ||
9005 StrStr(message, "rating") || StrStr(message, "?") ||
9006 StrStr(message, "result") || StrStr(message, "board") ||
9007 StrStr(message, "bk") || StrStr(message, "computer") ||
9008 StrStr(message, "variant") || StrStr(message, "hint") ||
9009 StrStr(message, "random") || StrStr(message, "depth") ||
9010 StrStr(message, "accepted")) {
9013 if (StrStr(message, "protover")) {
9014 /* Program is responding to input, so it's apparently done
9015 initializing, and this error message indicates it is
9016 protocol version 1. So we don't need to wait any longer
9017 for it to initialize and send feature commands. */
9018 FeatureDone(cps, 1);
9019 cps->protocolVersion = 1;
9022 cps->maybeThinking = FALSE;
9024 if (StrStr(message, "draw")) {
9025 /* Program doesn't have "draw" command */
9026 cps->sendDrawOffers = 0;
9029 if (cps->sendTime != 1 &&
9030 (StrStr(message, "time") || StrStr(message, "otim"))) {
9031 /* Program apparently doesn't have "time" or "otim" command */
9035 if (StrStr(message, "analyze")) {
9036 cps->analysisSupport = FALSE;
9037 cps->analyzing = FALSE;
9038 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9039 EditGameEvent(); // [HGM] try to preserve loaded game
9040 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9041 DisplayError(buf2, 0);
9044 if (StrStr(message, "(no matching move)st")) {
9045 /* Special kludge for GNU Chess 4 only */
9046 cps->stKludge = TRUE;
9047 SendTimeControl(cps, movesPerSession, timeControl,
9048 timeIncrement, appData.searchDepth,
9052 if (StrStr(message, "(no matching move)sd")) {
9053 /* Special kludge for GNU Chess 4 only */
9054 cps->sdKludge = TRUE;
9055 SendTimeControl(cps, movesPerSession, timeControl,
9056 timeIncrement, appData.searchDepth,
9060 if (!StrStr(message, "llegal")) {
9063 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9064 gameMode == IcsIdle) return;
9065 if (forwardMostMove <= backwardMostMove) return;
9066 if (pausing) PauseEvent();
9067 if(appData.forceIllegal) {
9068 // [HGM] illegal: machine refused move; force position after move into it
9069 SendToProgram("force\n", cps);
9070 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9071 // we have a real problem now, as SendBoard will use the a2a3 kludge
9072 // when black is to move, while there might be nothing on a2 or black
9073 // might already have the move. So send the board as if white has the move.
9074 // But first we must change the stm of the engine, as it refused the last move
9075 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9076 if(WhiteOnMove(forwardMostMove)) {
9077 SendToProgram("a7a6\n", cps); // for the engine black still had the move
9078 SendBoard(cps, forwardMostMove); // kludgeless board
9080 SendToProgram("a2a3\n", cps); // for the engine white still had the move
9081 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9082 SendBoard(cps, forwardMostMove+1); // kludgeless board
9084 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9085 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9086 gameMode == TwoMachinesPlay)
9087 SendToProgram("go\n", cps);
9090 if (gameMode == PlayFromGameFile) {
9091 /* Stop reading this game file */
9092 gameMode = EditGame;
9095 /* [HGM] illegal-move claim should forfeit game when Xboard */
9096 /* only passes fully legal moves */
9097 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9098 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9099 "False illegal-move claim", GE_XBOARD );
9100 return; // do not take back move we tested as valid
9102 currentMove = forwardMostMove-1;
9103 DisplayMove(currentMove-1); /* before DisplayMoveError */
9104 SwitchClocks(forwardMostMove-1); // [HGM] race
9105 DisplayBothClocks();
9106 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9107 parseList[currentMove], _(cps->which));
9108 DisplayMoveError(buf1);
9109 DrawPosition(FALSE, boards[currentMove]);
9111 SetUserThinkingEnables();
9114 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9115 /* Program has a broken "time" command that
9116 outputs a string not ending in newline.
9122 * If chess program startup fails, exit with an error message.
9123 * Attempts to recover here are futile. [HGM] Well, we try anyway
9125 if ((StrStr(message, "unknown host") != NULL)
9126 || (StrStr(message, "No remote directory") != NULL)
9127 || (StrStr(message, "not found") != NULL)
9128 || (StrStr(message, "No such file") != NULL)
9129 || (StrStr(message, "can't alloc") != NULL)
9130 || (StrStr(message, "Permission denied") != NULL)) {
9132 cps->maybeThinking = FALSE;
9133 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9134 _(cps->which), cps->program, cps->host, message);
9135 RemoveInputSource(cps->isr);
9136 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9137 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9138 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9144 * Look for hint output
9146 if (sscanf(message, "Hint: %s", buf1) == 1) {
9147 if (cps == &first && hintRequested) {
9148 hintRequested = FALSE;
9149 if (ParseOneMove(buf1, forwardMostMove, &moveType,
9150 &fromX, &fromY, &toX, &toY, &promoChar)) {
9151 (void) CoordsToAlgebraic(boards[forwardMostMove],
9152 PosFlags(forwardMostMove),
9153 fromY, fromX, toY, toX, promoChar, buf1);
9154 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9155 DisplayInformation(buf2);
9157 /* Hint move could not be parsed!? */
9158 snprintf(buf2, sizeof(buf2),
9159 _("Illegal hint move \"%s\"\nfrom %s chess program"),
9160 buf1, _(cps->which));
9161 DisplayError(buf2, 0);
9164 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9170 * Ignore other messages if game is not in progress
9172 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9173 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9176 * look for win, lose, draw, or draw offer
9178 if (strncmp(message, "1-0", 3) == 0) {
9179 char *p, *q, *r = "";
9180 p = strchr(message, '{');
9188 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9190 } else if (strncmp(message, "0-1", 3) == 0) {
9191 char *p, *q, *r = "";
9192 p = strchr(message, '{');
9200 /* Kludge for Arasan 4.1 bug */
9201 if (strcmp(r, "Black resigns") == 0) {
9202 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9205 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9207 } else if (strncmp(message, "1/2", 3) == 0) {
9208 char *p, *q, *r = "";
9209 p = strchr(message, '{');
9218 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9221 } else if (strncmp(message, "White resign", 12) == 0) {
9222 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9224 } else if (strncmp(message, "Black resign", 12) == 0) {
9225 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9227 } else if (strncmp(message, "White matches", 13) == 0 ||
9228 strncmp(message, "Black matches", 13) == 0 ) {
9229 /* [HGM] ignore GNUShogi noises */
9231 } else if (strncmp(message, "White", 5) == 0 &&
9232 message[5] != '(' &&
9233 StrStr(message, "Black") == NULL) {
9234 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9236 } else if (strncmp(message, "Black", 5) == 0 &&
9237 message[5] != '(') {
9238 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9240 } else if (strcmp(message, "resign") == 0 ||
9241 strcmp(message, "computer resigns") == 0) {
9243 case MachinePlaysBlack:
9244 case IcsPlayingBlack:
9245 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9247 case MachinePlaysWhite:
9248 case IcsPlayingWhite:
9249 GameEnds(BlackWins, "White resigns", GE_ENGINE);
9251 case TwoMachinesPlay:
9252 if (cps->twoMachinesColor[0] == 'w')
9253 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9255 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9262 } else if (strncmp(message, "opponent mates", 14) == 0) {
9264 case MachinePlaysBlack:
9265 case IcsPlayingBlack:
9266 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9268 case MachinePlaysWhite:
9269 case IcsPlayingWhite:
9270 GameEnds(BlackWins, "Black mates", GE_ENGINE);
9272 case TwoMachinesPlay:
9273 if (cps->twoMachinesColor[0] == 'w')
9274 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9276 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9283 } else if (strncmp(message, "computer mates", 14) == 0) {
9285 case MachinePlaysBlack:
9286 case IcsPlayingBlack:
9287 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9289 case MachinePlaysWhite:
9290 case IcsPlayingWhite:
9291 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9293 case TwoMachinesPlay:
9294 if (cps->twoMachinesColor[0] == 'w')
9295 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9297 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9304 } else if (strncmp(message, "checkmate", 9) == 0) {
9305 if (WhiteOnMove(forwardMostMove)) {
9306 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9308 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9311 } else if (strstr(message, "Draw") != NULL ||
9312 strstr(message, "game is a draw") != NULL) {
9313 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9315 } else if (strstr(message, "offer") != NULL &&
9316 strstr(message, "draw") != NULL) {
9318 if (appData.zippyPlay && first.initDone) {
9319 /* Relay offer to ICS */
9320 SendToICS(ics_prefix);
9321 SendToICS("draw\n");
9324 cps->offeredDraw = 2; /* valid until this engine moves twice */
9325 if (gameMode == TwoMachinesPlay) {
9326 if (cps->other->offeredDraw) {
9327 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9328 /* [HGM] in two-machine mode we delay relaying draw offer */
9329 /* until after we also have move, to see if it is really claim */
9331 } else if (gameMode == MachinePlaysWhite ||
9332 gameMode == MachinePlaysBlack) {
9333 if (userOfferedDraw) {
9334 DisplayInformation(_("Machine accepts your draw offer"));
9335 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9337 DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9344 * Look for thinking output
9346 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9347 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9349 int plylev, mvleft, mvtot, curscore, time;
9350 char mvname[MOVE_LEN];
9354 int prefixHint = FALSE;
9355 mvname[0] = NULLCHAR;
9358 case MachinePlaysBlack:
9359 case IcsPlayingBlack:
9360 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9362 case MachinePlaysWhite:
9363 case IcsPlayingWhite:
9364 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9369 case IcsObserving: /* [DM] icsEngineAnalyze */
9370 if (!appData.icsEngineAnalyze) ignore = TRUE;
9372 case TwoMachinesPlay:
9373 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9383 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9385 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9386 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9388 if (plyext != ' ' && plyext != '\t') {
9392 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9393 if( cps->scoreIsAbsolute &&
9394 ( gameMode == MachinePlaysBlack ||
9395 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9396 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9397 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9398 !WhiteOnMove(currentMove)
9401 curscore = -curscore;
9404 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9406 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9409 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9410 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9411 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9412 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9413 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9414 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9418 /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9419 DisplayError(_("failed writing PV"), 0);
9422 tempStats.depth = plylev;
9423 tempStats.nodes = nodes;
9424 tempStats.time = time;
9425 tempStats.score = curscore;
9426 tempStats.got_only_move = 0;
9428 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9431 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9432 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9433 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9434 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9435 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9436 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9437 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9438 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9441 /* Buffer overflow protection */
9442 if (pv[0] != NULLCHAR) {
9443 if (strlen(pv) >= sizeof(tempStats.movelist)
9444 && appData.debugMode) {
9446 "PV is too long; using the first %u bytes.\n",
9447 (unsigned) sizeof(tempStats.movelist) - 1);
9450 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9452 sprintf(tempStats.movelist, " no PV\n");
9455 if (tempStats.seen_stat) {
9456 tempStats.ok_to_send = 1;
9459 if (strchr(tempStats.movelist, '(') != NULL) {
9460 tempStats.line_is_book = 1;
9461 tempStats.nr_moves = 0;
9462 tempStats.moves_left = 0;
9464 tempStats.line_is_book = 0;
9467 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9468 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9470 SendProgramStatsToFrontend( cps, &tempStats );
9473 [AS] Protect the thinkOutput buffer from overflow... this
9474 is only useful if buf1 hasn't overflowed first!
9476 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9478 (gameMode == TwoMachinesPlay ?
9479 ToUpper(cps->twoMachinesColor[0]) : ' '),
9480 ((double) curscore) / 100.0,
9481 prefixHint ? lastHint : "",
9482 prefixHint ? " " : "" );
9484 if( buf1[0] != NULLCHAR ) {
9485 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9487 if( strlen(pv) > max_len ) {
9488 if( appData.debugMode) {
9489 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9491 pv[max_len+1] = '\0';
9494 strcat( thinkOutput, pv);
9497 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9498 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9499 DisplayMove(currentMove - 1);
9503 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9504 /* crafty (9.25+) says "(only move) <move>"
9505 * if there is only 1 legal move
9507 sscanf(p, "(only move) %s", buf1);
9508 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9509 sprintf(programStats.movelist, "%s (only move)", buf1);
9510 programStats.depth = 1;
9511 programStats.nr_moves = 1;
9512 programStats.moves_left = 1;
9513 programStats.nodes = 1;
9514 programStats.time = 1;
9515 programStats.got_only_move = 1;
9517 /* Not really, but we also use this member to
9518 mean "line isn't going to change" (Crafty
9519 isn't searching, so stats won't change) */
9520 programStats.line_is_book = 1;
9522 SendProgramStatsToFrontend( cps, &programStats );
9524 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9525 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9526 DisplayMove(currentMove - 1);
9529 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9530 &time, &nodes, &plylev, &mvleft,
9531 &mvtot, mvname) >= 5) {
9532 /* The stat01: line is from Crafty (9.29+) in response
9533 to the "." command */
9534 programStats.seen_stat = 1;
9535 cps->maybeThinking = TRUE;
9537 if (programStats.got_only_move || !appData.periodicUpdates)
9540 programStats.depth = plylev;
9541 programStats.time = time;
9542 programStats.nodes = nodes;
9543 programStats.moves_left = mvleft;
9544 programStats.nr_moves = mvtot;
9545 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9546 programStats.ok_to_send = 1;
9547 programStats.movelist[0] = '\0';
9549 SendProgramStatsToFrontend( cps, &programStats );
9553 } else if (strncmp(message,"++",2) == 0) {
9554 /* Crafty 9.29+ outputs this */
9555 programStats.got_fail = 2;
9558 } else if (strncmp(message,"--",2) == 0) {
9559 /* Crafty 9.29+ outputs this */
9560 programStats.got_fail = 1;
9563 } else if (thinkOutput[0] != NULLCHAR &&
9564 strncmp(message, " ", 4) == 0) {
9565 unsigned message_len;
9568 while (*p && *p == ' ') p++;
9570 message_len = strlen( p );
9572 /* [AS] Avoid buffer overflow */
9573 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9574 strcat(thinkOutput, " ");
9575 strcat(thinkOutput, p);
9578 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9579 strcat(programStats.movelist, " ");
9580 strcat(programStats.movelist, p);
9583 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9584 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9585 DisplayMove(currentMove - 1);
9593 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9594 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9596 ChessProgramStats cpstats;
9598 if (plyext != ' ' && plyext != '\t') {
9602 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9603 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9604 curscore = -curscore;
9607 cpstats.depth = plylev;
9608 cpstats.nodes = nodes;
9609 cpstats.time = time;
9610 cpstats.score = curscore;
9611 cpstats.got_only_move = 0;
9612 cpstats.movelist[0] = '\0';
9614 if (buf1[0] != NULLCHAR) {
9615 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9618 cpstats.ok_to_send = 0;
9619 cpstats.line_is_book = 0;
9620 cpstats.nr_moves = 0;
9621 cpstats.moves_left = 0;
9623 SendProgramStatsToFrontend( cps, &cpstats );
9630 /* Parse a game score from the character string "game", and
9631 record it as the history of the current game. The game
9632 score is NOT assumed to start from the standard position.
9633 The display is not updated in any way.
9636 ParseGameHistory (char *game)
9639 int fromX, fromY, toX, toY, boardIndex;
9644 if (appData.debugMode)
9645 fprintf(debugFP, "Parsing game history: %s\n", game);
9647 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9648 gameInfo.site = StrSave(appData.icsHost);
9649 gameInfo.date = PGNDate();
9650 gameInfo.round = StrSave("-");
9652 /* Parse out names of players */
9653 while (*game == ' ') game++;
9655 while (*game != ' ') *p++ = *game++;
9657 gameInfo.white = StrSave(buf);
9658 while (*game == ' ') game++;
9660 while (*game != ' ' && *game != '\n') *p++ = *game++;
9662 gameInfo.black = StrSave(buf);
9665 boardIndex = blackPlaysFirst ? 1 : 0;
9668 yyboardindex = boardIndex;
9669 moveType = (ChessMove) Myylex();
9671 case IllegalMove: /* maybe suicide chess, etc. */
9672 if (appData.debugMode) {
9673 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9674 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9675 setbuf(debugFP, NULL);
9677 case WhitePromotion:
9678 case BlackPromotion:
9679 case WhiteNonPromotion:
9680 case BlackNonPromotion:
9683 case WhiteCapturesEnPassant:
9684 case BlackCapturesEnPassant:
9685 case WhiteKingSideCastle:
9686 case WhiteQueenSideCastle:
9687 case BlackKingSideCastle:
9688 case BlackQueenSideCastle:
9689 case WhiteKingSideCastleWild:
9690 case WhiteQueenSideCastleWild:
9691 case BlackKingSideCastleWild:
9692 case BlackQueenSideCastleWild:
9694 case WhiteHSideCastleFR:
9695 case WhiteASideCastleFR:
9696 case BlackHSideCastleFR:
9697 case BlackASideCastleFR:
9699 fromX = currentMoveString[0] - AAA;
9700 fromY = currentMoveString[1] - ONE;
9701 toX = currentMoveString[2] - AAA;
9702 toY = currentMoveString[3] - ONE;
9703 promoChar = currentMoveString[4];
9707 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9708 fromX = moveType == WhiteDrop ?
9709 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9710 (int) CharToPiece(ToLower(currentMoveString[0]));
9712 toX = currentMoveString[2] - AAA;
9713 toY = currentMoveString[3] - ONE;
9714 promoChar = NULLCHAR;
9718 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9719 if (appData.debugMode) {
9720 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9721 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9722 setbuf(debugFP, NULL);
9724 DisplayError(buf, 0);
9726 case ImpossibleMove:
9728 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9729 if (appData.debugMode) {
9730 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9731 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9732 setbuf(debugFP, NULL);
9734 DisplayError(buf, 0);
9737 if (boardIndex < backwardMostMove) {
9738 /* Oops, gap. How did that happen? */
9739 DisplayError(_("Gap in move list"), 0);
9742 backwardMostMove = blackPlaysFirst ? 1 : 0;
9743 if (boardIndex > forwardMostMove) {
9744 forwardMostMove = boardIndex;
9748 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9749 strcat(parseList[boardIndex-1], " ");
9750 strcat(parseList[boardIndex-1], yy_text);
9762 case GameUnfinished:
9763 if (gameMode == IcsExamining) {
9764 if (boardIndex < backwardMostMove) {
9765 /* Oops, gap. How did that happen? */
9768 backwardMostMove = blackPlaysFirst ? 1 : 0;
9771 gameInfo.result = moveType;
9772 p = strchr(yy_text, '{');
9773 if (p == NULL) p = strchr(yy_text, '(');
9776 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9778 q = strchr(p, *p == '{' ? '}' : ')');
9779 if (q != NULL) *q = NULLCHAR;
9782 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9783 gameInfo.resultDetails = StrSave(p);
9786 if (boardIndex >= forwardMostMove &&
9787 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9788 backwardMostMove = blackPlaysFirst ? 1 : 0;
9791 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9792 fromY, fromX, toY, toX, promoChar,
9793 parseList[boardIndex]);
9794 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9795 /* currentMoveString is set as a side-effect of yylex */
9796 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9797 strcat(moveList[boardIndex], "\n");
9799 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9800 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9806 if(!IS_SHOGI(gameInfo.variant))
9807 strcat(parseList[boardIndex - 1], "+");
9811 strcat(parseList[boardIndex - 1], "#");
9818 /* Apply a move to the given board */
9820 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9822 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9823 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9825 /* [HGM] compute & store e.p. status and castling rights for new position */
9826 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9828 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9829 oldEP = (signed char)board[EP_STATUS];
9830 board[EP_STATUS] = EP_NONE;
9832 if (fromY == DROP_RANK) {
9834 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9835 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9838 piece = board[toY][toX] = (ChessSquare) fromX;
9840 // ChessSquare victim;
9843 if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9844 // victim = board[killY][killX],
9845 board[killY][killX] = EmptySquare,
9846 board[EP_STATUS] = EP_CAPTURE;
9848 if( board[toY][toX] != EmptySquare ) {
9849 board[EP_STATUS] = EP_CAPTURE;
9850 if( (fromX != toX || fromY != toY) && // not igui!
9851 (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9852 captured == BlackLion && board[fromY][fromX] != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules
9853 board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9857 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9858 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9859 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9861 if( board[fromY][fromX] == WhitePawn ) {
9862 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9863 board[EP_STATUS] = EP_PAWN_MOVE;
9865 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9866 gameInfo.variant != VariantBerolina || toX < fromX)
9867 board[EP_STATUS] = toX | berolina;
9868 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9869 gameInfo.variant != VariantBerolina || toX > fromX)
9870 board[EP_STATUS] = toX;
9873 if( board[fromY][fromX] == BlackPawn ) {
9874 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9875 board[EP_STATUS] = EP_PAWN_MOVE;
9876 if( toY-fromY== -2) {
9877 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9878 gameInfo.variant != VariantBerolina || toX < fromX)
9879 board[EP_STATUS] = toX | berolina;
9880 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9881 gameInfo.variant != VariantBerolina || toX > fromX)
9882 board[EP_STATUS] = toX;
9886 for(i=0; i<nrCastlingRights; i++) {
9887 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9888 board[CASTLING][i] == toX && castlingRank[i] == toY
9889 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9892 if(gameInfo.variant == VariantSChess) { // update virginity
9893 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9894 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9895 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
9896 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
9899 if (fromX == toX && fromY == toY) return;
9901 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9902 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9903 if(gameInfo.variant == VariantKnightmate)
9904 king += (int) WhiteUnicorn - (int) WhiteKing;
9906 /* Code added by Tord: */
9907 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9908 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9909 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9910 board[fromY][fromX] = EmptySquare;
9911 board[toY][toX] = EmptySquare;
9912 if((toX > fromX) != (piece == WhiteRook)) {
9913 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9915 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9917 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9918 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9919 board[fromY][fromX] = EmptySquare;
9920 board[toY][toX] = EmptySquare;
9921 if((toX > fromX) != (piece == BlackRook)) {
9922 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9924 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9926 /* End of code added by Tord */
9928 } else if (board[fromY][fromX] == king
9929 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9930 && toY == fromY && toX > fromX+1) {
9931 board[fromY][fromX] = EmptySquare;
9932 board[toY][toX] = king;
9933 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9934 board[fromY][BOARD_RGHT-1] = EmptySquare;
9935 } else if (board[fromY][fromX] == king
9936 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9937 && toY == fromY && toX < fromX-1) {
9938 board[fromY][fromX] = EmptySquare;
9939 board[toY][toX] = king;
9940 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9941 board[fromY][BOARD_LEFT] = EmptySquare;
9942 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9943 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9944 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9946 /* white pawn promotion */
9947 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9948 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9949 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9950 board[fromY][fromX] = EmptySquare;
9951 } else if ((fromY >= BOARD_HEIGHT>>1)
9952 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9954 && gameInfo.variant != VariantXiangqi
9955 && gameInfo.variant != VariantBerolina
9956 && (board[fromY][fromX] == WhitePawn)
9957 && (board[toY][toX] == EmptySquare)) {
9958 board[fromY][fromX] = EmptySquare;
9959 board[toY][toX] = WhitePawn;
9960 captured = board[toY - 1][toX];
9961 board[toY - 1][toX] = EmptySquare;
9962 } else if ((fromY == BOARD_HEIGHT-4)
9964 && gameInfo.variant == VariantBerolina
9965 && (board[fromY][fromX] == WhitePawn)
9966 && (board[toY][toX] == EmptySquare)) {
9967 board[fromY][fromX] = EmptySquare;
9968 board[toY][toX] = WhitePawn;
9969 if(oldEP & EP_BEROLIN_A) {
9970 captured = board[fromY][fromX-1];
9971 board[fromY][fromX-1] = EmptySquare;
9972 }else{ captured = board[fromY][fromX+1];
9973 board[fromY][fromX+1] = EmptySquare;
9975 } else if (board[fromY][fromX] == king
9976 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9977 && toY == fromY && toX > fromX+1) {
9978 board[fromY][fromX] = EmptySquare;
9979 board[toY][toX] = king;
9980 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9981 board[fromY][BOARD_RGHT-1] = EmptySquare;
9982 } else if (board[fromY][fromX] == king
9983 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9984 && toY == fromY && toX < fromX-1) {
9985 board[fromY][fromX] = EmptySquare;
9986 board[toY][toX] = king;
9987 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9988 board[fromY][BOARD_LEFT] = EmptySquare;
9989 } else if (fromY == 7 && fromX == 3
9990 && board[fromY][fromX] == BlackKing
9991 && toY == 7 && toX == 5) {
9992 board[fromY][fromX] = EmptySquare;
9993 board[toY][toX] = BlackKing;
9994 board[fromY][7] = EmptySquare;
9995 board[toY][4] = BlackRook;
9996 } else if (fromY == 7 && fromX == 3
9997 && board[fromY][fromX] == BlackKing
9998 && toY == 7 && toX == 1) {
9999 board[fromY][fromX] = EmptySquare;
10000 board[toY][toX] = BlackKing;
10001 board[fromY][0] = EmptySquare;
10002 board[toY][2] = BlackRook;
10003 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10004 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10005 && toY < promoRank && promoChar
10007 /* black pawn promotion */
10008 board[toY][toX] = CharToPiece(ToLower(promoChar));
10009 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10010 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10011 board[fromY][fromX] = EmptySquare;
10012 } else if ((fromY < BOARD_HEIGHT>>1)
10013 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
10015 && gameInfo.variant != VariantXiangqi
10016 && gameInfo.variant != VariantBerolina
10017 && (board[fromY][fromX] == BlackPawn)
10018 && (board[toY][toX] == EmptySquare)) {
10019 board[fromY][fromX] = EmptySquare;
10020 board[toY][toX] = BlackPawn;
10021 captured = board[toY + 1][toX];
10022 board[toY + 1][toX] = EmptySquare;
10023 } else if ((fromY == 3)
10025 && gameInfo.variant == VariantBerolina
10026 && (board[fromY][fromX] == BlackPawn)
10027 && (board[toY][toX] == EmptySquare)) {
10028 board[fromY][fromX] = EmptySquare;
10029 board[toY][toX] = BlackPawn;
10030 if(oldEP & EP_BEROLIN_A) {
10031 captured = board[fromY][fromX-1];
10032 board[fromY][fromX-1] = EmptySquare;
10033 }else{ captured = board[fromY][fromX+1];
10034 board[fromY][fromX+1] = EmptySquare;
10037 ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10038 board[fromY][fromX] = EmptySquare;
10039 board[toY][toX] = piece;
10043 if (gameInfo.holdingsWidth != 0) {
10045 /* !!A lot more code needs to be written to support holdings */
10046 /* [HGM] OK, so I have written it. Holdings are stored in the */
10047 /* penultimate board files, so they are automaticlly stored */
10048 /* in the game history. */
10049 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10050 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10051 /* Delete from holdings, by decreasing count */
10052 /* and erasing image if necessary */
10053 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10054 if(p < (int) BlackPawn) { /* white drop */
10055 p -= (int)WhitePawn;
10056 p = PieceToNumber((ChessSquare)p);
10057 if(p >= gameInfo.holdingsSize) p = 0;
10058 if(--board[p][BOARD_WIDTH-2] <= 0)
10059 board[p][BOARD_WIDTH-1] = EmptySquare;
10060 if((int)board[p][BOARD_WIDTH-2] < 0)
10061 board[p][BOARD_WIDTH-2] = 0;
10062 } else { /* black drop */
10063 p -= (int)BlackPawn;
10064 p = PieceToNumber((ChessSquare)p);
10065 if(p >= gameInfo.holdingsSize) p = 0;
10066 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10067 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10068 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10069 board[BOARD_HEIGHT-1-p][1] = 0;
10072 if (captured != EmptySquare && gameInfo.holdingsSize > 0
10073 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
10074 /* [HGM] holdings: Add to holdings, if holdings exist */
10075 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10076 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10077 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10079 p = (int) captured;
10080 if (p >= (int) BlackPawn) {
10081 p -= (int)BlackPawn;
10082 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10083 /* in Shogi restore piece to its original first */
10084 captured = (ChessSquare) (DEMOTED captured);
10087 p = PieceToNumber((ChessSquare)p);
10088 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10089 board[p][BOARD_WIDTH-2]++;
10090 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10092 p -= (int)WhitePawn;
10093 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10094 captured = (ChessSquare) (DEMOTED captured);
10097 p = PieceToNumber((ChessSquare)p);
10098 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10099 board[BOARD_HEIGHT-1-p][1]++;
10100 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10103 } else if (gameInfo.variant == VariantAtomic) {
10104 if (captured != EmptySquare) {
10106 for (y = toY-1; y <= toY+1; y++) {
10107 for (x = toX-1; x <= toX+1; x++) {
10108 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10109 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10110 board[y][x] = EmptySquare;
10114 board[toY][toX] = EmptySquare;
10118 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10119 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10121 if(promoChar == '+') {
10122 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10123 board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10124 if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10125 board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10126 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10127 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10128 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10129 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10130 board[toY][toX] = newPiece;
10132 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10133 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10134 // [HGM] superchess: take promotion piece out of holdings
10135 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10136 if((int)piece < (int)BlackPawn) { // determine stm from piece color
10137 if(!--board[k][BOARD_WIDTH-2])
10138 board[k][BOARD_WIDTH-1] = EmptySquare;
10140 if(!--board[BOARD_HEIGHT-1-k][1])
10141 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10146 /* Updates forwardMostMove */
10148 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10150 int x = toX, y = toY;
10151 char *s = parseList[forwardMostMove];
10152 ChessSquare p = boards[forwardMostMove][toY][toX];
10153 // forwardMostMove++; // [HGM] bare: moved downstream
10155 if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10156 (void) CoordsToAlgebraic(boards[forwardMostMove],
10157 PosFlags(forwardMostMove),
10158 fromY, fromX, y, x, promoChar,
10160 if(killX >= 0 && killY >= 0)
10161 sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10163 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10164 int timeLeft; static int lastLoadFlag=0; int king, piece;
10165 piece = boards[forwardMostMove][fromY][fromX];
10166 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10167 if(gameInfo.variant == VariantKnightmate)
10168 king += (int) WhiteUnicorn - (int) WhiteKing;
10169 if(forwardMostMove == 0) {
10170 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10171 fprintf(serverMoves, "%s;", UserName());
10172 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10173 fprintf(serverMoves, "%s;", second.tidy);
10174 fprintf(serverMoves, "%s;", first.tidy);
10175 if(gameMode == MachinePlaysWhite)
10176 fprintf(serverMoves, "%s;", UserName());
10177 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10178 fprintf(serverMoves, "%s;", second.tidy);
10179 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10180 lastLoadFlag = loadFlag;
10182 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10183 // print castling suffix
10184 if( toY == fromY && piece == king ) {
10186 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10188 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10191 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10192 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
10193 boards[forwardMostMove][toY][toX] == EmptySquare
10194 && fromX != toX && fromY != toY)
10195 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10196 // promotion suffix
10197 if(promoChar != NULLCHAR) {
10198 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10199 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10200 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10201 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10204 char buf[MOVE_LEN*2], *p; int len;
10205 fprintf(serverMoves, "/%d/%d",
10206 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10207 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10208 else timeLeft = blackTimeRemaining/1000;
10209 fprintf(serverMoves, "/%d", timeLeft);
10210 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10211 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10212 if(p = strchr(buf, '=')) *p = NULLCHAR;
10213 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10214 fprintf(serverMoves, "/%s", buf);
10216 fflush(serverMoves);
10219 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10220 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10223 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10224 if (commentList[forwardMostMove+1] != NULL) {
10225 free(commentList[forwardMostMove+1]);
10226 commentList[forwardMostMove+1] = NULL;
10228 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10229 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10230 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10231 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10232 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10233 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10234 adjustedClock = FALSE;
10235 gameInfo.result = GameUnfinished;
10236 if (gameInfo.resultDetails != NULL) {
10237 free(gameInfo.resultDetails);
10238 gameInfo.resultDetails = NULL;
10240 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10241 moveList[forwardMostMove - 1]);
10242 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10248 if(!IS_SHOGI(gameInfo.variant))
10249 strcat(parseList[forwardMostMove - 1], "+");
10253 strcat(parseList[forwardMostMove - 1], "#");
10258 /* Updates currentMove if not pausing */
10260 ShowMove (int fromX, int fromY, int toX, int toY)
10262 int instant = (gameMode == PlayFromGameFile) ?
10263 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10264 if(appData.noGUI) return;
10265 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10267 if (forwardMostMove == currentMove + 1) {
10268 AnimateMove(boards[forwardMostMove - 1],
10269 fromX, fromY, toX, toY);
10272 currentMove = forwardMostMove;
10275 killX = killY = -1; // [HGM] lion: used up
10277 if (instant) return;
10279 DisplayMove(currentMove - 1);
10280 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10281 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10282 SetHighlights(fromX, fromY, toX, toY);
10285 DrawPosition(FALSE, boards[currentMove]);
10286 DisplayBothClocks();
10287 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10291 SendEgtPath (ChessProgramState *cps)
10292 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10293 char buf[MSG_SIZ], name[MSG_SIZ], *p;
10295 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10298 char c, *q = name+1, *r, *s;
10300 name[0] = ','; // extract next format name from feature and copy with prefixed ','
10301 while(*p && *p != ',') *q++ = *p++;
10302 *q++ = ':'; *q = 0;
10303 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10304 strcmp(name, ",nalimov:") == 0 ) {
10305 // take nalimov path from the menu-changeable option first, if it is defined
10306 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10307 SendToProgram(buf,cps); // send egtbpath command for nalimov
10309 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10310 (s = StrStr(appData.egtFormats, name)) != NULL) {
10311 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10312 s = r = StrStr(s, ":") + 1; // beginning of path info
10313 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10314 c = *r; *r = 0; // temporarily null-terminate path info
10315 *--q = 0; // strip of trailig ':' from name
10316 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10318 SendToProgram(buf,cps); // send egtbpath command for this format
10320 if(*p == ',') p++; // read away comma to position for next format name
10325 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10327 int width = 8, height = 8, holdings = 0; // most common sizes
10328 if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10329 // correct the deviations default for each variant
10330 if( v == VariantXiangqi ) width = 9, height = 10;
10331 if( v == VariantShogi ) width = 9, height = 9, holdings = 7;
10332 if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10333 if( v == VariantCapablanca || v == VariantCapaRandom ||
10334 v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10336 if( v == VariantCourier ) width = 12;
10337 if( v == VariantSuper ) holdings = 8;
10338 if( v == VariantGreat ) width = 10, holdings = 8;
10339 if( v == VariantSChess ) holdings = 7;
10340 if( v == VariantGrand ) width = 10, height = 10, holdings = 7;
10341 if( v == VariantChuChess) width = 10, height = 10;
10342 if( v == VariantChu ) width = 12, height = 12;
10343 return boardWidth >= 0 && boardWidth != width || // -1 is default,
10344 boardHeight >= 0 && boardHeight != height || // and thus by definition OK
10345 holdingsSize >= 0 && holdingsSize != holdings;
10348 char variantError[MSG_SIZ];
10351 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10352 { // returns error message (recognizable by upper-case) if engine does not support the variant
10353 char *p, *variant = VariantName(v);
10354 static char b[MSG_SIZ];
10355 if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10356 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10357 holdingsSize, variant); // cook up sized variant name
10358 /* [HGM] varsize: try first if this deviant size variant is specifically known */
10359 if(StrStr(list, b) == NULL) {
10360 // specific sized variant not known, check if general sizing allowed
10361 if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10362 snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10363 boardWidth, boardHeight, holdingsSize, engine);
10366 /* [HGM] here we really should compare with the maximum supported board size */
10368 } else snprintf(b, MSG_SIZ,"%s", variant);
10369 if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10370 p = StrStr(list, b);
10371 while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10373 // occurs not at all in list, or only as sub-string
10374 snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10375 if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10376 int l = strlen(variantError);
10378 while(p != list && p[-1] != ',') p--;
10379 q = strchr(p, ',');
10380 if(q) *q = NULLCHAR;
10381 snprintf(variantError + l, MSG_SIZ - l, _(", but %s is"), p);
10390 InitChessProgram (ChessProgramState *cps, int setup)
10391 /* setup needed to setup FRC opening position */
10393 char buf[MSG_SIZ], *b;
10394 if (appData.noChessProgram) return;
10395 hintRequested = FALSE;
10396 bookRequested = FALSE;
10398 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10399 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10400 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10401 if(cps->memSize) { /* [HGM] memory */
10402 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10403 SendToProgram(buf, cps);
10405 SendEgtPath(cps); /* [HGM] EGT */
10406 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10407 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10408 SendToProgram(buf, cps);
10411 SendToProgram(cps->initString, cps);
10412 if (gameInfo.variant != VariantNormal &&
10413 gameInfo.variant != VariantLoadable
10414 /* [HGM] also send variant if board size non-standard */
10415 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10417 b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10418 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10420 DisplayFatalError(variantError, 0, 1);
10424 snprintf(buf, MSG_SIZ, "variant %s\n", b);
10425 SendToProgram(buf, cps);
10427 currentlyInitializedVariant = gameInfo.variant;
10429 /* [HGM] send opening position in FRC to first engine */
10431 SendToProgram("force\n", cps);
10433 /* engine is now in force mode! Set flag to wake it up after first move. */
10434 setboardSpoiledMachineBlack = 1;
10437 if (cps->sendICS) {
10438 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10439 SendToProgram(buf, cps);
10441 cps->maybeThinking = FALSE;
10442 cps->offeredDraw = 0;
10443 if (!appData.icsActive) {
10444 SendTimeControl(cps, movesPerSession, timeControl,
10445 timeIncrement, appData.searchDepth,
10448 if (appData.showThinking
10449 // [HGM] thinking: four options require thinking output to be sent
10450 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10452 SendToProgram("post\n", cps);
10454 SendToProgram("hard\n", cps);
10455 if (!appData.ponderNextMove) {
10456 /* Warning: "easy" is a toggle in GNU Chess, so don't send
10457 it without being sure what state we are in first. "hard"
10458 is not a toggle, so that one is OK.
10460 SendToProgram("easy\n", cps);
10462 if (cps->usePing) {
10463 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10464 SendToProgram(buf, cps);
10466 cps->initDone = TRUE;
10467 ClearEngineOutputPane(cps == &second);
10472 ResendOptions (ChessProgramState *cps)
10473 { // send the stored value of the options
10476 Option *opt = cps->option;
10477 for(i=0; i<cps->nrOptions; i++, opt++) {
10478 switch(opt->type) {
10482 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10485 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10488 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10494 SendToProgram(buf, cps);
10499 StartChessProgram (ChessProgramState *cps)
10504 if (appData.noChessProgram) return;
10505 cps->initDone = FALSE;
10507 if (strcmp(cps->host, "localhost") == 0) {
10508 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10509 } else if (*appData.remoteShell == NULLCHAR) {
10510 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10512 if (*appData.remoteUser == NULLCHAR) {
10513 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10516 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10517 cps->host, appData.remoteUser, cps->program);
10519 err = StartChildProcess(buf, "", &cps->pr);
10523 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10524 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10525 if(cps != &first) return;
10526 appData.noChessProgram = TRUE;
10529 // DisplayFatalError(buf, err, 1);
10530 // cps->pr = NoProc;
10531 // cps->isr = NULL;
10535 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10536 if (cps->protocolVersion > 1) {
10537 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10538 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10539 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10540 cps->comboCnt = 0; // and values of combo boxes
10542 SendToProgram(buf, cps);
10543 if(cps->reload) ResendOptions(cps);
10545 SendToProgram("xboard\n", cps);
10550 TwoMachinesEventIfReady P((void))
10552 static int curMess = 0;
10553 if (first.lastPing != first.lastPong) {
10554 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10555 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10558 if (second.lastPing != second.lastPong) {
10559 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10560 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10563 DisplayMessage("", ""); curMess = 0;
10564 TwoMachinesEvent();
10568 MakeName (char *template)
10572 static char buf[MSG_SIZ];
10576 clock = time((time_t *)NULL);
10577 tm = localtime(&clock);
10579 while(*p++ = *template++) if(p[-1] == '%') {
10580 switch(*template++) {
10581 case 0: *p = 0; return buf;
10582 case 'Y': i = tm->tm_year+1900; break;
10583 case 'y': i = tm->tm_year-100; break;
10584 case 'M': i = tm->tm_mon+1; break;
10585 case 'd': i = tm->tm_mday; break;
10586 case 'h': i = tm->tm_hour; break;
10587 case 'm': i = tm->tm_min; break;
10588 case 's': i = tm->tm_sec; break;
10591 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10597 CountPlayers (char *p)
10600 while(p = strchr(p, '\n')) p++, n++; // count participants
10605 WriteTourneyFile (char *results, FILE *f)
10606 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10607 if(f == NULL) f = fopen(appData.tourneyFile, "w");
10608 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10609 // create a file with tournament description
10610 fprintf(f, "-participants {%s}\n", appData.participants);
10611 fprintf(f, "-seedBase %d\n", appData.seedBase);
10612 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10613 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10614 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10615 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10616 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10617 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10618 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10619 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10620 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10621 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10622 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10623 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10624 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10625 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10626 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10627 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10628 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10629 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10630 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10631 fprintf(f, "-smpCores %d\n", appData.smpCores);
10633 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10635 fprintf(f, "-mps %d\n", appData.movesPerSession);
10636 fprintf(f, "-tc %s\n", appData.timeControl);
10637 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10639 fprintf(f, "-results \"%s\"\n", results);
10644 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10647 Substitute (char *participants, int expunge)
10649 int i, changed, changes=0, nPlayers=0;
10650 char *p, *q, *r, buf[MSG_SIZ];
10651 if(participants == NULL) return;
10652 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10653 r = p = participants; q = appData.participants;
10654 while(*p && *p == *q) {
10655 if(*p == '\n') r = p+1, nPlayers++;
10658 if(*p) { // difference
10659 while(*p && *p++ != '\n');
10660 while(*q && *q++ != '\n');
10661 changed = nPlayers;
10662 changes = 1 + (strcmp(p, q) != 0);
10664 if(changes == 1) { // a single engine mnemonic was changed
10665 q = r; while(*q) nPlayers += (*q++ == '\n');
10666 p = buf; while(*r && (*p = *r++) != '\n') p++;
10668 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10669 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10670 if(mnemonic[i]) { // The substitute is valid
10672 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10673 flock(fileno(f), LOCK_EX);
10674 ParseArgsFromFile(f);
10675 fseek(f, 0, SEEK_SET);
10676 FREE(appData.participants); appData.participants = participants;
10677 if(expunge) { // erase results of replaced engine
10678 int len = strlen(appData.results), w, b, dummy;
10679 for(i=0; i<len; i++) {
10680 Pairing(i, nPlayers, &w, &b, &dummy);
10681 if((w == changed || b == changed) && appData.results[i] == '*') {
10682 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10687 for(i=0; i<len; i++) {
10688 Pairing(i, nPlayers, &w, &b, &dummy);
10689 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10692 WriteTourneyFile(appData.results, f);
10693 fclose(f); // release lock
10696 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10698 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10699 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10700 free(participants);
10705 CheckPlayers (char *participants)
10708 char buf[MSG_SIZ], *p;
10709 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10710 while(p = strchr(participants, '\n')) {
10712 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10714 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10716 DisplayError(buf, 0);
10720 participants = p + 1;
10726 CreateTourney (char *name)
10729 if(matchMode && strcmp(name, appData.tourneyFile)) {
10730 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10732 if(name[0] == NULLCHAR) {
10733 if(appData.participants[0])
10734 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10737 f = fopen(name, "r");
10738 if(f) { // file exists
10739 ASSIGN(appData.tourneyFile, name);
10740 ParseArgsFromFile(f); // parse it
10742 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10743 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10744 DisplayError(_("Not enough participants"), 0);
10747 if(CheckPlayers(appData.participants)) return 0;
10748 ASSIGN(appData.tourneyFile, name);
10749 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10750 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10753 appData.noChessProgram = FALSE;
10754 appData.clockMode = TRUE;
10760 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10762 char buf[MSG_SIZ], *p, *q;
10763 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10764 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10765 skip = !all && group[0]; // if group requested, we start in skip mode
10766 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10767 p = names; q = buf; header = 0;
10768 while(*p && *p != '\n') *q++ = *p++;
10770 if(*p == '\n') p++;
10771 if(buf[0] == '#') {
10772 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10773 depth++; // we must be entering a new group
10774 if(all) continue; // suppress printing group headers when complete list requested
10776 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10778 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10779 if(engineList[i]) free(engineList[i]);
10780 engineList[i] = strdup(buf);
10781 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10782 if(engineMnemonic[i]) free(engineMnemonic[i]);
10783 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10785 sscanf(q + 8, "%s", buf + strlen(buf));
10788 engineMnemonic[i] = strdup(buf);
10791 engineList[i] = engineMnemonic[i] = NULL;
10795 // following implemented as macro to avoid type limitations
10796 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10799 SwapEngines (int n)
10800 { // swap settings for first engine and other engine (so far only some selected options)
10805 SWAP(chessProgram, p)
10807 SWAP(hasOwnBookUCI, h)
10808 SWAP(protocolVersion, h)
10810 SWAP(scoreIsAbsolute, h)
10815 SWAP(engOptions, p)
10816 SWAP(engInitString, p)
10817 SWAP(computerString, p)
10819 SWAP(fenOverride, p)
10821 SWAP(accumulateTC, h)
10827 GetEngineLine (char *s, int n)
10831 extern char *icsNames;
10832 if(!s || !*s) return 0;
10833 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10834 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10835 if(!mnemonic[i]) return 0;
10836 if(n == 11) return 1; // just testing if there was a match
10837 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10838 if(n == 1) SwapEngines(n);
10839 ParseArgsFromString(buf);
10840 if(n == 1) SwapEngines(n);
10841 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10842 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10843 ParseArgsFromString(buf);
10849 SetPlayer (int player, char *p)
10850 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10852 char buf[MSG_SIZ], *engineName;
10853 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10854 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10855 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10857 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10858 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10859 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10860 ParseArgsFromString(buf);
10861 } else { // no engine with this nickname is installed!
10862 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10863 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10864 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10866 DisplayError(buf, 0);
10873 char *recentEngines;
10876 RecentEngineEvent (int nr)
10879 // SwapEngines(1); // bump first to second
10880 // ReplaceEngine(&second, 1); // and load it there
10881 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10882 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10883 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10884 ReplaceEngine(&first, 0);
10885 FloatToFront(&appData.recentEngineList, command[n]);
10890 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10891 { // determine players from game number
10892 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10894 if(appData.tourneyType == 0) {
10895 roundsPerCycle = (nPlayers - 1) | 1;
10896 pairingsPerRound = nPlayers / 2;
10897 } else if(appData.tourneyType > 0) {
10898 roundsPerCycle = nPlayers - appData.tourneyType;
10899 pairingsPerRound = appData.tourneyType;
10901 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10902 gamesPerCycle = gamesPerRound * roundsPerCycle;
10903 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10904 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10905 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10906 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10907 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10908 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10910 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10911 if(appData.roundSync) *syncInterval = gamesPerRound;
10913 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10915 if(appData.tourneyType == 0) {
10916 if(curPairing == (nPlayers-1)/2 ) {
10917 *whitePlayer = curRound;
10918 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10920 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10921 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10922 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10923 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10925 } else if(appData.tourneyType > 1) {
10926 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10927 *whitePlayer = curRound + appData.tourneyType;
10928 } else if(appData.tourneyType > 0) {
10929 *whitePlayer = curPairing;
10930 *blackPlayer = curRound + appData.tourneyType;
10933 // take care of white/black alternation per round.
10934 // For cycles and games this is already taken care of by default, derived from matchGame!
10935 return curRound & 1;
10939 NextTourneyGame (int nr, int *swapColors)
10940 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10942 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10944 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10945 tf = fopen(appData.tourneyFile, "r");
10946 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10947 ParseArgsFromFile(tf); fclose(tf);
10948 InitTimeControls(); // TC might be altered from tourney file
10950 nPlayers = CountPlayers(appData.participants); // count participants
10951 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10952 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10955 p = q = appData.results;
10956 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10957 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10958 DisplayMessage(_("Waiting for other game(s)"),"");
10959 waitingForGame = TRUE;
10960 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10963 waitingForGame = FALSE;
10966 if(appData.tourneyType < 0) {
10967 if(nr>=0 && !pairingReceived) {
10969 if(pairing.pr == NoProc) {
10970 if(!appData.pairingEngine[0]) {
10971 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10974 StartChessProgram(&pairing); // starts the pairing engine
10976 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10977 SendToProgram(buf, &pairing);
10978 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10979 SendToProgram(buf, &pairing);
10980 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10982 pairingReceived = 0; // ... so we continue here
10984 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10985 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10986 matchGame = 1; roundNr = nr / syncInterval + 1;
10989 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10991 // redefine engines, engine dir, etc.
10992 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10993 if(first.pr == NoProc) {
10994 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10995 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10997 if(second.pr == NoProc) {
10999 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11000 SwapEngines(1); // and make that valid for second engine by swapping
11001 InitEngine(&second, 1);
11003 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
11004 UpdateLogos(FALSE); // leave display to ModeHiglight()
11010 { // performs game initialization that does not invoke engines, and then tries to start the game
11011 int res, firstWhite, swapColors = 0;
11012 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11013 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
11015 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11016 if(strcmp(buf, currentDebugFile)) { // name has changed
11017 FILE *f = fopen(buf, "w");
11018 if(f) { // if opening the new file failed, just keep using the old one
11019 ASSIGN(currentDebugFile, buf);
11023 if(appData.serverFileName) {
11024 if(serverFP) fclose(serverFP);
11025 serverFP = fopen(appData.serverFileName, "w");
11026 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11027 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11031 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11032 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11033 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
11034 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11035 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11036 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11037 Reset(FALSE, first.pr != NoProc);
11038 res = LoadGameOrPosition(matchGame); // setup game
11039 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11040 if(!res) return; // abort when bad game/pos file
11041 TwoMachinesEvent();
11045 UserAdjudicationEvent (int result)
11047 ChessMove gameResult = GameIsDrawn;
11050 gameResult = WhiteWins;
11052 else if( result < 0 ) {
11053 gameResult = BlackWins;
11056 if( gameMode == TwoMachinesPlay ) {
11057 GameEnds( gameResult, "User adjudication", GE_XBOARD );
11062 // [HGM] save: calculate checksum of game to make games easily identifiable
11064 StringCheckSum (char *s)
11067 if(s==NULL) return 0;
11068 while(*s) i = i*259 + *s++;
11076 for(i=backwardMostMove; i<forwardMostMove; i++) {
11077 sum += pvInfoList[i].depth;
11078 sum += StringCheckSum(parseList[i]);
11079 sum += StringCheckSum(commentList[i]);
11082 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11083 return sum + StringCheckSum(commentList[i]);
11084 } // end of save patch
11087 GameEnds (ChessMove result, char *resultDetails, int whosays)
11089 GameMode nextGameMode;
11091 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11093 if(endingGame) return; /* [HGM] crash: forbid recursion */
11095 if(twoBoards) { // [HGM] dual: switch back to one board
11096 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11097 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11099 if (appData.debugMode) {
11100 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11101 result, resultDetails ? resultDetails : "(null)", whosays);
11104 fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11106 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11108 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11109 /* If we are playing on ICS, the server decides when the
11110 game is over, but the engine can offer to draw, claim
11114 if (appData.zippyPlay && first.initDone) {
11115 if (result == GameIsDrawn) {
11116 /* In case draw still needs to be claimed */
11117 SendToICS(ics_prefix);
11118 SendToICS("draw\n");
11119 } else if (StrCaseStr(resultDetails, "resign")) {
11120 SendToICS(ics_prefix);
11121 SendToICS("resign\n");
11125 endingGame = 0; /* [HGM] crash */
11129 /* If we're loading the game from a file, stop */
11130 if (whosays == GE_FILE) {
11131 (void) StopLoadGameTimer();
11135 /* Cancel draw offers */
11136 first.offeredDraw = second.offeredDraw = 0;
11138 /* If this is an ICS game, only ICS can really say it's done;
11139 if not, anyone can. */
11140 isIcsGame = (gameMode == IcsPlayingWhite ||
11141 gameMode == IcsPlayingBlack ||
11142 gameMode == IcsObserving ||
11143 gameMode == IcsExamining);
11145 if (!isIcsGame || whosays == GE_ICS) {
11146 /* OK -- not an ICS game, or ICS said it was done */
11148 if (!isIcsGame && !appData.noChessProgram)
11149 SetUserThinkingEnables();
11151 /* [HGM] if a machine claims the game end we verify this claim */
11152 if(gameMode == TwoMachinesPlay && appData.testClaims) {
11153 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11155 ChessMove trueResult = (ChessMove) -1;
11157 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
11158 first.twoMachinesColor[0] :
11159 second.twoMachinesColor[0] ;
11161 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11162 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11163 /* [HGM] verify: engine mate claims accepted if they were flagged */
11164 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11166 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11167 /* [HGM] verify: engine mate claims accepted if they were flagged */
11168 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11170 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11171 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11174 // now verify win claims, but not in drop games, as we don't understand those yet
11175 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11176 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11177 (result == WhiteWins && claimer == 'w' ||
11178 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
11179 if (appData.debugMode) {
11180 fprintf(debugFP, "result=%d sp=%d move=%d\n",
11181 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11183 if(result != trueResult) {
11184 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11185 result = claimer == 'w' ? BlackWins : WhiteWins;
11186 resultDetails = buf;
11189 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11190 && (forwardMostMove <= backwardMostMove ||
11191 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11192 (claimer=='b')==(forwardMostMove&1))
11194 /* [HGM] verify: draws that were not flagged are false claims */
11195 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11196 result = claimer == 'w' ? BlackWins : WhiteWins;
11197 resultDetails = buf;
11199 /* (Claiming a loss is accepted no questions asked!) */
11200 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11201 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11202 result = GameUnfinished;
11203 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11205 /* [HGM] bare: don't allow bare King to win */
11206 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11207 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11208 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11209 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11210 && result != GameIsDrawn)
11211 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11212 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11213 int p = (signed char)boards[forwardMostMove][i][j] - color;
11214 if(p >= 0 && p <= (int)WhiteKing) k++;
11216 if (appData.debugMode) {
11217 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11218 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11221 result = GameIsDrawn;
11222 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11223 resultDetails = buf;
11229 if(serverMoves != NULL && !loadFlag) { char c = '=';
11230 if(result==WhiteWins) c = '+';
11231 if(result==BlackWins) c = '-';
11232 if(resultDetails != NULL)
11233 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11235 if (resultDetails != NULL) {
11236 gameInfo.result = result;
11237 gameInfo.resultDetails = StrSave(resultDetails);
11239 /* display last move only if game was not loaded from file */
11240 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11241 DisplayMove(currentMove - 1);
11243 if (forwardMostMove != 0) {
11244 if (gameMode != PlayFromGameFile && gameMode != EditGame
11245 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11247 if (*appData.saveGameFile != NULLCHAR) {
11248 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11249 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11251 SaveGameToFile(appData.saveGameFile, TRUE);
11252 } else if (appData.autoSaveGames) {
11253 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11255 if (*appData.savePositionFile != NULLCHAR) {
11256 SavePositionToFile(appData.savePositionFile);
11258 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11262 /* Tell program how game ended in case it is learning */
11263 /* [HGM] Moved this to after saving the PGN, just in case */
11264 /* engine died and we got here through time loss. In that */
11265 /* case we will get a fatal error writing the pipe, which */
11266 /* would otherwise lose us the PGN. */
11267 /* [HGM] crash: not needed anymore, but doesn't hurt; */
11268 /* output during GameEnds should never be fatal anymore */
11269 if (gameMode == MachinePlaysWhite ||
11270 gameMode == MachinePlaysBlack ||
11271 gameMode == TwoMachinesPlay ||
11272 gameMode == IcsPlayingWhite ||
11273 gameMode == IcsPlayingBlack ||
11274 gameMode == BeginningOfGame) {
11276 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11278 if (first.pr != NoProc) {
11279 SendToProgram(buf, &first);
11281 if (second.pr != NoProc &&
11282 gameMode == TwoMachinesPlay) {
11283 SendToProgram(buf, &second);
11288 if (appData.icsActive) {
11289 if (appData.quietPlay &&
11290 (gameMode == IcsPlayingWhite ||
11291 gameMode == IcsPlayingBlack)) {
11292 SendToICS(ics_prefix);
11293 SendToICS("set shout 1\n");
11295 nextGameMode = IcsIdle;
11296 ics_user_moved = FALSE;
11297 /* clean up premove. It's ugly when the game has ended and the
11298 * premove highlights are still on the board.
11301 gotPremove = FALSE;
11302 ClearPremoveHighlights();
11303 DrawPosition(FALSE, boards[currentMove]);
11305 if (whosays == GE_ICS) {
11308 if (gameMode == IcsPlayingWhite)
11310 else if(gameMode == IcsPlayingBlack)
11311 PlayIcsLossSound();
11314 if (gameMode == IcsPlayingBlack)
11316 else if(gameMode == IcsPlayingWhite)
11317 PlayIcsLossSound();
11320 PlayIcsDrawSound();
11323 PlayIcsUnfinishedSound();
11326 if(appData.quitNext) { ExitEvent(0); return; }
11327 } else if (gameMode == EditGame ||
11328 gameMode == PlayFromGameFile ||
11329 gameMode == AnalyzeMode ||
11330 gameMode == AnalyzeFile) {
11331 nextGameMode = gameMode;
11333 nextGameMode = EndOfGame;
11338 nextGameMode = gameMode;
11341 if (appData.noChessProgram) {
11342 gameMode = nextGameMode;
11344 endingGame = 0; /* [HGM] crash */
11349 /* Put first chess program into idle state */
11350 if (first.pr != NoProc &&
11351 (gameMode == MachinePlaysWhite ||
11352 gameMode == MachinePlaysBlack ||
11353 gameMode == TwoMachinesPlay ||
11354 gameMode == IcsPlayingWhite ||
11355 gameMode == IcsPlayingBlack ||
11356 gameMode == BeginningOfGame)) {
11357 SendToProgram("force\n", &first);
11358 if (first.usePing) {
11360 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11361 SendToProgram(buf, &first);
11364 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11365 /* Kill off first chess program */
11366 if (first.isr != NULL)
11367 RemoveInputSource(first.isr);
11370 if (first.pr != NoProc) {
11372 DoSleep( appData.delayBeforeQuit );
11373 SendToProgram("quit\n", &first);
11374 DestroyChildProcess(first.pr, 4 + first.useSigterm);
11375 first.reload = TRUE;
11379 if (second.reuse) {
11380 /* Put second chess program into idle state */
11381 if (second.pr != NoProc &&
11382 gameMode == TwoMachinesPlay) {
11383 SendToProgram("force\n", &second);
11384 if (second.usePing) {
11386 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11387 SendToProgram(buf, &second);
11390 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11391 /* Kill off second chess program */
11392 if (second.isr != NULL)
11393 RemoveInputSource(second.isr);
11396 if (second.pr != NoProc) {
11397 DoSleep( appData.delayBeforeQuit );
11398 SendToProgram("quit\n", &second);
11399 DestroyChildProcess(second.pr, 4 + second.useSigterm);
11400 second.reload = TRUE;
11402 second.pr = NoProc;
11405 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11406 char resChar = '=';
11410 if (first.twoMachinesColor[0] == 'w') {
11413 second.matchWins++;
11418 if (first.twoMachinesColor[0] == 'b') {
11421 second.matchWins++;
11424 case GameUnfinished:
11430 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11431 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11432 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11433 ReserveGame(nextGame, resChar); // sets nextGame
11434 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11435 else ranking = strdup("busy"); //suppress popup when aborted but not finished
11436 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11438 if (nextGame <= appData.matchGames && !abortMatch) {
11439 gameMode = nextGameMode;
11440 matchGame = nextGame; // this will be overruled in tourney mode!
11441 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11442 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11443 endingGame = 0; /* [HGM] crash */
11446 gameMode = nextGameMode;
11447 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11448 first.tidy, second.tidy,
11449 first.matchWins, second.matchWins,
11450 appData.matchGames - (first.matchWins + second.matchWins));
11451 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11452 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11453 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11454 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11455 first.twoMachinesColor = "black\n";
11456 second.twoMachinesColor = "white\n";
11458 first.twoMachinesColor = "white\n";
11459 second.twoMachinesColor = "black\n";
11463 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11464 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11466 gameMode = nextGameMode;
11468 endingGame = 0; /* [HGM] crash */
11469 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11470 if(matchMode == TRUE) { // match through command line: exit with or without popup
11472 ToNrEvent(forwardMostMove);
11473 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11475 } else DisplayFatalError(buf, 0, 0);
11476 } else { // match through menu; just stop, with or without popup
11477 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11480 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11481 } else DisplayNote(buf);
11483 if(ranking) free(ranking);
11487 /* Assumes program was just initialized (initString sent).
11488 Leaves program in force mode. */
11490 FeedMovesToProgram (ChessProgramState *cps, int upto)
11494 if (appData.debugMode)
11495 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11496 startedFromSetupPosition ? "position and " : "",
11497 backwardMostMove, upto, cps->which);
11498 if(currentlyInitializedVariant != gameInfo.variant) {
11500 // [HGM] variantswitch: make engine aware of new variant
11501 if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11502 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11503 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11504 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11505 SendToProgram(buf, cps);
11506 currentlyInitializedVariant = gameInfo.variant;
11508 SendToProgram("force\n", cps);
11509 if (startedFromSetupPosition) {
11510 SendBoard(cps, backwardMostMove);
11511 if (appData.debugMode) {
11512 fprintf(debugFP, "feedMoves\n");
11515 for (i = backwardMostMove; i < upto; i++) {
11516 SendMoveToProgram(i, cps);
11522 ResurrectChessProgram ()
11524 /* The chess program may have exited.
11525 If so, restart it and feed it all the moves made so far. */
11526 static int doInit = 0;
11528 if (appData.noChessProgram) return 1;
11530 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11531 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11532 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11533 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11535 if (first.pr != NoProc) return 1;
11536 StartChessProgram(&first);
11538 InitChessProgram(&first, FALSE);
11539 FeedMovesToProgram(&first, currentMove);
11541 if (!first.sendTime) {
11542 /* can't tell gnuchess what its clock should read,
11543 so we bow to its notion. */
11545 timeRemaining[0][currentMove] = whiteTimeRemaining;
11546 timeRemaining[1][currentMove] = blackTimeRemaining;
11549 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11550 appData.icsEngineAnalyze) && first.analysisSupport) {
11551 SendToProgram("analyze\n", &first);
11552 first.analyzing = TRUE;
11558 * Button procedures
11561 Reset (int redraw, int init)
11565 if (appData.debugMode) {
11566 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11567 redraw, init, gameMode);
11569 CleanupTail(); // [HGM] vari: delete any stored variations
11570 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11571 pausing = pauseExamInvalid = FALSE;
11572 startedFromSetupPosition = blackPlaysFirst = FALSE;
11574 whiteFlag = blackFlag = FALSE;
11575 userOfferedDraw = FALSE;
11576 hintRequested = bookRequested = FALSE;
11577 first.maybeThinking = FALSE;
11578 second.maybeThinking = FALSE;
11579 first.bookSuspend = FALSE; // [HGM] book
11580 second.bookSuspend = FALSE;
11581 thinkOutput[0] = NULLCHAR;
11582 lastHint[0] = NULLCHAR;
11583 ClearGameInfo(&gameInfo);
11584 gameInfo.variant = StringToVariant(appData.variant);
11585 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11586 ics_user_moved = ics_clock_paused = FALSE;
11587 ics_getting_history = H_FALSE;
11589 white_holding[0] = black_holding[0] = NULLCHAR;
11590 ClearProgramStats();
11591 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11595 flipView = appData.flipView;
11596 ClearPremoveHighlights();
11597 gotPremove = FALSE;
11598 alarmSounded = FALSE;
11599 killX = killY = -1; // [HGM] lion
11601 GameEnds(EndOfFile, NULL, GE_PLAYER);
11602 if(appData.serverMovesName != NULL) {
11603 /* [HGM] prepare to make moves file for broadcasting */
11604 clock_t t = clock();
11605 if(serverMoves != NULL) fclose(serverMoves);
11606 serverMoves = fopen(appData.serverMovesName, "r");
11607 if(serverMoves != NULL) {
11608 fclose(serverMoves);
11609 /* delay 15 sec before overwriting, so all clients can see end */
11610 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11612 serverMoves = fopen(appData.serverMovesName, "w");
11616 gameMode = BeginningOfGame;
11618 if(appData.icsActive) gameInfo.variant = VariantNormal;
11619 currentMove = forwardMostMove = backwardMostMove = 0;
11620 MarkTargetSquares(1);
11621 InitPosition(redraw);
11622 for (i = 0; i < MAX_MOVES; i++) {
11623 if (commentList[i] != NULL) {
11624 free(commentList[i]);
11625 commentList[i] = NULL;
11629 timeRemaining[0][0] = whiteTimeRemaining;
11630 timeRemaining[1][0] = blackTimeRemaining;
11632 if (first.pr == NoProc) {
11633 StartChessProgram(&first);
11636 InitChessProgram(&first, startedFromSetupPosition);
11639 DisplayMessage("", "");
11640 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11641 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11642 ClearMap(); // [HGM] exclude: invalidate map
11646 AutoPlayGameLoop ()
11649 if (!AutoPlayOneMove())
11651 if (matchMode || appData.timeDelay == 0)
11653 if (appData.timeDelay < 0)
11655 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11663 ReloadGame(1); // next game
11669 int fromX, fromY, toX, toY;
11671 if (appData.debugMode) {
11672 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11675 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11678 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11679 pvInfoList[currentMove].depth = programStats.depth;
11680 pvInfoList[currentMove].score = programStats.score;
11681 pvInfoList[currentMove].time = 0;
11682 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11683 else { // append analysis of final position as comment
11685 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11686 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11688 programStats.depth = 0;
11691 if (currentMove >= forwardMostMove) {
11692 if(gameMode == AnalyzeFile) {
11693 if(appData.loadGameIndex == -1) {
11694 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11695 ScheduleDelayedEvent(AnalyzeNextGame, 10);
11697 ExitAnalyzeMode(); SendToProgram("force\n", &first);
11700 // gameMode = EndOfGame;
11701 // ModeHighlight();
11703 /* [AS] Clear current move marker at the end of a game */
11704 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11709 toX = moveList[currentMove][2] - AAA;
11710 toY = moveList[currentMove][3] - ONE;
11712 if (moveList[currentMove][1] == '@') {
11713 if (appData.highlightLastMove) {
11714 SetHighlights(-1, -1, toX, toY);
11717 fromX = moveList[currentMove][0] - AAA;
11718 fromY = moveList[currentMove][1] - ONE;
11720 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11722 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11724 if (appData.highlightLastMove) {
11725 SetHighlights(fromX, fromY, toX, toY);
11728 DisplayMove(currentMove);
11729 SendMoveToProgram(currentMove++, &first);
11730 DisplayBothClocks();
11731 DrawPosition(FALSE, boards[currentMove]);
11732 // [HGM] PV info: always display, routine tests if empty
11733 DisplayComment(currentMove - 1, commentList[currentMove]);
11739 LoadGameOneMove (ChessMove readAhead)
11741 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11742 char promoChar = NULLCHAR;
11743 ChessMove moveType;
11744 char move[MSG_SIZ];
11747 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11748 gameMode != AnalyzeMode && gameMode != Training) {
11753 yyboardindex = forwardMostMove;
11754 if (readAhead != EndOfFile) {
11755 moveType = readAhead;
11757 if (gameFileFP == NULL)
11759 moveType = (ChessMove) Myylex();
11763 switch (moveType) {
11765 if (appData.debugMode)
11766 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11769 /* append the comment but don't display it */
11770 AppendComment(currentMove, p, FALSE);
11773 case WhiteCapturesEnPassant:
11774 case BlackCapturesEnPassant:
11775 case WhitePromotion:
11776 case BlackPromotion:
11777 case WhiteNonPromotion:
11778 case BlackNonPromotion:
11781 case WhiteKingSideCastle:
11782 case WhiteQueenSideCastle:
11783 case BlackKingSideCastle:
11784 case BlackQueenSideCastle:
11785 case WhiteKingSideCastleWild:
11786 case WhiteQueenSideCastleWild:
11787 case BlackKingSideCastleWild:
11788 case BlackQueenSideCastleWild:
11790 case WhiteHSideCastleFR:
11791 case WhiteASideCastleFR:
11792 case BlackHSideCastleFR:
11793 case BlackASideCastleFR:
11795 if (appData.debugMode)
11796 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11797 fromX = currentMoveString[0] - AAA;
11798 fromY = currentMoveString[1] - ONE;
11799 toX = currentMoveString[2] - AAA;
11800 toY = currentMoveString[3] - ONE;
11801 promoChar = currentMoveString[4];
11802 if(promoChar == ';') promoChar = NULLCHAR;
11807 if (appData.debugMode)
11808 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11809 fromX = moveType == WhiteDrop ?
11810 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11811 (int) CharToPiece(ToLower(currentMoveString[0]));
11813 toX = currentMoveString[2] - AAA;
11814 toY = currentMoveString[3] - ONE;
11820 case GameUnfinished:
11821 if (appData.debugMode)
11822 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11823 p = strchr(yy_text, '{');
11824 if (p == NULL) p = strchr(yy_text, '(');
11827 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11829 q = strchr(p, *p == '{' ? '}' : ')');
11830 if (q != NULL) *q = NULLCHAR;
11833 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11834 GameEnds(moveType, p, GE_FILE);
11836 if (cmailMsgLoaded) {
11838 flipView = WhiteOnMove(currentMove);
11839 if (moveType == GameUnfinished) flipView = !flipView;
11840 if (appData.debugMode)
11841 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11846 if (appData.debugMode)
11847 fprintf(debugFP, "Parser hit end of file\n");
11848 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11854 if (WhiteOnMove(currentMove)) {
11855 GameEnds(BlackWins, "Black mates", GE_FILE);
11857 GameEnds(WhiteWins, "White mates", GE_FILE);
11861 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11867 case MoveNumberOne:
11868 if (lastLoadGameStart == GNUChessGame) {
11869 /* GNUChessGames have numbers, but they aren't move numbers */
11870 if (appData.debugMode)
11871 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11872 yy_text, (int) moveType);
11873 return LoadGameOneMove(EndOfFile); /* tail recursion */
11875 /* else fall thru */
11880 /* Reached start of next game in file */
11881 if (appData.debugMode)
11882 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11883 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11889 if (WhiteOnMove(currentMove)) {
11890 GameEnds(BlackWins, "Black mates", GE_FILE);
11892 GameEnds(WhiteWins, "White mates", GE_FILE);
11896 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11902 case PositionDiagram: /* should not happen; ignore */
11903 case ElapsedTime: /* ignore */
11904 case NAG: /* ignore */
11905 if (appData.debugMode)
11906 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11907 yy_text, (int) moveType);
11908 return LoadGameOneMove(EndOfFile); /* tail recursion */
11911 if (appData.testLegality) {
11912 if (appData.debugMode)
11913 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11914 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11915 (forwardMostMove / 2) + 1,
11916 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11917 DisplayError(move, 0);
11920 if (appData.debugMode)
11921 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11922 yy_text, currentMoveString);
11923 fromX = currentMoveString[0] - AAA;
11924 fromY = currentMoveString[1] - ONE;
11925 toX = currentMoveString[2] - AAA;
11926 toY = currentMoveString[3] - ONE;
11927 promoChar = currentMoveString[4];
11931 case AmbiguousMove:
11932 if (appData.debugMode)
11933 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11934 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11935 (forwardMostMove / 2) + 1,
11936 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11937 DisplayError(move, 0);
11942 case ImpossibleMove:
11943 if (appData.debugMode)
11944 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11945 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11946 (forwardMostMove / 2) + 1,
11947 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11948 DisplayError(move, 0);
11954 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11955 DrawPosition(FALSE, boards[currentMove]);
11956 DisplayBothClocks();
11957 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11958 DisplayComment(currentMove - 1, commentList[currentMove]);
11960 (void) StopLoadGameTimer();
11962 cmailOldMove = forwardMostMove;
11965 /* currentMoveString is set as a side-effect of yylex */
11967 thinkOutput[0] = NULLCHAR;
11968 MakeMove(fromX, fromY, toX, toY, promoChar);
11969 killX = killY = -1; // [HGM] lion: used up
11970 currentMove = forwardMostMove;
11975 /* Load the nth game from the given file */
11977 LoadGameFromFile (char *filename, int n, char *title, int useList)
11982 if (strcmp(filename, "-") == 0) {
11986 f = fopen(filename, "rb");
11988 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11989 DisplayError(buf, errno);
11993 if (fseek(f, 0, 0) == -1) {
11994 /* f is not seekable; probably a pipe */
11997 if (useList && n == 0) {
11998 int error = GameListBuild(f);
12000 DisplayError(_("Cannot build game list"), error);
12001 } else if (!ListEmpty(&gameList) &&
12002 ((ListGame *) gameList.tailPred)->number > 1) {
12003 GameListPopUp(f, title);
12010 return LoadGame(f, n, title, FALSE);
12015 MakeRegisteredMove ()
12017 int fromX, fromY, toX, toY;
12019 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12020 switch (cmailMoveType[lastLoadGameNumber - 1]) {
12023 if (appData.debugMode)
12024 fprintf(debugFP, "Restoring %s for game %d\n",
12025 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12027 thinkOutput[0] = NULLCHAR;
12028 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12029 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12030 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12031 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12032 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12033 promoChar = cmailMove[lastLoadGameNumber - 1][4];
12034 MakeMove(fromX, fromY, toX, toY, promoChar);
12035 ShowMove(fromX, fromY, toX, toY);
12037 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12044 if (WhiteOnMove(currentMove)) {
12045 GameEnds(BlackWins, "Black mates", GE_PLAYER);
12047 GameEnds(WhiteWins, "White mates", GE_PLAYER);
12052 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12059 if (WhiteOnMove(currentMove)) {
12060 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12062 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12067 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12078 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12080 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12084 if (gameNumber > nCmailGames) {
12085 DisplayError(_("No more games in this message"), 0);
12088 if (f == lastLoadGameFP) {
12089 int offset = gameNumber - lastLoadGameNumber;
12091 cmailMsg[0] = NULLCHAR;
12092 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12093 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12094 nCmailMovesRegistered--;
12096 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12097 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12098 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12101 if (! RegisterMove()) return FALSE;
12105 retVal = LoadGame(f, gameNumber, title, useList);
12107 /* Make move registered during previous look at this game, if any */
12108 MakeRegisteredMove();
12110 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12111 commentList[currentMove]
12112 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12113 DisplayComment(currentMove - 1, commentList[currentMove]);
12119 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12121 ReloadGame (int offset)
12123 int gameNumber = lastLoadGameNumber + offset;
12124 if (lastLoadGameFP == NULL) {
12125 DisplayError(_("No game has been loaded yet"), 0);
12128 if (gameNumber <= 0) {
12129 DisplayError(_("Can't back up any further"), 0);
12132 if (cmailMsgLoaded) {
12133 return CmailLoadGame(lastLoadGameFP, gameNumber,
12134 lastLoadGameTitle, lastLoadGameUseList);
12136 return LoadGame(lastLoadGameFP, gameNumber,
12137 lastLoadGameTitle, lastLoadGameUseList);
12141 int keys[EmptySquare+1];
12144 PositionMatches (Board b1, Board b2)
12147 switch(appData.searchMode) {
12148 case 1: return CompareWithRights(b1, b2);
12150 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12151 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12155 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12156 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12157 sum += keys[b1[r][f]] - keys[b2[r][f]];
12161 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12162 sum += keys[b1[r][f]] - keys[b2[r][f]];
12174 int pieceList[256], quickBoard[256];
12175 ChessSquare pieceType[256] = { EmptySquare };
12176 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12177 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12178 int soughtTotal, turn;
12179 Boolean epOK, flipSearch;
12182 unsigned char piece, to;
12185 #define DSIZE (250000)
12187 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12188 Move *moveDatabase = initialSpace;
12189 unsigned int movePtr, dataSize = DSIZE;
12192 MakePieceList (Board board, int *counts)
12194 int r, f, n=Q_PROMO, total=0;
12195 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12196 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12197 int sq = f + (r<<4);
12198 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12199 quickBoard[sq] = ++n;
12201 pieceType[n] = board[r][f];
12202 counts[board[r][f]]++;
12203 if(board[r][f] == WhiteKing) pieceList[1] = n; else
12204 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12208 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12213 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12215 int sq = fromX + (fromY<<4);
12216 int piece = quickBoard[sq], rook;
12217 quickBoard[sq] = 0;
12218 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12219 if(piece == pieceList[1] && fromY == toY) {
12220 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12221 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12222 moveDatabase[movePtr++].piece = Q_WCASTL;
12223 quickBoard[sq] = piece;
12224 piece = quickBoard[from]; quickBoard[from] = 0;
12225 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12226 } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12227 quickBoard[sq] = 0; // remove Rook
12228 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12229 moveDatabase[movePtr++].piece = Q_WCASTL;
12230 quickBoard[sq] = pieceList[1]; // put King
12232 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12235 if(piece == pieceList[2] && fromY == toY) {
12236 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12237 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12238 moveDatabase[movePtr++].piece = Q_BCASTL;
12239 quickBoard[sq] = piece;
12240 piece = quickBoard[from]; quickBoard[from] = 0;
12241 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12242 } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12243 quickBoard[sq] = 0; // remove Rook
12244 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12245 moveDatabase[movePtr++].piece = Q_BCASTL;
12246 quickBoard[sq] = pieceList[2]; // put King
12248 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12251 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12252 quickBoard[(fromY<<4)+toX] = 0;
12253 moveDatabase[movePtr].piece = Q_EP;
12254 moveDatabase[movePtr++].to = (fromY<<4)+toX;
12255 moveDatabase[movePtr].to = sq;
12257 if(promoPiece != pieceType[piece]) {
12258 moveDatabase[movePtr++].piece = Q_PROMO;
12259 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12261 moveDatabase[movePtr].piece = piece;
12262 quickBoard[sq] = piece;
12267 PackGame (Board board)
12269 Move *newSpace = NULL;
12270 moveDatabase[movePtr].piece = 0; // terminate previous game
12271 if(movePtr > dataSize) {
12272 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12273 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12274 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12277 Move *p = moveDatabase, *q = newSpace;
12278 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
12279 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12280 moveDatabase = newSpace;
12281 } else { // calloc failed, we must be out of memory. Too bad...
12282 dataSize = 0; // prevent calloc events for all subsequent games
12283 return 0; // and signal this one isn't cached
12287 MakePieceList(board, counts);
12292 QuickCompare (Board board, int *minCounts, int *maxCounts)
12293 { // compare according to search mode
12295 switch(appData.searchMode)
12297 case 1: // exact position match
12298 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12299 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12300 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12303 case 2: // can have extra material on empty squares
12304 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12305 if(board[r][f] == EmptySquare) continue;
12306 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12309 case 3: // material with exact Pawn structure
12310 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12311 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12312 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12313 } // fall through to material comparison
12314 case 4: // exact material
12315 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12317 case 6: // material range with given imbalance
12318 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12319 // fall through to range comparison
12320 case 5: // material range
12321 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12327 QuickScan (Board board, Move *move)
12328 { // reconstruct game,and compare all positions in it
12329 int cnt=0, stretch=0, total = MakePieceList(board, counts);
12331 int piece = move->piece;
12332 int to = move->to, from = pieceList[piece];
12333 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12334 if(!piece) return -1;
12335 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12336 piece = (++move)->piece;
12337 from = pieceList[piece];
12338 counts[pieceType[piece]]--;
12339 pieceType[piece] = (ChessSquare) move->to;
12340 counts[move->to]++;
12341 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12342 counts[pieceType[quickBoard[to]]]--;
12343 quickBoard[to] = 0; total--;
12346 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12347 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12348 from = pieceList[piece]; // so this must be King
12349 quickBoard[from] = 0;
12350 pieceList[piece] = to;
12351 from = pieceList[(++move)->piece]; // for FRC this has to be done here
12352 quickBoard[from] = 0; // rook
12353 quickBoard[to] = piece;
12354 to = move->to; piece = move->piece;
12358 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12359 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12360 quickBoard[from] = 0;
12362 quickBoard[to] = piece;
12363 pieceList[piece] = to;
12365 if(QuickCompare(soughtBoard, minSought, maxSought) ||
12366 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12367 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12368 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12370 static int lastCounts[EmptySquare+1];
12372 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12373 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12374 } else stretch = 0;
12375 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12384 flipSearch = FALSE;
12385 CopyBoard(soughtBoard, boards[currentMove]);
12386 soughtTotal = MakePieceList(soughtBoard, maxSought);
12387 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12388 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12389 CopyBoard(reverseBoard, boards[currentMove]);
12390 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12391 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12392 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12393 reverseBoard[r][f] = piece;
12395 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12396 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12397 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12398 || (boards[currentMove][CASTLING][2] == NoRights ||
12399 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12400 && (boards[currentMove][CASTLING][5] == NoRights ||
12401 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12404 CopyBoard(flipBoard, soughtBoard);
12405 CopyBoard(rotateBoard, reverseBoard);
12406 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12407 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
12408 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12411 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12412 if(appData.searchMode >= 5) {
12413 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12414 MakePieceList(soughtBoard, minSought);
12415 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12417 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12418 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12421 GameInfo dummyInfo;
12422 static int creatingBook;
12425 GameContainsPosition (FILE *f, ListGame *lg)
12427 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12428 int fromX, fromY, toX, toY;
12430 static int initDone=FALSE;
12432 // weed out games based on numerical tag comparison
12433 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12434 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12435 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12436 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12438 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12441 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12442 else CopyBoard(boards[scratch], initialPosition); // default start position
12445 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12446 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12449 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12450 fseek(f, lg->offset, 0);
12453 yyboardindex = scratch;
12454 quickFlag = plyNr+1;
12459 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12465 if(plyNr) return -1; // after we have seen moves, this is for new game
12468 case AmbiguousMove: // we cannot reconstruct the game beyond these two
12469 case ImpossibleMove:
12470 case WhiteWins: // game ends here with these four
12473 case GameUnfinished:
12477 if(appData.testLegality) return -1;
12478 case WhiteCapturesEnPassant:
12479 case BlackCapturesEnPassant:
12480 case WhitePromotion:
12481 case BlackPromotion:
12482 case WhiteNonPromotion:
12483 case BlackNonPromotion:
12486 case WhiteKingSideCastle:
12487 case WhiteQueenSideCastle:
12488 case BlackKingSideCastle:
12489 case BlackQueenSideCastle:
12490 case WhiteKingSideCastleWild:
12491 case WhiteQueenSideCastleWild:
12492 case BlackKingSideCastleWild:
12493 case BlackQueenSideCastleWild:
12494 case WhiteHSideCastleFR:
12495 case WhiteASideCastleFR:
12496 case BlackHSideCastleFR:
12497 case BlackASideCastleFR:
12498 fromX = currentMoveString[0] - AAA;
12499 fromY = currentMoveString[1] - ONE;
12500 toX = currentMoveString[2] - AAA;
12501 toY = currentMoveString[3] - ONE;
12502 promoChar = currentMoveString[4];
12506 fromX = next == WhiteDrop ?
12507 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12508 (int) CharToPiece(ToLower(currentMoveString[0]));
12510 toX = currentMoveString[2] - AAA;
12511 toY = currentMoveString[3] - ONE;
12515 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12517 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12518 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12519 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12520 if(appData.findMirror) {
12521 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12522 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12527 /* Load the nth game from open file f */
12529 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12533 int gn = gameNumber;
12534 ListGame *lg = NULL;
12535 int numPGNTags = 0;
12537 GameMode oldGameMode;
12538 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12540 if (appData.debugMode)
12541 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12543 if (gameMode == Training )
12544 SetTrainingModeOff();
12546 oldGameMode = gameMode;
12547 if (gameMode != BeginningOfGame) {
12548 Reset(FALSE, TRUE);
12550 killX = killY = -1; // [HGM] lion: in case we did not Reset
12553 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12554 fclose(lastLoadGameFP);
12558 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12561 fseek(f, lg->offset, 0);
12562 GameListHighlight(gameNumber);
12563 pos = lg->position;
12567 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12568 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12570 DisplayError(_("Game number out of range"), 0);
12575 if (fseek(f, 0, 0) == -1) {
12576 if (f == lastLoadGameFP ?
12577 gameNumber == lastLoadGameNumber + 1 :
12581 DisplayError(_("Can't seek on game file"), 0);
12586 lastLoadGameFP = f;
12587 lastLoadGameNumber = gameNumber;
12588 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12589 lastLoadGameUseList = useList;
12593 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12594 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12595 lg->gameInfo.black);
12597 } else if (*title != NULLCHAR) {
12598 if (gameNumber > 1) {
12599 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12602 DisplayTitle(title);
12606 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12607 gameMode = PlayFromGameFile;
12611 currentMove = forwardMostMove = backwardMostMove = 0;
12612 CopyBoard(boards[0], initialPosition);
12616 * Skip the first gn-1 games in the file.
12617 * Also skip over anything that precedes an identifiable
12618 * start of game marker, to avoid being confused by
12619 * garbage at the start of the file. Currently
12620 * recognized start of game markers are the move number "1",
12621 * the pattern "gnuchess .* game", the pattern
12622 * "^[#;%] [^ ]* game file", and a PGN tag block.
12623 * A game that starts with one of the latter two patterns
12624 * will also have a move number 1, possibly
12625 * following a position diagram.
12626 * 5-4-02: Let's try being more lenient and allowing a game to
12627 * start with an unnumbered move. Does that break anything?
12629 cm = lastLoadGameStart = EndOfFile;
12631 yyboardindex = forwardMostMove;
12632 cm = (ChessMove) Myylex();
12635 if (cmailMsgLoaded) {
12636 nCmailGames = CMAIL_MAX_GAMES - gn;
12639 DisplayError(_("Game not found in file"), 0);
12646 lastLoadGameStart = cm;
12649 case MoveNumberOne:
12650 switch (lastLoadGameStart) {
12655 case MoveNumberOne:
12657 gn--; /* count this game */
12658 lastLoadGameStart = cm;
12667 switch (lastLoadGameStart) {
12670 case MoveNumberOne:
12672 gn--; /* count this game */
12673 lastLoadGameStart = cm;
12676 lastLoadGameStart = cm; /* game counted already */
12684 yyboardindex = forwardMostMove;
12685 cm = (ChessMove) Myylex();
12686 } while (cm == PGNTag || cm == Comment);
12693 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12694 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
12695 != CMAIL_OLD_RESULT) {
12697 cmailResult[ CMAIL_MAX_GAMES
12698 - gn - 1] = CMAIL_OLD_RESULT;
12705 /* Only a NormalMove can be at the start of a game
12706 * without a position diagram. */
12707 if (lastLoadGameStart == EndOfFile ) {
12709 lastLoadGameStart = MoveNumberOne;
12718 if (appData.debugMode)
12719 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12721 if (cm == XBoardGame) {
12722 /* Skip any header junk before position diagram and/or move 1 */
12724 yyboardindex = forwardMostMove;
12725 cm = (ChessMove) Myylex();
12727 if (cm == EndOfFile ||
12728 cm == GNUChessGame || cm == XBoardGame) {
12729 /* Empty game; pretend end-of-file and handle later */
12734 if (cm == MoveNumberOne || cm == PositionDiagram ||
12735 cm == PGNTag || cm == Comment)
12738 } else if (cm == GNUChessGame) {
12739 if (gameInfo.event != NULL) {
12740 free(gameInfo.event);
12742 gameInfo.event = StrSave(yy_text);
12745 startedFromSetupPosition = FALSE;
12746 while (cm == PGNTag) {
12747 if (appData.debugMode)
12748 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12749 err = ParsePGNTag(yy_text, &gameInfo);
12750 if (!err) numPGNTags++;
12752 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12753 if(gameInfo.variant != oldVariant) {
12754 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12755 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12756 InitPosition(TRUE);
12757 oldVariant = gameInfo.variant;
12758 if (appData.debugMode)
12759 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12763 if (gameInfo.fen != NULL) {
12764 Board initial_position;
12765 startedFromSetupPosition = TRUE;
12766 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12768 DisplayError(_("Bad FEN position in file"), 0);
12771 CopyBoard(boards[0], initial_position);
12772 if (blackPlaysFirst) {
12773 currentMove = forwardMostMove = backwardMostMove = 1;
12774 CopyBoard(boards[1], initial_position);
12775 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12776 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12777 timeRemaining[0][1] = whiteTimeRemaining;
12778 timeRemaining[1][1] = blackTimeRemaining;
12779 if (commentList[0] != NULL) {
12780 commentList[1] = commentList[0];
12781 commentList[0] = NULL;
12784 currentMove = forwardMostMove = backwardMostMove = 0;
12786 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12788 initialRulePlies = FENrulePlies;
12789 for( i=0; i< nrCastlingRights; i++ )
12790 initialRights[i] = initial_position[CASTLING][i];
12792 yyboardindex = forwardMostMove;
12793 free(gameInfo.fen);
12794 gameInfo.fen = NULL;
12797 yyboardindex = forwardMostMove;
12798 cm = (ChessMove) Myylex();
12800 /* Handle comments interspersed among the tags */
12801 while (cm == Comment) {
12803 if (appData.debugMode)
12804 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12806 AppendComment(currentMove, p, FALSE);
12807 yyboardindex = forwardMostMove;
12808 cm = (ChessMove) Myylex();
12812 /* don't rely on existence of Event tag since if game was
12813 * pasted from clipboard the Event tag may not exist
12815 if (numPGNTags > 0){
12817 if (gameInfo.variant == VariantNormal) {
12818 VariantClass v = StringToVariant(gameInfo.event);
12819 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12820 if(v < VariantShogi) gameInfo.variant = v;
12823 if( appData.autoDisplayTags ) {
12824 tags = PGNTags(&gameInfo);
12825 TagsPopUp(tags, CmailMsg());
12830 /* Make something up, but don't display it now */
12835 if (cm == PositionDiagram) {
12838 Board initial_position;
12840 if (appData.debugMode)
12841 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12843 if (!startedFromSetupPosition) {
12845 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12846 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12857 initial_position[i][j++] = CharToPiece(*p);
12860 while (*p == ' ' || *p == '\t' ||
12861 *p == '\n' || *p == '\r') p++;
12863 if (strncmp(p, "black", strlen("black"))==0)
12864 blackPlaysFirst = TRUE;
12866 blackPlaysFirst = FALSE;
12867 startedFromSetupPosition = TRUE;
12869 CopyBoard(boards[0], initial_position);
12870 if (blackPlaysFirst) {
12871 currentMove = forwardMostMove = backwardMostMove = 1;
12872 CopyBoard(boards[1], initial_position);
12873 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12874 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12875 timeRemaining[0][1] = whiteTimeRemaining;
12876 timeRemaining[1][1] = blackTimeRemaining;
12877 if (commentList[0] != NULL) {
12878 commentList[1] = commentList[0];
12879 commentList[0] = NULL;
12882 currentMove = forwardMostMove = backwardMostMove = 0;
12885 yyboardindex = forwardMostMove;
12886 cm = (ChessMove) Myylex();
12889 if(!creatingBook) {
12890 if (first.pr == NoProc) {
12891 StartChessProgram(&first);
12893 InitChessProgram(&first, FALSE);
12894 SendToProgram("force\n", &first);
12895 if (startedFromSetupPosition) {
12896 SendBoard(&first, forwardMostMove);
12897 if (appData.debugMode) {
12898 fprintf(debugFP, "Load Game\n");
12900 DisplayBothClocks();
12904 /* [HGM] server: flag to write setup moves in broadcast file as one */
12905 loadFlag = appData.suppressLoadMoves;
12907 while (cm == Comment) {
12909 if (appData.debugMode)
12910 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12912 AppendComment(currentMove, p, FALSE);
12913 yyboardindex = forwardMostMove;
12914 cm = (ChessMove) Myylex();
12917 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12918 cm == WhiteWins || cm == BlackWins ||
12919 cm == GameIsDrawn || cm == GameUnfinished) {
12920 DisplayMessage("", _("No moves in game"));
12921 if (cmailMsgLoaded) {
12922 if (appData.debugMode)
12923 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12927 DrawPosition(FALSE, boards[currentMove]);
12928 DisplayBothClocks();
12929 gameMode = EditGame;
12936 // [HGM] PV info: routine tests if comment empty
12937 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12938 DisplayComment(currentMove - 1, commentList[currentMove]);
12940 if (!matchMode && appData.timeDelay != 0)
12941 DrawPosition(FALSE, boards[currentMove]);
12943 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12944 programStats.ok_to_send = 1;
12947 /* if the first token after the PGN tags is a move
12948 * and not move number 1, retrieve it from the parser
12950 if (cm != MoveNumberOne)
12951 LoadGameOneMove(cm);
12953 /* load the remaining moves from the file */
12954 while (LoadGameOneMove(EndOfFile)) {
12955 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12956 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12959 /* rewind to the start of the game */
12960 currentMove = backwardMostMove;
12962 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12964 if (oldGameMode == AnalyzeFile) {
12965 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12966 AnalyzeFileEvent();
12968 if (oldGameMode == AnalyzeMode) {
12969 AnalyzeFileEvent();
12972 if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12973 long int w, b; // [HGM] adjourn: restore saved clock times
12974 char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12975 if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12976 timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12977 timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12981 if(creatingBook) return TRUE;
12982 if (!matchMode && pos > 0) {
12983 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12985 if (matchMode || appData.timeDelay == 0) {
12987 } else if (appData.timeDelay > 0) {
12988 AutoPlayGameLoop();
12991 if (appData.debugMode)
12992 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12994 loadFlag = 0; /* [HGM] true game starts */
12998 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13000 ReloadPosition (int offset)
13002 int positionNumber = lastLoadPositionNumber + offset;
13003 if (lastLoadPositionFP == NULL) {
13004 DisplayError(_("No position has been loaded yet"), 0);
13007 if (positionNumber <= 0) {
13008 DisplayError(_("Can't back up any further"), 0);
13011 return LoadPosition(lastLoadPositionFP, positionNumber,
13012 lastLoadPositionTitle);
13015 /* Load the nth position from the given file */
13017 LoadPositionFromFile (char *filename, int n, char *title)
13022 if (strcmp(filename, "-") == 0) {
13023 return LoadPosition(stdin, n, "stdin");
13025 f = fopen(filename, "rb");
13027 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13028 DisplayError(buf, errno);
13031 return LoadPosition(f, n, title);
13036 /* Load the nth position from the given open file, and close it */
13038 LoadPosition (FILE *f, int positionNumber, char *title)
13040 char *p, line[MSG_SIZ];
13041 Board initial_position;
13042 int i, j, fenMode, pn;
13044 if (gameMode == Training )
13045 SetTrainingModeOff();
13047 if (gameMode != BeginningOfGame) {
13048 Reset(FALSE, TRUE);
13050 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13051 fclose(lastLoadPositionFP);
13053 if (positionNumber == 0) positionNumber = 1;
13054 lastLoadPositionFP = f;
13055 lastLoadPositionNumber = positionNumber;
13056 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13057 if (first.pr == NoProc && !appData.noChessProgram) {
13058 StartChessProgram(&first);
13059 InitChessProgram(&first, FALSE);
13061 pn = positionNumber;
13062 if (positionNumber < 0) {
13063 /* Negative position number means to seek to that byte offset */
13064 if (fseek(f, -positionNumber, 0) == -1) {
13065 DisplayError(_("Can't seek on position file"), 0);
13070 if (fseek(f, 0, 0) == -1) {
13071 if (f == lastLoadPositionFP ?
13072 positionNumber == lastLoadPositionNumber + 1 :
13073 positionNumber == 1) {
13076 DisplayError(_("Can't seek on position file"), 0);
13081 /* See if this file is FEN or old-style xboard */
13082 if (fgets(line, MSG_SIZ, f) == NULL) {
13083 DisplayError(_("Position not found in file"), 0);
13086 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13087 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13090 if (fenMode || line[0] == '#') pn--;
13092 /* skip positions before number pn */
13093 if (fgets(line, MSG_SIZ, f) == NULL) {
13095 DisplayError(_("Position not found in file"), 0);
13098 if (fenMode || line[0] == '#') pn--;
13103 if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13104 DisplayError(_("Bad FEN position in file"), 0);
13108 (void) fgets(line, MSG_SIZ, f);
13109 (void) fgets(line, MSG_SIZ, f);
13111 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13112 (void) fgets(line, MSG_SIZ, f);
13113 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13116 initial_position[i][j++] = CharToPiece(*p);
13120 blackPlaysFirst = FALSE;
13122 (void) fgets(line, MSG_SIZ, f);
13123 if (strncmp(line, "black", strlen("black"))==0)
13124 blackPlaysFirst = TRUE;
13127 startedFromSetupPosition = TRUE;
13129 CopyBoard(boards[0], initial_position);
13130 if (blackPlaysFirst) {
13131 currentMove = forwardMostMove = backwardMostMove = 1;
13132 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13133 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13134 CopyBoard(boards[1], initial_position);
13135 DisplayMessage("", _("Black to play"));
13137 currentMove = forwardMostMove = backwardMostMove = 0;
13138 DisplayMessage("", _("White to play"));
13140 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13141 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13142 SendToProgram("force\n", &first);
13143 SendBoard(&first, forwardMostMove);
13145 if (appData.debugMode) {
13147 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13148 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13149 fprintf(debugFP, "Load Position\n");
13152 if (positionNumber > 1) {
13153 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13154 DisplayTitle(line);
13156 DisplayTitle(title);
13158 gameMode = EditGame;
13161 timeRemaining[0][1] = whiteTimeRemaining;
13162 timeRemaining[1][1] = blackTimeRemaining;
13163 DrawPosition(FALSE, boards[currentMove]);
13170 CopyPlayerNameIntoFileName (char **dest, char *src)
13172 while (*src != NULLCHAR && *src != ',') {
13177 *(*dest)++ = *src++;
13183 DefaultFileName (char *ext)
13185 static char def[MSG_SIZ];
13188 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13190 CopyPlayerNameIntoFileName(&p, gameInfo.white);
13192 CopyPlayerNameIntoFileName(&p, gameInfo.black);
13194 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13201 /* Save the current game to the given file */
13203 SaveGameToFile (char *filename, int append)
13207 int result, i, t,tot=0;
13209 if (strcmp(filename, "-") == 0) {
13210 return SaveGame(stdout, 0, NULL);
13212 for(i=0; i<10; i++) { // upto 10 tries
13213 f = fopen(filename, append ? "a" : "w");
13214 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13215 if(f || errno != 13) break;
13216 DoSleep(t = 5 + random()%11); // wait 5-15 msec
13220 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13221 DisplayError(buf, errno);
13224 safeStrCpy(buf, lastMsg, MSG_SIZ);
13225 DisplayMessage(_("Waiting for access to save file"), "");
13226 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13227 DisplayMessage(_("Saving game"), "");
13228 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
13229 result = SaveGame(f, 0, NULL);
13230 DisplayMessage(buf, "");
13237 SavePart (char *str)
13239 static char buf[MSG_SIZ];
13242 p = strchr(str, ' ');
13243 if (p == NULL) return str;
13244 strncpy(buf, str, p - str);
13245 buf[p - str] = NULLCHAR;
13249 #define PGN_MAX_LINE 75
13251 #define PGN_SIDE_WHITE 0
13252 #define PGN_SIDE_BLACK 1
13255 FindFirstMoveOutOfBook (int side)
13259 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13260 int index = backwardMostMove;
13261 int has_book_hit = 0;
13263 if( (index % 2) != side ) {
13267 while( index < forwardMostMove ) {
13268 /* Check to see if engine is in book */
13269 int depth = pvInfoList[index].depth;
13270 int score = pvInfoList[index].score;
13276 else if( score == 0 && depth == 63 ) {
13277 in_book = 1; /* Zappa */
13279 else if( score == 2 && depth == 99 ) {
13280 in_book = 1; /* Abrok */
13283 has_book_hit += in_book;
13299 GetOutOfBookInfo (char * buf)
13303 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13305 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13306 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13310 if( oob[0] >= 0 || oob[1] >= 0 ) {
13311 for( i=0; i<2; i++ ) {
13315 if( i > 0 && oob[0] >= 0 ) {
13316 strcat( buf, " " );
13319 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13320 sprintf( buf+strlen(buf), "%s%.2f",
13321 pvInfoList[idx].score >= 0 ? "+" : "",
13322 pvInfoList[idx].score / 100.0 );
13328 /* Save game in PGN style and close the file */
13330 SaveGamePGN (FILE *f)
13332 int i, offset, linelen, newblock;
13335 int movelen, numlen, blank;
13336 char move_buffer[100]; /* [AS] Buffer for move+PV info */
13338 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13340 PrintPGNTags(f, &gameInfo);
13342 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13344 if (backwardMostMove > 0 || startedFromSetupPosition) {
13345 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13346 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13347 fprintf(f, "\n{--------------\n");
13348 PrintPosition(f, backwardMostMove);
13349 fprintf(f, "--------------}\n");
13353 /* [AS] Out of book annotation */
13354 if( appData.saveOutOfBookInfo ) {
13357 GetOutOfBookInfo( buf );
13359 if( buf[0] != '\0' ) {
13360 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13367 i = backwardMostMove;
13371 while (i < forwardMostMove) {
13372 /* Print comments preceding this move */
13373 if (commentList[i] != NULL) {
13374 if (linelen > 0) fprintf(f, "\n");
13375 fprintf(f, "%s", commentList[i]);
13380 /* Format move number */
13382 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13385 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13387 numtext[0] = NULLCHAR;
13389 numlen = strlen(numtext);
13392 /* Print move number */
13393 blank = linelen > 0 && numlen > 0;
13394 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13403 fprintf(f, "%s", numtext);
13407 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13408 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13411 blank = linelen > 0 && movelen > 0;
13412 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13421 fprintf(f, "%s", move_buffer);
13422 linelen += movelen;
13424 /* [AS] Add PV info if present */
13425 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13426 /* [HGM] add time */
13427 char buf[MSG_SIZ]; int seconds;
13429 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13435 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13438 seconds = (seconds + 4)/10; // round to full seconds
13440 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13442 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13445 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13446 pvInfoList[i].score >= 0 ? "+" : "",
13447 pvInfoList[i].score / 100.0,
13448 pvInfoList[i].depth,
13451 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13453 /* Print score/depth */
13454 blank = linelen > 0 && movelen > 0;
13455 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13464 fprintf(f, "%s", move_buffer);
13465 linelen += movelen;
13471 /* Start a new line */
13472 if (linelen > 0) fprintf(f, "\n");
13474 /* Print comments after last move */
13475 if (commentList[i] != NULL) {
13476 fprintf(f, "%s\n", commentList[i]);
13480 if (gameInfo.resultDetails != NULL &&
13481 gameInfo.resultDetails[0] != NULLCHAR) {
13482 char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13483 if(gameInfo.result == GameUnfinished && appData.clockMode &&
13484 (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13485 snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13486 fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13488 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13492 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13496 /* Save game in old style and close the file */
13498 SaveGameOldStyle (FILE *f)
13503 tm = time((time_t *) NULL);
13505 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13508 if (backwardMostMove > 0 || startedFromSetupPosition) {
13509 fprintf(f, "\n[--------------\n");
13510 PrintPosition(f, backwardMostMove);
13511 fprintf(f, "--------------]\n");
13516 i = backwardMostMove;
13517 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13519 while (i < forwardMostMove) {
13520 if (commentList[i] != NULL) {
13521 fprintf(f, "[%s]\n", commentList[i]);
13524 if ((i % 2) == 1) {
13525 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
13528 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
13530 if (commentList[i] != NULL) {
13534 if (i >= forwardMostMove) {
13538 fprintf(f, "%s\n", parseList[i]);
13543 if (commentList[i] != NULL) {
13544 fprintf(f, "[%s]\n", commentList[i]);
13547 /* This isn't really the old style, but it's close enough */
13548 if (gameInfo.resultDetails != NULL &&
13549 gameInfo.resultDetails[0] != NULLCHAR) {
13550 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13551 gameInfo.resultDetails);
13553 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13560 /* Save the current game to open file f and close the file */
13562 SaveGame (FILE *f, int dummy, char *dummy2)
13564 if (gameMode == EditPosition) EditPositionDone(TRUE);
13565 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13566 if (appData.oldSaveStyle)
13567 return SaveGameOldStyle(f);
13569 return SaveGamePGN(f);
13572 /* Save the current position to the given file */
13574 SavePositionToFile (char *filename)
13579 if (strcmp(filename, "-") == 0) {
13580 return SavePosition(stdout, 0, NULL);
13582 f = fopen(filename, "a");
13584 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13585 DisplayError(buf, errno);
13588 safeStrCpy(buf, lastMsg, MSG_SIZ);
13589 DisplayMessage(_("Waiting for access to save file"), "");
13590 flock(fileno(f), LOCK_EX); // [HGM] lock
13591 DisplayMessage(_("Saving position"), "");
13592 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
13593 SavePosition(f, 0, NULL);
13594 DisplayMessage(buf, "");
13600 /* Save the current position to the given open file and close the file */
13602 SavePosition (FILE *f, int dummy, char *dummy2)
13607 if (gameMode == EditPosition) EditPositionDone(TRUE);
13608 if (appData.oldSaveStyle) {
13609 tm = time((time_t *) NULL);
13611 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13613 fprintf(f, "[--------------\n");
13614 PrintPosition(f, currentMove);
13615 fprintf(f, "--------------]\n");
13617 fen = PositionToFEN(currentMove, NULL, 1);
13618 fprintf(f, "%s\n", fen);
13626 ReloadCmailMsgEvent (int unregister)
13629 static char *inFilename = NULL;
13630 static char *outFilename;
13632 struct stat inbuf, outbuf;
13635 /* Any registered moves are unregistered if unregister is set, */
13636 /* i.e. invoked by the signal handler */
13638 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13639 cmailMoveRegistered[i] = FALSE;
13640 if (cmailCommentList[i] != NULL) {
13641 free(cmailCommentList[i]);
13642 cmailCommentList[i] = NULL;
13645 nCmailMovesRegistered = 0;
13648 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13649 cmailResult[i] = CMAIL_NOT_RESULT;
13653 if (inFilename == NULL) {
13654 /* Because the filenames are static they only get malloced once */
13655 /* and they never get freed */
13656 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13657 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13659 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13660 sprintf(outFilename, "%s.out", appData.cmailGameName);
13663 status = stat(outFilename, &outbuf);
13665 cmailMailedMove = FALSE;
13667 status = stat(inFilename, &inbuf);
13668 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13671 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13672 counts the games, notes how each one terminated, etc.
13674 It would be nice to remove this kludge and instead gather all
13675 the information while building the game list. (And to keep it
13676 in the game list nodes instead of having a bunch of fixed-size
13677 parallel arrays.) Note this will require getting each game's
13678 termination from the PGN tags, as the game list builder does
13679 not process the game moves. --mann
13681 cmailMsgLoaded = TRUE;
13682 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13684 /* Load first game in the file or popup game menu */
13685 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13687 #endif /* !WIN32 */
13695 char string[MSG_SIZ];
13697 if ( cmailMailedMove
13698 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13699 return TRUE; /* Allow free viewing */
13702 /* Unregister move to ensure that we don't leave RegisterMove */
13703 /* with the move registered when the conditions for registering no */
13705 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13706 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13707 nCmailMovesRegistered --;
13709 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13711 free(cmailCommentList[lastLoadGameNumber - 1]);
13712 cmailCommentList[lastLoadGameNumber - 1] = NULL;
13716 if (cmailOldMove == -1) {
13717 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13721 if (currentMove > cmailOldMove + 1) {
13722 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13726 if (currentMove < cmailOldMove) {
13727 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13731 if (forwardMostMove > currentMove) {
13732 /* Silently truncate extra moves */
13736 if ( (currentMove == cmailOldMove + 1)
13737 || ( (currentMove == cmailOldMove)
13738 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13739 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13740 if (gameInfo.result != GameUnfinished) {
13741 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13744 if (commentList[currentMove] != NULL) {
13745 cmailCommentList[lastLoadGameNumber - 1]
13746 = StrSave(commentList[currentMove]);
13748 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13750 if (appData.debugMode)
13751 fprintf(debugFP, "Saving %s for game %d\n",
13752 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13754 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13756 f = fopen(string, "w");
13757 if (appData.oldSaveStyle) {
13758 SaveGameOldStyle(f); /* also closes the file */
13760 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13761 f = fopen(string, "w");
13762 SavePosition(f, 0, NULL); /* also closes the file */
13764 fprintf(f, "{--------------\n");
13765 PrintPosition(f, currentMove);
13766 fprintf(f, "--------------}\n\n");
13768 SaveGame(f, 0, NULL); /* also closes the file*/
13771 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13772 nCmailMovesRegistered ++;
13773 } else if (nCmailGames == 1) {
13774 DisplayError(_("You have not made a move yet"), 0);
13785 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13786 FILE *commandOutput;
13787 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13788 int nBytes = 0; /* Suppress warnings on uninitialized variables */
13794 if (! cmailMsgLoaded) {
13795 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13799 if (nCmailGames == nCmailResults) {
13800 DisplayError(_("No unfinished games"), 0);
13804 #if CMAIL_PROHIBIT_REMAIL
13805 if (cmailMailedMove) {
13806 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);
13807 DisplayError(msg, 0);
13812 if (! (cmailMailedMove || RegisterMove())) return;
13814 if ( cmailMailedMove
13815 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13816 snprintf(string, MSG_SIZ, partCommandString,
13817 appData.debugMode ? " -v" : "", appData.cmailGameName);
13818 commandOutput = popen(string, "r");
13820 if (commandOutput == NULL) {
13821 DisplayError(_("Failed to invoke cmail"), 0);
13823 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13824 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13826 if (nBuffers > 1) {
13827 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13828 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13829 nBytes = MSG_SIZ - 1;
13831 (void) memcpy(msg, buffer, nBytes);
13833 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13835 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13836 cmailMailedMove = TRUE; /* Prevent >1 moves */
13839 for (i = 0; i < nCmailGames; i ++) {
13840 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13845 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13847 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13849 appData.cmailGameName,
13851 LoadGameFromFile(buffer, 1, buffer, FALSE);
13852 cmailMsgLoaded = FALSE;
13856 DisplayInformation(msg);
13857 pclose(commandOutput);
13860 if ((*cmailMsg) != '\0') {
13861 DisplayInformation(cmailMsg);
13866 #endif /* !WIN32 */
13875 int prependComma = 0;
13877 char string[MSG_SIZ]; /* Space for game-list */
13880 if (!cmailMsgLoaded) return "";
13882 if (cmailMailedMove) {
13883 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13885 /* Create a list of games left */
13886 snprintf(string, MSG_SIZ, "[");
13887 for (i = 0; i < nCmailGames; i ++) {
13888 if (! ( cmailMoveRegistered[i]
13889 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13890 if (prependComma) {
13891 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13893 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13897 strcat(string, number);
13900 strcat(string, "]");
13902 if (nCmailMovesRegistered + nCmailResults == 0) {
13903 switch (nCmailGames) {
13905 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13909 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13913 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13918 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13920 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13925 if (nCmailResults == nCmailGames) {
13926 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13928 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13933 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13945 if (gameMode == Training)
13946 SetTrainingModeOff();
13949 cmailMsgLoaded = FALSE;
13950 if (appData.icsActive) {
13951 SendToICS(ics_prefix);
13952 SendToICS("refresh\n");
13957 ExitEvent (int status)
13961 /* Give up on clean exit */
13965 /* Keep trying for clean exit */
13969 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13971 if (telnetISR != NULL) {
13972 RemoveInputSource(telnetISR);
13974 if (icsPR != NoProc) {
13975 DestroyChildProcess(icsPR, TRUE);
13978 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13979 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13981 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13982 /* make sure this other one finishes before killing it! */
13983 if(endingGame) { int count = 0;
13984 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13985 while(endingGame && count++ < 10) DoSleep(1);
13986 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13989 /* Kill off chess programs */
13990 if (first.pr != NoProc) {
13993 DoSleep( appData.delayBeforeQuit );
13994 SendToProgram("quit\n", &first);
13995 DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
13997 if (second.pr != NoProc) {
13998 DoSleep( appData.delayBeforeQuit );
13999 SendToProgram("quit\n", &second);
14000 DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14002 if (first.isr != NULL) {
14003 RemoveInputSource(first.isr);
14005 if (second.isr != NULL) {
14006 RemoveInputSource(second.isr);
14009 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14010 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14012 ShutDownFrontEnd();
14017 PauseEngine (ChessProgramState *cps)
14019 SendToProgram("pause\n", cps);
14024 UnPauseEngine (ChessProgramState *cps)
14026 SendToProgram("resume\n", cps);
14033 if (appData.debugMode)
14034 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14038 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14040 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14041 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14042 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14044 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14045 HandleMachineMove(stashedInputMove, stalledEngine);
14046 stalledEngine = NULL;
14049 if (gameMode == MachinePlaysWhite ||
14050 gameMode == TwoMachinesPlay ||
14051 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14052 if(first.pause) UnPauseEngine(&first);
14053 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14054 if(second.pause) UnPauseEngine(&second);
14055 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14058 DisplayBothClocks();
14060 if (gameMode == PlayFromGameFile) {
14061 if (appData.timeDelay >= 0)
14062 AutoPlayGameLoop();
14063 } else if (gameMode == IcsExamining && pauseExamInvalid) {
14064 Reset(FALSE, TRUE);
14065 SendToICS(ics_prefix);
14066 SendToICS("refresh\n");
14067 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14068 ForwardInner(forwardMostMove);
14070 pauseExamInvalid = FALSE;
14072 switch (gameMode) {
14076 pauseExamForwardMostMove = forwardMostMove;
14077 pauseExamInvalid = FALSE;
14080 case IcsPlayingWhite:
14081 case IcsPlayingBlack:
14085 case PlayFromGameFile:
14086 (void) StopLoadGameTimer();
14090 case BeginningOfGame:
14091 if (appData.icsActive) return;
14092 /* else fall through */
14093 case MachinePlaysWhite:
14094 case MachinePlaysBlack:
14095 case TwoMachinesPlay:
14096 if (forwardMostMove == 0)
14097 return; /* don't pause if no one has moved */
14098 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14099 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14100 if(onMove->pause) { // thinking engine can be paused
14101 PauseEngine(onMove); // do it
14102 if(onMove->other->pause) // pondering opponent can always be paused immediately
14103 PauseEngine(onMove->other);
14105 SendToProgram("easy\n", onMove->other);
14107 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14108 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14110 PauseEngine(&first);
14112 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14113 } else { // human on move, pause pondering by either method
14115 PauseEngine(&first);
14116 else if(appData.ponderNextMove)
14117 SendToProgram("easy\n", &first);
14120 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14130 EditCommentEvent ()
14132 char title[MSG_SIZ];
14134 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14135 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14137 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14138 WhiteOnMove(currentMove - 1) ? " " : ".. ",
14139 parseList[currentMove - 1]);
14142 EditCommentPopUp(currentMove, title, commentList[currentMove]);
14149 char *tags = PGNTags(&gameInfo);
14151 EditTagsPopUp(tags, NULL);
14158 if(second.analyzing) {
14159 SendToProgram("exit\n", &second);
14160 second.analyzing = FALSE;
14162 if (second.pr == NoProc) StartChessProgram(&second);
14163 InitChessProgram(&second, FALSE);
14164 FeedMovesToProgram(&second, currentMove);
14166 SendToProgram("analyze\n", &second);
14167 second.analyzing = TRUE;
14171 /* Toggle ShowThinking */
14173 ToggleShowThinking()
14175 appData.showThinking = !appData.showThinking;
14176 ShowThinkingEvent();
14180 AnalyzeModeEvent ()
14184 if (!first.analysisSupport) {
14185 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14186 DisplayError(buf, 0);
14189 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14190 if (appData.icsActive) {
14191 if (gameMode != IcsObserving) {
14192 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14193 DisplayError(buf, 0);
14195 if (appData.icsEngineAnalyze) {
14196 if (appData.debugMode)
14197 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14203 /* if enable, user wants to disable icsEngineAnalyze */
14204 if (appData.icsEngineAnalyze) {
14209 appData.icsEngineAnalyze = TRUE;
14210 if (appData.debugMode)
14211 fprintf(debugFP, "ICS engine analyze starting... \n");
14214 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14215 if (appData.noChessProgram || gameMode == AnalyzeMode)
14218 if (gameMode != AnalyzeFile) {
14219 if (!appData.icsEngineAnalyze) {
14221 if (gameMode != EditGame) return 0;
14223 if (!appData.showThinking) ToggleShowThinking();
14224 ResurrectChessProgram();
14225 SendToProgram("analyze\n", &first);
14226 first.analyzing = TRUE;
14227 /*first.maybeThinking = TRUE;*/
14228 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14229 EngineOutputPopUp();
14231 if (!appData.icsEngineAnalyze) {
14232 gameMode = AnalyzeMode;
14233 ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14239 StartAnalysisClock();
14240 GetTimeMark(&lastNodeCountTime);
14246 AnalyzeFileEvent ()
14248 if (appData.noChessProgram || gameMode == AnalyzeFile)
14251 if (!first.analysisSupport) {
14253 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14254 DisplayError(buf, 0);
14258 if (gameMode != AnalyzeMode) {
14259 keepInfo = 1; // mere annotating should not alter PGN tags
14262 if (gameMode != EditGame) return;
14263 if (!appData.showThinking) ToggleShowThinking();
14264 ResurrectChessProgram();
14265 SendToProgram("analyze\n", &first);
14266 first.analyzing = TRUE;
14267 /*first.maybeThinking = TRUE;*/
14268 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14269 EngineOutputPopUp();
14271 gameMode = AnalyzeFile;
14275 StartAnalysisClock();
14276 GetTimeMark(&lastNodeCountTime);
14278 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14279 AnalysisPeriodicEvent(1);
14283 MachineWhiteEvent ()
14286 char *bookHit = NULL;
14288 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14292 if (gameMode == PlayFromGameFile ||
14293 gameMode == TwoMachinesPlay ||
14294 gameMode == Training ||
14295 gameMode == AnalyzeMode ||
14296 gameMode == EndOfGame)
14299 if (gameMode == EditPosition)
14300 EditPositionDone(TRUE);
14302 if (!WhiteOnMove(currentMove)) {
14303 DisplayError(_("It is not White's turn"), 0);
14307 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14310 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14311 gameMode == AnalyzeFile)
14314 ResurrectChessProgram(); /* in case it isn't running */
14315 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14316 gameMode = MachinePlaysWhite;
14319 gameMode = MachinePlaysWhite;
14323 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14325 if (first.sendName) {
14326 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14327 SendToProgram(buf, &first);
14329 if (first.sendTime) {
14330 if (first.useColors) {
14331 SendToProgram("black\n", &first); /*gnu kludge*/
14333 SendTimeRemaining(&first, TRUE);
14335 if (first.useColors) {
14336 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14338 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14339 SetMachineThinkingEnables();
14340 first.maybeThinking = TRUE;
14344 if (appData.autoFlipView && !flipView) {
14345 flipView = !flipView;
14346 DrawPosition(FALSE, NULL);
14347 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14350 if(bookHit) { // [HGM] book: simulate book reply
14351 static char bookMove[MSG_SIZ]; // a bit generous?
14353 programStats.nodes = programStats.depth = programStats.time =
14354 programStats.score = programStats.got_only_move = 0;
14355 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14357 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14358 strcat(bookMove, bookHit);
14359 HandleMachineMove(bookMove, &first);
14364 MachineBlackEvent ()
14367 char *bookHit = NULL;
14369 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14373 if (gameMode == PlayFromGameFile ||
14374 gameMode == TwoMachinesPlay ||
14375 gameMode == Training ||
14376 gameMode == AnalyzeMode ||
14377 gameMode == EndOfGame)
14380 if (gameMode == EditPosition)
14381 EditPositionDone(TRUE);
14383 if (WhiteOnMove(currentMove)) {
14384 DisplayError(_("It is not Black's turn"), 0);
14388 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14391 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14392 gameMode == AnalyzeFile)
14395 ResurrectChessProgram(); /* in case it isn't running */
14396 gameMode = MachinePlaysBlack;
14400 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14402 if (first.sendName) {
14403 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14404 SendToProgram(buf, &first);
14406 if (first.sendTime) {
14407 if (first.useColors) {
14408 SendToProgram("white\n", &first); /*gnu kludge*/
14410 SendTimeRemaining(&first, FALSE);
14412 if (first.useColors) {
14413 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14415 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14416 SetMachineThinkingEnables();
14417 first.maybeThinking = TRUE;
14420 if (appData.autoFlipView && flipView) {
14421 flipView = !flipView;
14422 DrawPosition(FALSE, NULL);
14423 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14425 if(bookHit) { // [HGM] book: simulate book reply
14426 static char bookMove[MSG_SIZ]; // a bit generous?
14428 programStats.nodes = programStats.depth = programStats.time =
14429 programStats.score = programStats.got_only_move = 0;
14430 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14432 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14433 strcat(bookMove, bookHit);
14434 HandleMachineMove(bookMove, &first);
14440 DisplayTwoMachinesTitle ()
14443 if (appData.matchGames > 0) {
14444 if(appData.tourneyFile[0]) {
14445 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14446 gameInfo.white, _("vs."), gameInfo.black,
14447 nextGame+1, appData.matchGames+1,
14448 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14450 if (first.twoMachinesColor[0] == 'w') {
14451 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14452 gameInfo.white, _("vs."), gameInfo.black,
14453 first.matchWins, second.matchWins,
14454 matchGame - 1 - (first.matchWins + second.matchWins));
14456 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14457 gameInfo.white, _("vs."), gameInfo.black,
14458 second.matchWins, first.matchWins,
14459 matchGame - 1 - (first.matchWins + second.matchWins));
14462 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14468 SettingsMenuIfReady ()
14470 if (second.lastPing != second.lastPong) {
14471 DisplayMessage("", _("Waiting for second chess program"));
14472 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14476 DisplayMessage("", "");
14477 SettingsPopUp(&second);
14481 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14484 if (cps->pr == NoProc) {
14485 StartChessProgram(cps);
14486 if (cps->protocolVersion == 1) {
14488 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14490 /* kludge: allow timeout for initial "feature" command */
14491 if(retry != TwoMachinesEventIfReady) FreezeUI();
14492 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14493 DisplayMessage("", buf);
14494 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14502 TwoMachinesEvent P((void))
14506 ChessProgramState *onmove;
14507 char *bookHit = NULL;
14508 static int stalling = 0;
14512 if (appData.noChessProgram) return;
14514 switch (gameMode) {
14515 case TwoMachinesPlay:
14517 case MachinePlaysWhite:
14518 case MachinePlaysBlack:
14519 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14520 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14524 case BeginningOfGame:
14525 case PlayFromGameFile:
14528 if (gameMode != EditGame) return;
14531 EditPositionDone(TRUE);
14542 // forwardMostMove = currentMove;
14543 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14544 startingEngine = TRUE;
14546 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14548 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14549 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14550 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14553 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14555 if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14556 gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14557 startingEngine = FALSE;
14558 DisplayError("second engine does not play this", 0);
14563 InitChessProgram(&second, FALSE); // unbalances ping of second engine
14564 SendToProgram("force\n", &second);
14566 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14569 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14570 if(appData.matchPause>10000 || appData.matchPause<10)
14571 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14572 wait = SubtractTimeMarks(&now, &pauseStart);
14573 if(wait < appData.matchPause) {
14574 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14577 // we are now committed to starting the game
14579 DisplayMessage("", "");
14580 if (startedFromSetupPosition) {
14581 SendBoard(&second, backwardMostMove);
14582 if (appData.debugMode) {
14583 fprintf(debugFP, "Two Machines\n");
14586 for (i = backwardMostMove; i < forwardMostMove; i++) {
14587 SendMoveToProgram(i, &second);
14590 gameMode = TwoMachinesPlay;
14591 pausing = startingEngine = FALSE;
14592 ModeHighlight(); // [HGM] logo: this triggers display update of logos
14594 DisplayTwoMachinesTitle();
14596 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14601 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14602 SendToProgram(first.computerString, &first);
14603 if (first.sendName) {
14604 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14605 SendToProgram(buf, &first);
14607 SendToProgram(second.computerString, &second);
14608 if (second.sendName) {
14609 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14610 SendToProgram(buf, &second);
14614 if (!first.sendTime || !second.sendTime) {
14615 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14616 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14618 if (onmove->sendTime) {
14619 if (onmove->useColors) {
14620 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14622 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14624 if (onmove->useColors) {
14625 SendToProgram(onmove->twoMachinesColor, onmove);
14627 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14628 // SendToProgram("go\n", onmove);
14629 onmove->maybeThinking = TRUE;
14630 SetMachineThinkingEnables();
14634 if(bookHit) { // [HGM] book: simulate book reply
14635 static char bookMove[MSG_SIZ]; // a bit generous?
14637 programStats.nodes = programStats.depth = programStats.time =
14638 programStats.score = programStats.got_only_move = 0;
14639 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14641 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14642 strcat(bookMove, bookHit);
14643 savedMessage = bookMove; // args for deferred call
14644 savedState = onmove;
14645 ScheduleDelayedEvent(DeferredBookMove, 1);
14652 if (gameMode == Training) {
14653 SetTrainingModeOff();
14654 gameMode = PlayFromGameFile;
14655 DisplayMessage("", _("Training mode off"));
14657 gameMode = Training;
14658 animateTraining = appData.animate;
14660 /* make sure we are not already at the end of the game */
14661 if (currentMove < forwardMostMove) {
14662 SetTrainingModeOn();
14663 DisplayMessage("", _("Training mode on"));
14665 gameMode = PlayFromGameFile;
14666 DisplayError(_("Already at end of game"), 0);
14675 if (!appData.icsActive) return;
14676 switch (gameMode) {
14677 case IcsPlayingWhite:
14678 case IcsPlayingBlack:
14681 case BeginningOfGame:
14689 EditPositionDone(TRUE);
14702 gameMode = IcsIdle;
14712 switch (gameMode) {
14714 SetTrainingModeOff();
14716 case MachinePlaysWhite:
14717 case MachinePlaysBlack:
14718 case BeginningOfGame:
14719 SendToProgram("force\n", &first);
14720 SetUserThinkingEnables();
14722 case PlayFromGameFile:
14723 (void) StopLoadGameTimer();
14724 if (gameFileFP != NULL) {
14729 EditPositionDone(TRUE);
14734 SendToProgram("force\n", &first);
14736 case TwoMachinesPlay:
14737 GameEnds(EndOfFile, NULL, GE_PLAYER);
14738 ResurrectChessProgram();
14739 SetUserThinkingEnables();
14742 ResurrectChessProgram();
14744 case IcsPlayingBlack:
14745 case IcsPlayingWhite:
14746 DisplayError(_("Warning: You are still playing a game"), 0);
14749 DisplayError(_("Warning: You are still observing a game"), 0);
14752 DisplayError(_("Warning: You are still examining a game"), 0);
14763 first.offeredDraw = second.offeredDraw = 0;
14765 if (gameMode == PlayFromGameFile) {
14766 whiteTimeRemaining = timeRemaining[0][currentMove];
14767 blackTimeRemaining = timeRemaining[1][currentMove];
14771 if (gameMode == MachinePlaysWhite ||
14772 gameMode == MachinePlaysBlack ||
14773 gameMode == TwoMachinesPlay ||
14774 gameMode == EndOfGame) {
14775 i = forwardMostMove;
14776 while (i > currentMove) {
14777 SendToProgram("undo\n", &first);
14780 if(!adjustedClock) {
14781 whiteTimeRemaining = timeRemaining[0][currentMove];
14782 blackTimeRemaining = timeRemaining[1][currentMove];
14783 DisplayBothClocks();
14785 if (whiteFlag || blackFlag) {
14786 whiteFlag = blackFlag = 0;
14791 gameMode = EditGame;
14798 EditPositionEvent ()
14800 if (gameMode == EditPosition) {
14806 if (gameMode != EditGame) return;
14808 gameMode = EditPosition;
14811 if (currentMove > 0)
14812 CopyBoard(boards[0], boards[currentMove]);
14814 blackPlaysFirst = !WhiteOnMove(currentMove);
14816 currentMove = forwardMostMove = backwardMostMove = 0;
14817 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14819 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14825 /* [DM] icsEngineAnalyze - possible call from other functions */
14826 if (appData.icsEngineAnalyze) {
14827 appData.icsEngineAnalyze = FALSE;
14829 DisplayMessage("",_("Close ICS engine analyze..."));
14831 if (first.analysisSupport && first.analyzing) {
14832 SendToBoth("exit\n");
14833 first.analyzing = second.analyzing = FALSE;
14835 thinkOutput[0] = NULLCHAR;
14839 EditPositionDone (Boolean fakeRights)
14841 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14843 startedFromSetupPosition = TRUE;
14844 InitChessProgram(&first, FALSE);
14845 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14846 boards[0][EP_STATUS] = EP_NONE;
14847 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14848 if(boards[0][0][BOARD_WIDTH>>1] == king) {
14849 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14850 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14851 } else boards[0][CASTLING][2] = NoRights;
14852 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14853 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14854 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14855 } else boards[0][CASTLING][5] = NoRights;
14856 if(gameInfo.variant == VariantSChess) {
14858 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14859 boards[0][VIRGIN][i] = 0;
14860 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14861 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14865 SendToProgram("force\n", &first);
14866 if (blackPlaysFirst) {
14867 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14868 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14869 currentMove = forwardMostMove = backwardMostMove = 1;
14870 CopyBoard(boards[1], boards[0]);
14872 currentMove = forwardMostMove = backwardMostMove = 0;
14874 SendBoard(&first, forwardMostMove);
14875 if (appData.debugMode) {
14876 fprintf(debugFP, "EditPosDone\n");
14879 DisplayMessage("", "");
14880 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14881 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14882 gameMode = EditGame;
14884 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14885 ClearHighlights(); /* [AS] */
14888 /* Pause for `ms' milliseconds */
14889 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14891 TimeDelay (long ms)
14898 } while (SubtractTimeMarks(&m2, &m1) < ms);
14901 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14903 SendMultiLineToICS (char *buf)
14905 char temp[MSG_SIZ+1], *p;
14912 strncpy(temp, buf, len);
14917 if (*p == '\n' || *p == '\r')
14922 strcat(temp, "\n");
14924 SendToPlayer(temp, strlen(temp));
14928 SetWhiteToPlayEvent ()
14930 if (gameMode == EditPosition) {
14931 blackPlaysFirst = FALSE;
14932 DisplayBothClocks(); /* works because currentMove is 0 */
14933 } else if (gameMode == IcsExamining) {
14934 SendToICS(ics_prefix);
14935 SendToICS("tomove white\n");
14940 SetBlackToPlayEvent ()
14942 if (gameMode == EditPosition) {
14943 blackPlaysFirst = TRUE;
14944 currentMove = 1; /* kludge */
14945 DisplayBothClocks();
14947 } else if (gameMode == IcsExamining) {
14948 SendToICS(ics_prefix);
14949 SendToICS("tomove black\n");
14954 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14957 ChessSquare piece = boards[0][y][x];
14958 static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14959 static int lastVariant;
14961 if (gameMode != EditPosition && gameMode != IcsExamining) return;
14963 switch (selection) {
14965 CopyBoard(currentBoard, boards[0]);
14966 CopyBoard(menuBoard, initialPosition);
14967 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14968 SendToICS(ics_prefix);
14969 SendToICS("bsetup clear\n");
14970 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14971 SendToICS(ics_prefix);
14972 SendToICS("clearboard\n");
14975 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14976 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14977 for (y = 0; y < BOARD_HEIGHT; y++) {
14978 if (gameMode == IcsExamining) {
14979 if (boards[currentMove][y][x] != EmptySquare) {
14980 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14985 if(boards[0][y][x] != p) nonEmpty++;
14986 boards[0][y][x] = p;
14989 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14991 if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14992 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
14993 ChessSquare p = menuBoard[0][x];
14994 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14995 p = menuBoard[BOARD_HEIGHT-1][x];
14996 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14998 DisplayMessage("Clicking clock again restores position", "");
14999 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15000 if(!nonEmpty) { // asked to clear an empty board
15001 CopyBoard(boards[0], menuBoard);
15003 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15004 CopyBoard(boards[0], initialPosition);
15006 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15007 && !CompareBoards(nullBoard, erasedBoard)) {
15008 CopyBoard(boards[0], erasedBoard);
15010 CopyBoard(erasedBoard, currentBoard);
15014 if (gameMode == EditPosition) {
15015 DrawPosition(FALSE, boards[0]);
15020 SetWhiteToPlayEvent();
15024 SetBlackToPlayEvent();
15028 if (gameMode == IcsExamining) {
15029 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15030 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15033 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15034 if(x == BOARD_LEFT-2) {
15035 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15036 boards[0][y][1] = 0;
15038 if(x == BOARD_RGHT+1) {
15039 if(y >= gameInfo.holdingsSize) break;
15040 boards[0][y][BOARD_WIDTH-2] = 0;
15043 boards[0][y][x] = EmptySquare;
15044 DrawPosition(FALSE, boards[0]);
15049 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15050 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
15051 selection = (ChessSquare) (PROMOTED piece);
15052 } else if(piece == EmptySquare) selection = WhiteSilver;
15053 else selection = (ChessSquare)((int)piece - 1);
15057 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15058 piece > (int)BlackMan && piece <= (int)BlackKing ) {
15059 selection = (ChessSquare) (DEMOTED piece);
15060 } else if(piece == EmptySquare) selection = BlackSilver;
15061 else selection = (ChessSquare)((int)piece + 1);
15066 if(gameInfo.variant == VariantShatranj ||
15067 gameInfo.variant == VariantXiangqi ||
15068 gameInfo.variant == VariantCourier ||
15069 gameInfo.variant == VariantASEAN ||
15070 gameInfo.variant == VariantMakruk )
15071 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15076 if(gameInfo.variant == VariantXiangqi)
15077 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15078 if(gameInfo.variant == VariantKnightmate)
15079 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15082 if (gameMode == IcsExamining) {
15083 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15084 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15085 PieceToChar(selection), AAA + x, ONE + y);
15088 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15090 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15091 n = PieceToNumber(selection - BlackPawn);
15092 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15093 boards[0][BOARD_HEIGHT-1-n][0] = selection;
15094 boards[0][BOARD_HEIGHT-1-n][1]++;
15096 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15097 n = PieceToNumber(selection);
15098 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15099 boards[0][n][BOARD_WIDTH-1] = selection;
15100 boards[0][n][BOARD_WIDTH-2]++;
15103 boards[0][y][x] = selection;
15104 DrawPosition(TRUE, boards[0]);
15106 fromX = fromY = -1;
15114 DropMenuEvent (ChessSquare selection, int x, int y)
15116 ChessMove moveType;
15118 switch (gameMode) {
15119 case IcsPlayingWhite:
15120 case MachinePlaysBlack:
15121 if (!WhiteOnMove(currentMove)) {
15122 DisplayMoveError(_("It is Black's turn"));
15125 moveType = WhiteDrop;
15127 case IcsPlayingBlack:
15128 case MachinePlaysWhite:
15129 if (WhiteOnMove(currentMove)) {
15130 DisplayMoveError(_("It is White's turn"));
15133 moveType = BlackDrop;
15136 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15142 if (moveType == BlackDrop && selection < BlackPawn) {
15143 selection = (ChessSquare) ((int) selection
15144 + (int) BlackPawn - (int) WhitePawn);
15146 if (boards[currentMove][y][x] != EmptySquare) {
15147 DisplayMoveError(_("That square is occupied"));
15151 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15157 /* Accept a pending offer of any kind from opponent */
15159 if (appData.icsActive) {
15160 SendToICS(ics_prefix);
15161 SendToICS("accept\n");
15162 } else if (cmailMsgLoaded) {
15163 if (currentMove == cmailOldMove &&
15164 commentList[cmailOldMove] != NULL &&
15165 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15166 "Black offers a draw" : "White offers a draw")) {
15168 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15169 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15171 DisplayError(_("There is no pending offer on this move"), 0);
15172 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15175 /* Not used for offers from chess program */
15182 /* Decline a pending offer of any kind from opponent */
15184 if (appData.icsActive) {
15185 SendToICS(ics_prefix);
15186 SendToICS("decline\n");
15187 } else if (cmailMsgLoaded) {
15188 if (currentMove == cmailOldMove &&
15189 commentList[cmailOldMove] != NULL &&
15190 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15191 "Black offers a draw" : "White offers a draw")) {
15193 AppendComment(cmailOldMove, "Draw declined", TRUE);
15194 DisplayComment(cmailOldMove - 1, "Draw declined");
15197 DisplayError(_("There is no pending offer on this move"), 0);
15200 /* Not used for offers from chess program */
15207 /* Issue ICS rematch command */
15208 if (appData.icsActive) {
15209 SendToICS(ics_prefix);
15210 SendToICS("rematch\n");
15217 /* Call your opponent's flag (claim a win on time) */
15218 if (appData.icsActive) {
15219 SendToICS(ics_prefix);
15220 SendToICS("flag\n");
15222 switch (gameMode) {
15225 case MachinePlaysWhite:
15228 GameEnds(GameIsDrawn, "Both players ran out of time",
15231 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15233 DisplayError(_("Your opponent is not out of time"), 0);
15236 case MachinePlaysBlack:
15239 GameEnds(GameIsDrawn, "Both players ran out of time",
15242 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15244 DisplayError(_("Your opponent is not out of time"), 0);
15252 ClockClick (int which)
15253 { // [HGM] code moved to back-end from winboard.c
15254 if(which) { // black clock
15255 if (gameMode == EditPosition || gameMode == IcsExamining) {
15256 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15257 SetBlackToPlayEvent();
15258 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15259 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15260 } else if (shiftKey) {
15261 AdjustClock(which, -1);
15262 } else if (gameMode == IcsPlayingWhite ||
15263 gameMode == MachinePlaysBlack) {
15266 } else { // white clock
15267 if (gameMode == EditPosition || gameMode == IcsExamining) {
15268 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15269 SetWhiteToPlayEvent();
15270 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15271 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15272 } else if (shiftKey) {
15273 AdjustClock(which, -1);
15274 } else if (gameMode == IcsPlayingBlack ||
15275 gameMode == MachinePlaysWhite) {
15284 /* Offer draw or accept pending draw offer from opponent */
15286 if (appData.icsActive) {
15287 /* Note: tournament rules require draw offers to be
15288 made after you make your move but before you punch
15289 your clock. Currently ICS doesn't let you do that;
15290 instead, you immediately punch your clock after making
15291 a move, but you can offer a draw at any time. */
15293 SendToICS(ics_prefix);
15294 SendToICS("draw\n");
15295 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15296 } else if (cmailMsgLoaded) {
15297 if (currentMove == cmailOldMove &&
15298 commentList[cmailOldMove] != NULL &&
15299 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15300 "Black offers a draw" : "White offers a draw")) {
15301 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15302 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15303 } else if (currentMove == cmailOldMove + 1) {
15304 char *offer = WhiteOnMove(cmailOldMove) ?
15305 "White offers a draw" : "Black offers a draw";
15306 AppendComment(currentMove, offer, TRUE);
15307 DisplayComment(currentMove - 1, offer);
15308 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15310 DisplayError(_("You must make your move before offering a draw"), 0);
15311 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15313 } else if (first.offeredDraw) {
15314 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15316 if (first.sendDrawOffers) {
15317 SendToProgram("draw\n", &first);
15318 userOfferedDraw = TRUE;
15326 /* Offer Adjourn or accept pending Adjourn offer from opponent */
15328 if (appData.icsActive) {
15329 SendToICS(ics_prefix);
15330 SendToICS("adjourn\n");
15332 /* Currently GNU Chess doesn't offer or accept Adjourns */
15340 /* Offer Abort or accept pending Abort offer from opponent */
15342 if (appData.icsActive) {
15343 SendToICS(ics_prefix);
15344 SendToICS("abort\n");
15346 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15353 /* Resign. You can do this even if it's not your turn. */
15355 if (appData.icsActive) {
15356 SendToICS(ics_prefix);
15357 SendToICS("resign\n");
15359 switch (gameMode) {
15360 case MachinePlaysWhite:
15361 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15363 case MachinePlaysBlack:
15364 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15367 if (cmailMsgLoaded) {
15369 if (WhiteOnMove(cmailOldMove)) {
15370 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15372 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15374 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15385 StopObservingEvent ()
15387 /* Stop observing current games */
15388 SendToICS(ics_prefix);
15389 SendToICS("unobserve\n");
15393 StopExaminingEvent ()
15395 /* Stop observing current game */
15396 SendToICS(ics_prefix);
15397 SendToICS("unexamine\n");
15401 ForwardInner (int target)
15403 int limit; int oldSeekGraphUp = seekGraphUp;
15405 if (appData.debugMode)
15406 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15407 target, currentMove, forwardMostMove);
15409 if (gameMode == EditPosition)
15412 seekGraphUp = FALSE;
15413 MarkTargetSquares(1);
15415 if (gameMode == PlayFromGameFile && !pausing)
15418 if (gameMode == IcsExamining && pausing)
15419 limit = pauseExamForwardMostMove;
15421 limit = forwardMostMove;
15423 if (target > limit) target = limit;
15425 if (target > 0 && moveList[target - 1][0]) {
15426 int fromX, fromY, toX, toY;
15427 toX = moveList[target - 1][2] - AAA;
15428 toY = moveList[target - 1][3] - ONE;
15429 if (moveList[target - 1][1] == '@') {
15430 if (appData.highlightLastMove) {
15431 SetHighlights(-1, -1, toX, toY);
15434 fromX = moveList[target - 1][0] - AAA;
15435 fromY = moveList[target - 1][1] - ONE;
15436 if (target == currentMove + 1) {
15437 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15439 if (appData.highlightLastMove) {
15440 SetHighlights(fromX, fromY, toX, toY);
15444 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15445 gameMode == Training || gameMode == PlayFromGameFile ||
15446 gameMode == AnalyzeFile) {
15447 while (currentMove < target) {
15448 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15449 SendMoveToProgram(currentMove++, &first);
15452 currentMove = target;
15455 if (gameMode == EditGame || gameMode == EndOfGame) {
15456 whiteTimeRemaining = timeRemaining[0][currentMove];
15457 blackTimeRemaining = timeRemaining[1][currentMove];
15459 DisplayBothClocks();
15460 DisplayMove(currentMove - 1);
15461 DrawPosition(oldSeekGraphUp, boards[currentMove]);
15462 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15463 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15464 DisplayComment(currentMove - 1, commentList[currentMove]);
15466 ClearMap(); // [HGM] exclude: invalidate map
15473 if (gameMode == IcsExamining && !pausing) {
15474 SendToICS(ics_prefix);
15475 SendToICS("forward\n");
15477 ForwardInner(currentMove + 1);
15484 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15485 /* to optimze, we temporarily turn off analysis mode while we feed
15486 * the remaining moves to the engine. Otherwise we get analysis output
15489 if (first.analysisSupport) {
15490 SendToProgram("exit\nforce\n", &first);
15491 first.analyzing = FALSE;
15495 if (gameMode == IcsExamining && !pausing) {
15496 SendToICS(ics_prefix);
15497 SendToICS("forward 999999\n");
15499 ForwardInner(forwardMostMove);
15502 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15503 /* we have fed all the moves, so reactivate analysis mode */
15504 SendToProgram("analyze\n", &first);
15505 first.analyzing = TRUE;
15506 /*first.maybeThinking = TRUE;*/
15507 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15512 BackwardInner (int target)
15514 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15516 if (appData.debugMode)
15517 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15518 target, currentMove, forwardMostMove);
15520 if (gameMode == EditPosition) return;
15521 seekGraphUp = FALSE;
15522 MarkTargetSquares(1);
15523 if (currentMove <= backwardMostMove) {
15525 DrawPosition(full_redraw, boards[currentMove]);
15528 if (gameMode == PlayFromGameFile && !pausing)
15531 if (moveList[target][0]) {
15532 int fromX, fromY, toX, toY;
15533 toX = moveList[target][2] - AAA;
15534 toY = moveList[target][3] - ONE;
15535 if (moveList[target][1] == '@') {
15536 if (appData.highlightLastMove) {
15537 SetHighlights(-1, -1, toX, toY);
15540 fromX = moveList[target][0] - AAA;
15541 fromY = moveList[target][1] - ONE;
15542 if (target == currentMove - 1) {
15543 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15545 if (appData.highlightLastMove) {
15546 SetHighlights(fromX, fromY, toX, toY);
15550 if (gameMode == EditGame || gameMode==AnalyzeMode ||
15551 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15552 while (currentMove > target) {
15553 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15554 // null move cannot be undone. Reload program with move history before it.
15556 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15557 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15559 SendBoard(&first, i);
15560 if(second.analyzing) SendBoard(&second, i);
15561 for(currentMove=i; currentMove<target; currentMove++) {
15562 SendMoveToProgram(currentMove, &first);
15563 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15567 SendToBoth("undo\n");
15571 currentMove = target;
15574 if (gameMode == EditGame || gameMode == EndOfGame) {
15575 whiteTimeRemaining = timeRemaining[0][currentMove];
15576 blackTimeRemaining = timeRemaining[1][currentMove];
15578 DisplayBothClocks();
15579 DisplayMove(currentMove - 1);
15580 DrawPosition(full_redraw, boards[currentMove]);
15581 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15582 // [HGM] PV info: routine tests if comment empty
15583 DisplayComment(currentMove - 1, commentList[currentMove]);
15584 ClearMap(); // [HGM] exclude: invalidate map
15590 if (gameMode == IcsExamining && !pausing) {
15591 SendToICS(ics_prefix);
15592 SendToICS("backward\n");
15594 BackwardInner(currentMove - 1);
15601 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15602 /* to optimize, we temporarily turn off analysis mode while we undo
15603 * all the moves. Otherwise we get analysis output after each undo.
15605 if (first.analysisSupport) {
15606 SendToProgram("exit\nforce\n", &first);
15607 first.analyzing = FALSE;
15611 if (gameMode == IcsExamining && !pausing) {
15612 SendToICS(ics_prefix);
15613 SendToICS("backward 999999\n");
15615 BackwardInner(backwardMostMove);
15618 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15619 /* we have fed all the moves, so reactivate analysis mode */
15620 SendToProgram("analyze\n", &first);
15621 first.analyzing = TRUE;
15622 /*first.maybeThinking = TRUE;*/
15623 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15630 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15631 if (to >= forwardMostMove) to = forwardMostMove;
15632 if (to <= backwardMostMove) to = backwardMostMove;
15633 if (to < currentMove) {
15641 RevertEvent (Boolean annotate)
15643 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15646 if (gameMode != IcsExamining) {
15647 DisplayError(_("You are not examining a game"), 0);
15651 DisplayError(_("You can't revert while pausing"), 0);
15654 SendToICS(ics_prefix);
15655 SendToICS("revert\n");
15659 RetractMoveEvent ()
15661 switch (gameMode) {
15662 case MachinePlaysWhite:
15663 case MachinePlaysBlack:
15664 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15665 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15668 if (forwardMostMove < 2) return;
15669 currentMove = forwardMostMove = forwardMostMove - 2;
15670 whiteTimeRemaining = timeRemaining[0][currentMove];
15671 blackTimeRemaining = timeRemaining[1][currentMove];
15672 DisplayBothClocks();
15673 DisplayMove(currentMove - 1);
15674 ClearHighlights();/*!! could figure this out*/
15675 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15676 SendToProgram("remove\n", &first);
15677 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15680 case BeginningOfGame:
15684 case IcsPlayingWhite:
15685 case IcsPlayingBlack:
15686 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15687 SendToICS(ics_prefix);
15688 SendToICS("takeback 2\n");
15690 SendToICS(ics_prefix);
15691 SendToICS("takeback 1\n");
15700 ChessProgramState *cps;
15702 switch (gameMode) {
15703 case MachinePlaysWhite:
15704 if (!WhiteOnMove(forwardMostMove)) {
15705 DisplayError(_("It is your turn"), 0);
15710 case MachinePlaysBlack:
15711 if (WhiteOnMove(forwardMostMove)) {
15712 DisplayError(_("It is your turn"), 0);
15717 case TwoMachinesPlay:
15718 if (WhiteOnMove(forwardMostMove) ==
15719 (first.twoMachinesColor[0] == 'w')) {
15725 case BeginningOfGame:
15729 SendToProgram("?\n", cps);
15733 TruncateGameEvent ()
15736 if (gameMode != EditGame) return;
15743 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15744 if (forwardMostMove > currentMove) {
15745 if (gameInfo.resultDetails != NULL) {
15746 free(gameInfo.resultDetails);
15747 gameInfo.resultDetails = NULL;
15748 gameInfo.result = GameUnfinished;
15750 forwardMostMove = currentMove;
15751 HistorySet(parseList, backwardMostMove, forwardMostMove,
15759 if (appData.noChessProgram) return;
15760 switch (gameMode) {
15761 case MachinePlaysWhite:
15762 if (WhiteOnMove(forwardMostMove)) {
15763 DisplayError(_("Wait until your turn."), 0);
15767 case BeginningOfGame:
15768 case MachinePlaysBlack:
15769 if (!WhiteOnMove(forwardMostMove)) {
15770 DisplayError(_("Wait until your turn."), 0);
15775 DisplayError(_("No hint available"), 0);
15778 SendToProgram("hint\n", &first);
15779 hintRequested = TRUE;
15785 ListGame * lg = (ListGame *) gameList.head;
15788 static int secondTime = FALSE;
15790 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15791 DisplayError(_("Game list not loaded or empty"), 0);
15795 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15798 DisplayNote(_("Book file exists! Try again for overwrite."));
15802 creatingBook = TRUE;
15803 secondTime = FALSE;
15805 /* Get list size */
15806 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15807 LoadGame(f, nItem, "", TRUE);
15808 AddGameToBook(TRUE);
15809 lg = (ListGame *) lg->node.succ;
15812 creatingBook = FALSE;
15819 if (appData.noChessProgram) return;
15820 switch (gameMode) {
15821 case MachinePlaysWhite:
15822 if (WhiteOnMove(forwardMostMove)) {
15823 DisplayError(_("Wait until your turn."), 0);
15827 case BeginningOfGame:
15828 case MachinePlaysBlack:
15829 if (!WhiteOnMove(forwardMostMove)) {
15830 DisplayError(_("Wait until your turn."), 0);
15835 EditPositionDone(TRUE);
15837 case TwoMachinesPlay:
15842 SendToProgram("bk\n", &first);
15843 bookOutput[0] = NULLCHAR;
15844 bookRequested = TRUE;
15850 char *tags = PGNTags(&gameInfo);
15851 TagsPopUp(tags, CmailMsg());
15855 /* end button procedures */
15858 PrintPosition (FILE *fp, int move)
15862 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15863 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15864 char c = PieceToChar(boards[move][i][j]);
15865 fputc(c == 'x' ? '.' : c, fp);
15866 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15869 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15870 fprintf(fp, "white to play\n");
15872 fprintf(fp, "black to play\n");
15876 PrintOpponents (FILE *fp)
15878 if (gameInfo.white != NULL) {
15879 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15885 /* Find last component of program's own name, using some heuristics */
15887 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15890 int local = (strcmp(host, "localhost") == 0);
15891 while (!local && (p = strchr(prog, ';')) != NULL) {
15893 while (*p == ' ') p++;
15896 if (*prog == '"' || *prog == '\'') {
15897 q = strchr(prog + 1, *prog);
15899 q = strchr(prog, ' ');
15901 if (q == NULL) q = prog + strlen(prog);
15903 while (p >= prog && *p != '/' && *p != '\\') p--;
15905 if(p == prog && *p == '"') p++;
15907 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15908 memcpy(buf, p, q - p);
15909 buf[q - p] = NULLCHAR;
15917 TimeControlTagValue ()
15920 if (!appData.clockMode) {
15921 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15922 } else if (movesPerSession > 0) {
15923 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15924 } else if (timeIncrement == 0) {
15925 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15927 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15929 return StrSave(buf);
15935 /* This routine is used only for certain modes */
15936 VariantClass v = gameInfo.variant;
15937 ChessMove r = GameUnfinished;
15940 if(keepInfo) return;
15942 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15943 r = gameInfo.result;
15944 p = gameInfo.resultDetails;
15945 gameInfo.resultDetails = NULL;
15947 ClearGameInfo(&gameInfo);
15948 gameInfo.variant = v;
15950 switch (gameMode) {
15951 case MachinePlaysWhite:
15952 gameInfo.event = StrSave( appData.pgnEventHeader );
15953 gameInfo.site = StrSave(HostName());
15954 gameInfo.date = PGNDate();
15955 gameInfo.round = StrSave("-");
15956 gameInfo.white = StrSave(first.tidy);
15957 gameInfo.black = StrSave(UserName());
15958 gameInfo.timeControl = TimeControlTagValue();
15961 case MachinePlaysBlack:
15962 gameInfo.event = StrSave( appData.pgnEventHeader );
15963 gameInfo.site = StrSave(HostName());
15964 gameInfo.date = PGNDate();
15965 gameInfo.round = StrSave("-");
15966 gameInfo.white = StrSave(UserName());
15967 gameInfo.black = StrSave(first.tidy);
15968 gameInfo.timeControl = TimeControlTagValue();
15971 case TwoMachinesPlay:
15972 gameInfo.event = StrSave( appData.pgnEventHeader );
15973 gameInfo.site = StrSave(HostName());
15974 gameInfo.date = PGNDate();
15977 snprintf(buf, MSG_SIZ, "%d", roundNr);
15978 gameInfo.round = StrSave(buf);
15980 gameInfo.round = StrSave("-");
15982 if (first.twoMachinesColor[0] == 'w') {
15983 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15984 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15986 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15987 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15989 gameInfo.timeControl = TimeControlTagValue();
15993 gameInfo.event = StrSave("Edited game");
15994 gameInfo.site = StrSave(HostName());
15995 gameInfo.date = PGNDate();
15996 gameInfo.round = StrSave("-");
15997 gameInfo.white = StrSave("-");
15998 gameInfo.black = StrSave("-");
15999 gameInfo.result = r;
16000 gameInfo.resultDetails = p;
16004 gameInfo.event = StrSave("Edited position");
16005 gameInfo.site = StrSave(HostName());
16006 gameInfo.date = PGNDate();
16007 gameInfo.round = StrSave("-");
16008 gameInfo.white = StrSave("-");
16009 gameInfo.black = StrSave("-");
16012 case IcsPlayingWhite:
16013 case IcsPlayingBlack:
16018 case PlayFromGameFile:
16019 gameInfo.event = StrSave("Game from non-PGN file");
16020 gameInfo.site = StrSave(HostName());
16021 gameInfo.date = PGNDate();
16022 gameInfo.round = StrSave("-");
16023 gameInfo.white = StrSave("?");
16024 gameInfo.black = StrSave("?");
16033 ReplaceComment (int index, char *text)
16039 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16040 pvInfoList[index-1].depth == len &&
16041 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16042 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16043 while (*text == '\n') text++;
16044 len = strlen(text);
16045 while (len > 0 && text[len - 1] == '\n') len--;
16047 if (commentList[index] != NULL)
16048 free(commentList[index]);
16051 commentList[index] = NULL;
16054 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16055 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16056 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16057 commentList[index] = (char *) malloc(len + 2);
16058 strncpy(commentList[index], text, len);
16059 commentList[index][len] = '\n';
16060 commentList[index][len + 1] = NULLCHAR;
16062 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16064 commentList[index] = (char *) malloc(len + 7);
16065 safeStrCpy(commentList[index], "{\n", 3);
16066 safeStrCpy(commentList[index]+2, text, len+1);
16067 commentList[index][len+2] = NULLCHAR;
16068 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16069 strcat(commentList[index], "\n}\n");
16074 CrushCRs (char *text)
16082 if (ch == '\r') continue;
16084 } while (ch != '\0');
16088 AppendComment (int index, char *text, Boolean addBraces)
16089 /* addBraces tells if we should add {} */
16094 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16095 if(addBraces == 3) addBraces = 0; else // force appending literally
16096 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16099 while (*text == '\n') text++;
16100 len = strlen(text);
16101 while (len > 0 && text[len - 1] == '\n') len--;
16102 text[len] = NULLCHAR;
16104 if (len == 0) return;
16106 if (commentList[index] != NULL) {
16107 Boolean addClosingBrace = addBraces;
16108 old = commentList[index];
16109 oldlen = strlen(old);
16110 while(commentList[index][oldlen-1] == '\n')
16111 commentList[index][--oldlen] = NULLCHAR;
16112 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16113 safeStrCpy(commentList[index], old, oldlen + len + 6);
16115 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16116 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16117 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16118 while (*text == '\n') { text++; len--; }
16119 commentList[index][--oldlen] = NULLCHAR;
16121 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16122 else strcat(commentList[index], "\n");
16123 strcat(commentList[index], text);
16124 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16125 else strcat(commentList[index], "\n");
16127 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16129 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16130 else commentList[index][0] = NULLCHAR;
16131 strcat(commentList[index], text);
16132 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16133 if(addBraces == TRUE) strcat(commentList[index], "}\n");
16138 FindStr (char * text, char * sub_text)
16140 char * result = strstr( text, sub_text );
16142 if( result != NULL ) {
16143 result += strlen( sub_text );
16149 /* [AS] Try to extract PV info from PGN comment */
16150 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16152 GetInfoFromComment (int index, char * text)
16154 char * sep = text, *p;
16156 if( text != NULL && index > 0 ) {
16159 int time = -1, sec = 0, deci;
16160 char * s_eval = FindStr( text, "[%eval " );
16161 char * s_emt = FindStr( text, "[%emt " );
16163 if( s_eval != NULL || s_emt != NULL ) {
16165 if(0) { // [HGM] this code is not finished, and could actually be detrimental
16170 if( s_eval != NULL ) {
16171 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16175 if( delim != ']' ) {
16180 if( s_emt != NULL ) {
16185 /* We expect something like: [+|-]nnn.nn/dd */
16188 if(*text != '{') return text; // [HGM] braces: must be normal comment
16190 sep = strchr( text, '/' );
16191 if( sep == NULL || sep < (text+4) ) {
16196 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16197 if(p[1] == '(') { // comment starts with PV
16198 p = strchr(p, ')'); // locate end of PV
16199 if(p == NULL || sep < p+5) return text;
16200 // at this point we have something like "{(.*) +0.23/6 ..."
16201 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16202 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16203 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16205 time = -1; sec = -1; deci = -1;
16206 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16207 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16208 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16209 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
16213 if( score_lo < 0 || score_lo >= 100 ) {
16217 if(sec >= 0) time = 600*time + 10*sec; else
16218 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16220 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16222 /* [HGM] PV time: now locate end of PV info */
16223 while( *++sep >= '0' && *sep <= '9'); // strip depth
16225 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16227 while( *++sep >= '0' && *sep <= '9'); // strip seconds
16229 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16230 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16241 pvInfoList[index-1].depth = depth;
16242 pvInfoList[index-1].score = score;
16243 pvInfoList[index-1].time = 10*time; // centi-sec
16244 if(*sep == '}') *sep = 0; else *--sep = '{';
16245 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16251 SendToProgram (char *message, ChessProgramState *cps)
16253 int count, outCount, error;
16256 if (cps->pr == NoProc) return;
16259 if (appData.debugMode) {
16262 fprintf(debugFP, "%ld >%-6s: %s",
16263 SubtractTimeMarks(&now, &programStartTime),
16264 cps->which, message);
16266 fprintf(serverFP, "%ld >%-6s: %s",
16267 SubtractTimeMarks(&now, &programStartTime),
16268 cps->which, message), fflush(serverFP);
16271 count = strlen(message);
16272 outCount = OutputToProcess(cps->pr, message, count, &error);
16273 if (outCount < count && !exiting
16274 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16275 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16276 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16277 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16278 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16279 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16280 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16281 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16283 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16284 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16285 gameInfo.result = res;
16287 gameInfo.resultDetails = StrSave(buf);
16289 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16290 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16295 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16299 ChessProgramState *cps = (ChessProgramState *)closure;
16301 if (isr != cps->isr) return; /* Killed intentionally */
16304 RemoveInputSource(cps->isr);
16305 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16306 _(cps->which), cps->program);
16307 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16308 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16309 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16310 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16311 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16312 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16314 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16315 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16316 gameInfo.result = res;
16318 gameInfo.resultDetails = StrSave(buf);
16320 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16321 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16323 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16324 _(cps->which), cps->program);
16325 RemoveInputSource(cps->isr);
16327 /* [AS] Program is misbehaving badly... kill it */
16328 if( count == -2 ) {
16329 DestroyChildProcess( cps->pr, 9 );
16333 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16338 if ((end_str = strchr(message, '\r')) != NULL)
16339 *end_str = NULLCHAR;
16340 if ((end_str = strchr(message, '\n')) != NULL)
16341 *end_str = NULLCHAR;
16343 if (appData.debugMode) {
16344 TimeMark now; int print = 1;
16345 char *quote = ""; char c; int i;
16347 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16348 char start = message[0];
16349 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16350 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16351 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
16352 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16353 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16354 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
16355 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16356 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
16357 sscanf(message, "hint: %c", &c)!=1 &&
16358 sscanf(message, "pong %c", &c)!=1 && start != '#') {
16359 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16360 print = (appData.engineComments >= 2);
16362 message[0] = start; // restore original message
16366 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16367 SubtractTimeMarks(&now, &programStartTime), cps->which,
16371 fprintf(serverFP, "%ld <%-6s: %s%s\n",
16372 SubtractTimeMarks(&now, &programStartTime), cps->which,
16374 message), fflush(serverFP);
16378 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16379 if (appData.icsEngineAnalyze) {
16380 if (strstr(message, "whisper") != NULL ||
16381 strstr(message, "kibitz") != NULL ||
16382 strstr(message, "tellics") != NULL) return;
16385 HandleMachineMove(message, cps);
16390 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16395 if( timeControl_2 > 0 ) {
16396 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16397 tc = timeControl_2;
16400 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16401 inc /= cps->timeOdds;
16402 st /= cps->timeOdds;
16404 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16407 /* Set exact time per move, normally using st command */
16408 if (cps->stKludge) {
16409 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16411 if (seconds == 0) {
16412 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16414 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16417 snprintf(buf, MSG_SIZ, "st %d\n", st);
16420 /* Set conventional or incremental time control, using level command */
16421 if (seconds == 0) {
16422 /* Note old gnuchess bug -- minutes:seconds used to not work.
16423 Fixed in later versions, but still avoid :seconds
16424 when seconds is 0. */
16425 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16427 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16428 seconds, inc/1000.);
16431 SendToProgram(buf, cps);
16433 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16434 /* Orthogonally, limit search to given depth */
16436 if (cps->sdKludge) {
16437 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16439 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16441 SendToProgram(buf, cps);
16444 if(cps->nps >= 0) { /* [HGM] nps */
16445 if(cps->supportsNPS == FALSE)
16446 cps->nps = -1; // don't use if engine explicitly says not supported!
16448 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16449 SendToProgram(buf, cps);
16454 ChessProgramState *
16456 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16458 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16459 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16465 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16467 char message[MSG_SIZ];
16470 /* Note: this routine must be called when the clocks are stopped
16471 or when they have *just* been set or switched; otherwise
16472 it will be off by the time since the current tick started.
16474 if (machineWhite) {
16475 time = whiteTimeRemaining / 10;
16476 otime = blackTimeRemaining / 10;
16478 time = blackTimeRemaining / 10;
16479 otime = whiteTimeRemaining / 10;
16481 /* [HGM] translate opponent's time by time-odds factor */
16482 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16484 if (time <= 0) time = 1;
16485 if (otime <= 0) otime = 1;
16487 snprintf(message, MSG_SIZ, "time %ld\n", time);
16488 SendToProgram(message, cps);
16490 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16491 SendToProgram(message, cps);
16495 EngineDefinedVariant (ChessProgramState *cps, int n)
16496 { // return name of n-th unknown variant that engine supports
16497 static char buf[MSG_SIZ];
16498 char *p, *s = cps->variants;
16499 if(!s) return NULL;
16500 do { // parse string from variants feature
16502 p = strchr(s, ',');
16503 if(p) *p = NULLCHAR;
16504 v = StringToVariant(s);
16505 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16506 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16507 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16510 if(n < 0) return buf;
16516 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16519 int len = strlen(name);
16522 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16524 sscanf(*p, "%d", &val);
16526 while (**p && **p != ' ')
16528 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16529 SendToProgram(buf, cps);
16536 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16539 int len = strlen(name);
16540 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16542 sscanf(*p, "%d", loc);
16543 while (**p && **p != ' ') (*p)++;
16544 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16545 SendToProgram(buf, cps);
16552 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16555 int len = strlen(name);
16556 if (strncmp((*p), name, len) == 0
16557 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16559 ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16560 sscanf(*p, "%[^\"]", *loc);
16561 while (**p && **p != '\"') (*p)++;
16562 if (**p == '\"') (*p)++;
16563 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16564 SendToProgram(buf, cps);
16571 ParseOption (Option *opt, ChessProgramState *cps)
16572 // [HGM] options: process the string that defines an engine option, and determine
16573 // name, type, default value, and allowed value range
16575 char *p, *q, buf[MSG_SIZ];
16576 int n, min = (-1)<<31, max = 1<<31, def;
16578 if(p = strstr(opt->name, " -spin ")) {
16579 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16580 if(max < min) max = min; // enforce consistency
16581 if(def < min) def = min;
16582 if(def > max) def = max;
16587 } else if((p = strstr(opt->name, " -slider "))) {
16588 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16589 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16590 if(max < min) max = min; // enforce consistency
16591 if(def < min) def = min;
16592 if(def > max) def = max;
16596 opt->type = Spin; // Slider;
16597 } else if((p = strstr(opt->name, " -string "))) {
16598 opt->textValue = p+9;
16599 opt->type = TextBox;
16600 } else if((p = strstr(opt->name, " -file "))) {
16601 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16602 opt->textValue = p+7;
16603 opt->type = FileName; // FileName;
16604 } else if((p = strstr(opt->name, " -path "))) {
16605 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16606 opt->textValue = p+7;
16607 opt->type = PathName; // PathName;
16608 } else if(p = strstr(opt->name, " -check ")) {
16609 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16610 opt->value = (def != 0);
16611 opt->type = CheckBox;
16612 } else if(p = strstr(opt->name, " -combo ")) {
16613 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16614 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16615 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16616 opt->value = n = 0;
16617 while(q = StrStr(q, " /// ")) {
16618 n++; *q = 0; // count choices, and null-terminate each of them
16620 if(*q == '*') { // remember default, which is marked with * prefix
16624 cps->comboList[cps->comboCnt++] = q;
16626 cps->comboList[cps->comboCnt++] = NULL;
16628 opt->type = ComboBox;
16629 } else if(p = strstr(opt->name, " -button")) {
16630 opt->type = Button;
16631 } else if(p = strstr(opt->name, " -save")) {
16632 opt->type = SaveButton;
16633 } else return FALSE;
16634 *p = 0; // terminate option name
16635 // now look if the command-line options define a setting for this engine option.
16636 if(cps->optionSettings && cps->optionSettings[0])
16637 p = strstr(cps->optionSettings, opt->name); else p = NULL;
16638 if(p && (p == cps->optionSettings || p[-1] == ',')) {
16639 snprintf(buf, MSG_SIZ, "option %s", p);
16640 if(p = strstr(buf, ",")) *p = 0;
16641 if(q = strchr(buf, '=')) switch(opt->type) {
16643 for(n=0; n<opt->max; n++)
16644 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16647 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16651 opt->value = atoi(q+1);
16656 SendToProgram(buf, cps);
16662 FeatureDone (ChessProgramState *cps, int val)
16664 DelayedEventCallback cb = GetDelayedEvent();
16665 if ((cb == InitBackEnd3 && cps == &first) ||
16666 (cb == SettingsMenuIfReady && cps == &second) ||
16667 (cb == LoadEngine) ||
16668 (cb == TwoMachinesEventIfReady)) {
16669 CancelDelayedEvent();
16670 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16672 cps->initDone = val;
16673 if(val) cps->reload = FALSE;
16676 /* Parse feature command from engine */
16678 ParseFeatures (char *args, ChessProgramState *cps)
16686 while (*p == ' ') p++;
16687 if (*p == NULLCHAR) return;
16689 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16690 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16691 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16692 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16693 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16694 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16695 if (BoolFeature(&p, "reuse", &val, cps)) {
16696 /* Engine can disable reuse, but can't enable it if user said no */
16697 if (!val) cps->reuse = FALSE;
16700 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16701 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16702 if (gameMode == TwoMachinesPlay) {
16703 DisplayTwoMachinesTitle();
16709 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16710 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16711 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16712 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16713 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16714 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16715 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16716 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16717 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16718 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16719 if (IntFeature(&p, "done", &val, cps)) {
16720 FeatureDone(cps, val);
16723 /* Added by Tord: */
16724 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16725 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16726 /* End of additions by Tord */
16728 /* [HGM] added features: */
16729 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16730 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16731 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16732 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16733 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16734 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16735 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16736 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16737 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16738 FREE(cps->option[cps->nrOptions].name);
16739 cps->option[cps->nrOptions].name = q; q = NULL;
16740 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16741 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16742 SendToProgram(buf, cps);
16745 if(cps->nrOptions >= MAX_OPTIONS) {
16747 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16748 DisplayError(buf, 0);
16752 /* End of additions by HGM */
16754 /* unknown feature: complain and skip */
16756 while (*q && *q != '=') q++;
16757 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16758 SendToProgram(buf, cps);
16764 while (*p && *p != '\"') p++;
16765 if (*p == '\"') p++;
16767 while (*p && *p != ' ') p++;
16775 PeriodicUpdatesEvent (int newState)
16777 if (newState == appData.periodicUpdates)
16780 appData.periodicUpdates=newState;
16782 /* Display type changes, so update it now */
16783 // DisplayAnalysis();
16785 /* Get the ball rolling again... */
16787 AnalysisPeriodicEvent(1);
16788 StartAnalysisClock();
16793 PonderNextMoveEvent (int newState)
16795 if (newState == appData.ponderNextMove) return;
16796 if (gameMode == EditPosition) EditPositionDone(TRUE);
16798 SendToProgram("hard\n", &first);
16799 if (gameMode == TwoMachinesPlay) {
16800 SendToProgram("hard\n", &second);
16803 SendToProgram("easy\n", &first);
16804 thinkOutput[0] = NULLCHAR;
16805 if (gameMode == TwoMachinesPlay) {
16806 SendToProgram("easy\n", &second);
16809 appData.ponderNextMove = newState;
16813 NewSettingEvent (int option, int *feature, char *command, int value)
16817 if (gameMode == EditPosition) EditPositionDone(TRUE);
16818 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16819 if(feature == NULL || *feature) SendToProgram(buf, &first);
16820 if (gameMode == TwoMachinesPlay) {
16821 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16826 ShowThinkingEvent ()
16827 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16829 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16830 int newState = appData.showThinking
16831 // [HGM] thinking: other features now need thinking output as well
16832 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16834 if (oldState == newState) return;
16835 oldState = newState;
16836 if (gameMode == EditPosition) EditPositionDone(TRUE);
16838 SendToProgram("post\n", &first);
16839 if (gameMode == TwoMachinesPlay) {
16840 SendToProgram("post\n", &second);
16843 SendToProgram("nopost\n", &first);
16844 thinkOutput[0] = NULLCHAR;
16845 if (gameMode == TwoMachinesPlay) {
16846 SendToProgram("nopost\n", &second);
16849 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16853 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16855 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16856 if (pr == NoProc) return;
16857 AskQuestion(title, question, replyPrefix, pr);
16861 TypeInEvent (char firstChar)
16863 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16864 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16865 gameMode == AnalyzeMode || gameMode == EditGame ||
16866 gameMode == EditPosition || gameMode == IcsExamining ||
16867 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16868 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16869 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16870 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
16871 gameMode == Training) PopUpMoveDialog(firstChar);
16875 TypeInDoneEvent (char *move)
16878 int n, fromX, fromY, toX, toY;
16880 ChessMove moveType;
16883 if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16884 EditPositionPasteFEN(move);
16887 // [HGM] movenum: allow move number to be typed in any mode
16888 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16892 // undocumented kludge: allow command-line option to be typed in!
16893 // (potentially fatal, and does not implement the effect of the option.)
16894 // should only be used for options that are values on which future decisions will be made,
16895 // and definitely not on options that would be used during initialization.
16896 if(strstr(move, "!!! -") == move) {
16897 ParseArgsFromString(move+4);
16901 if (gameMode != EditGame && currentMove != forwardMostMove &&
16902 gameMode != Training) {
16903 DisplayMoveError(_("Displayed move is not current"));
16905 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16906 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16907 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16908 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16909 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16910 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16912 DisplayMoveError(_("Could not parse move"));
16918 DisplayMove (int moveNumber)
16920 char message[MSG_SIZ];
16922 char cpThinkOutput[MSG_SIZ];
16924 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16926 if (moveNumber == forwardMostMove - 1 ||
16927 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16929 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16931 if (strchr(cpThinkOutput, '\n')) {
16932 *strchr(cpThinkOutput, '\n') = NULLCHAR;
16935 *cpThinkOutput = NULLCHAR;
16938 /* [AS] Hide thinking from human user */
16939 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16940 *cpThinkOutput = NULLCHAR;
16941 if( thinkOutput[0] != NULLCHAR ) {
16944 for( i=0; i<=hiddenThinkOutputState; i++ ) {
16945 cpThinkOutput[i] = '.';
16947 cpThinkOutput[i] = NULLCHAR;
16948 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16952 if (moveNumber == forwardMostMove - 1 &&
16953 gameInfo.resultDetails != NULL) {
16954 if (gameInfo.resultDetails[0] == NULLCHAR) {
16955 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16957 snprintf(res, MSG_SIZ, " {%s} %s",
16958 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16964 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16965 DisplayMessage(res, cpThinkOutput);
16967 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16968 WhiteOnMove(moveNumber) ? " " : ".. ",
16969 parseList[moveNumber], res);
16970 DisplayMessage(message, cpThinkOutput);
16975 DisplayComment (int moveNumber, char *text)
16977 char title[MSG_SIZ];
16979 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16980 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16982 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16983 WhiteOnMove(moveNumber) ? " " : ".. ",
16984 parseList[moveNumber]);
16986 if (text != NULL && (appData.autoDisplayComment || commentUp))
16987 CommentPopUp(title, text);
16990 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16991 * might be busy thinking or pondering. It can be omitted if your
16992 * gnuchess is configured to stop thinking immediately on any user
16993 * input. However, that gnuchess feature depends on the FIONREAD
16994 * ioctl, which does not work properly on some flavors of Unix.
16997 Attention (ChessProgramState *cps)
17000 if (!cps->useSigint) return;
17001 if (appData.noChessProgram || (cps->pr == NoProc)) return;
17002 switch (gameMode) {
17003 case MachinePlaysWhite:
17004 case MachinePlaysBlack:
17005 case TwoMachinesPlay:
17006 case IcsPlayingWhite:
17007 case IcsPlayingBlack:
17010 /* Skip if we know it isn't thinking */
17011 if (!cps->maybeThinking) return;
17012 if (appData.debugMode)
17013 fprintf(debugFP, "Interrupting %s\n", cps->which);
17014 InterruptChildProcess(cps->pr);
17015 cps->maybeThinking = FALSE;
17020 #endif /*ATTENTION*/
17026 if (whiteTimeRemaining <= 0) {
17029 if (appData.icsActive) {
17030 if (appData.autoCallFlag &&
17031 gameMode == IcsPlayingBlack && !blackFlag) {
17032 SendToICS(ics_prefix);
17033 SendToICS("flag\n");
17037 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17039 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17040 if (appData.autoCallFlag) {
17041 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17048 if (blackTimeRemaining <= 0) {
17051 if (appData.icsActive) {
17052 if (appData.autoCallFlag &&
17053 gameMode == IcsPlayingWhite && !whiteFlag) {
17054 SendToICS(ics_prefix);
17055 SendToICS("flag\n");
17059 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17061 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17062 if (appData.autoCallFlag) {
17063 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17074 CheckTimeControl ()
17076 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17077 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17080 * add time to clocks when time control is achieved ([HGM] now also used for increment)
17082 if ( !WhiteOnMove(forwardMostMove) ) {
17083 /* White made time control */
17084 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17085 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17086 /* [HGM] time odds: correct new time quota for time odds! */
17087 / WhitePlayer()->timeOdds;
17088 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17090 lastBlack -= blackTimeRemaining;
17091 /* Black made time control */
17092 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17093 / WhitePlayer()->other->timeOdds;
17094 lastWhite = whiteTimeRemaining;
17099 DisplayBothClocks ()
17101 int wom = gameMode == EditPosition ?
17102 !blackPlaysFirst : WhiteOnMove(currentMove);
17103 DisplayWhiteClock(whiteTimeRemaining, wom);
17104 DisplayBlackClock(blackTimeRemaining, !wom);
17108 /* Timekeeping seems to be a portability nightmare. I think everyone
17109 has ftime(), but I'm really not sure, so I'm including some ifdefs
17110 to use other calls if you don't. Clocks will be less accurate if
17111 you have neither ftime nor gettimeofday.
17114 /* VS 2008 requires the #include outside of the function */
17115 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17116 #include <sys/timeb.h>
17119 /* Get the current time as a TimeMark */
17121 GetTimeMark (TimeMark *tm)
17123 #if HAVE_GETTIMEOFDAY
17125 struct timeval timeVal;
17126 struct timezone timeZone;
17128 gettimeofday(&timeVal, &timeZone);
17129 tm->sec = (long) timeVal.tv_sec;
17130 tm->ms = (int) (timeVal.tv_usec / 1000L);
17132 #else /*!HAVE_GETTIMEOFDAY*/
17135 // include <sys/timeb.h> / moved to just above start of function
17136 struct timeb timeB;
17139 tm->sec = (long) timeB.time;
17140 tm->ms = (int) timeB.millitm;
17142 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17143 tm->sec = (long) time(NULL);
17149 /* Return the difference in milliseconds between two
17150 time marks. We assume the difference will fit in a long!
17153 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17155 return 1000L*(tm2->sec - tm1->sec) +
17156 (long) (tm2->ms - tm1->ms);
17161 * Code to manage the game clocks.
17163 * In tournament play, black starts the clock and then white makes a move.
17164 * We give the human user a slight advantage if he is playing white---the
17165 * clocks don't run until he makes his first move, so it takes zero time.
17166 * Also, we don't account for network lag, so we could get out of sync
17167 * with GNU Chess's clock -- but then, referees are always right.
17170 static TimeMark tickStartTM;
17171 static long intendedTickLength;
17174 NextTickLength (long timeRemaining)
17176 long nominalTickLength, nextTickLength;
17178 if (timeRemaining > 0L && timeRemaining <= 10000L)
17179 nominalTickLength = 100L;
17181 nominalTickLength = 1000L;
17182 nextTickLength = timeRemaining % nominalTickLength;
17183 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17185 return nextTickLength;
17188 /* Adjust clock one minute up or down */
17190 AdjustClock (Boolean which, int dir)
17192 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17193 if(which) blackTimeRemaining += 60000*dir;
17194 else whiteTimeRemaining += 60000*dir;
17195 DisplayBothClocks();
17196 adjustedClock = TRUE;
17199 /* Stop clocks and reset to a fresh time control */
17203 (void) StopClockTimer();
17204 if (appData.icsActive) {
17205 whiteTimeRemaining = blackTimeRemaining = 0;
17206 } else if (searchTime) {
17207 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17208 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17209 } else { /* [HGM] correct new time quote for time odds */
17210 whiteTC = blackTC = fullTimeControlString;
17211 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17212 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17214 if (whiteFlag || blackFlag) {
17216 whiteFlag = blackFlag = FALSE;
17218 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17219 DisplayBothClocks();
17220 adjustedClock = FALSE;
17223 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17225 /* Decrement running clock by amount of time that has passed */
17229 long timeRemaining;
17230 long lastTickLength, fudge;
17233 if (!appData.clockMode) return;
17234 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17238 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17240 /* Fudge if we woke up a little too soon */
17241 fudge = intendedTickLength - lastTickLength;
17242 if (fudge < 0 || fudge > FUDGE) fudge = 0;
17244 if (WhiteOnMove(forwardMostMove)) {
17245 if(whiteNPS >= 0) lastTickLength = 0;
17246 timeRemaining = whiteTimeRemaining -= lastTickLength;
17247 if(timeRemaining < 0 && !appData.icsActive) {
17248 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17249 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17250 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17251 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17254 DisplayWhiteClock(whiteTimeRemaining - fudge,
17255 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17257 if(blackNPS >= 0) lastTickLength = 0;
17258 timeRemaining = blackTimeRemaining -= lastTickLength;
17259 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17260 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17262 blackStartMove = forwardMostMove;
17263 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17266 DisplayBlackClock(blackTimeRemaining - fudge,
17267 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17269 if (CheckFlags()) return;
17271 if(twoBoards) { // count down secondary board's clocks as well
17272 activePartnerTime -= lastTickLength;
17274 if(activePartner == 'W')
17275 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17277 DisplayBlackClock(activePartnerTime, TRUE);
17282 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17283 StartClockTimer(intendedTickLength);
17285 /* if the time remaining has fallen below the alarm threshold, sound the
17286 * alarm. if the alarm has sounded and (due to a takeback or time control
17287 * with increment) the time remaining has increased to a level above the
17288 * threshold, reset the alarm so it can sound again.
17291 if (appData.icsActive && appData.icsAlarm) {
17293 /* make sure we are dealing with the user's clock */
17294 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17295 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17298 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17299 alarmSounded = FALSE;
17300 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17302 alarmSounded = TRUE;
17308 /* A player has just moved, so stop the previously running
17309 clock and (if in clock mode) start the other one.
17310 We redisplay both clocks in case we're in ICS mode, because
17311 ICS gives us an update to both clocks after every move.
17312 Note that this routine is called *after* forwardMostMove
17313 is updated, so the last fractional tick must be subtracted
17314 from the color that is *not* on move now.
17317 SwitchClocks (int newMoveNr)
17319 long lastTickLength;
17321 int flagged = FALSE;
17325 if (StopClockTimer() && appData.clockMode) {
17326 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17327 if (!WhiteOnMove(forwardMostMove)) {
17328 if(blackNPS >= 0) lastTickLength = 0;
17329 blackTimeRemaining -= lastTickLength;
17330 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17331 // if(pvInfoList[forwardMostMove].time == -1)
17332 pvInfoList[forwardMostMove].time = // use GUI time
17333 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17335 if(whiteNPS >= 0) lastTickLength = 0;
17336 whiteTimeRemaining -= lastTickLength;
17337 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17338 // if(pvInfoList[forwardMostMove].time == -1)
17339 pvInfoList[forwardMostMove].time =
17340 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17342 flagged = CheckFlags();
17344 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17345 CheckTimeControl();
17347 if (flagged || !appData.clockMode) return;
17349 switch (gameMode) {
17350 case MachinePlaysBlack:
17351 case MachinePlaysWhite:
17352 case BeginningOfGame:
17353 if (pausing) return;
17357 case PlayFromGameFile:
17365 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17366 if(WhiteOnMove(forwardMostMove))
17367 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17368 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17372 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17373 whiteTimeRemaining : blackTimeRemaining);
17374 StartClockTimer(intendedTickLength);
17378 /* Stop both clocks */
17382 long lastTickLength;
17385 if (!StopClockTimer()) return;
17386 if (!appData.clockMode) return;
17390 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17391 if (WhiteOnMove(forwardMostMove)) {
17392 if(whiteNPS >= 0) lastTickLength = 0;
17393 whiteTimeRemaining -= lastTickLength;
17394 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17396 if(blackNPS >= 0) lastTickLength = 0;
17397 blackTimeRemaining -= lastTickLength;
17398 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17403 /* Start clock of player on move. Time may have been reset, so
17404 if clock is already running, stop and restart it. */
17408 (void) StopClockTimer(); /* in case it was running already */
17409 DisplayBothClocks();
17410 if (CheckFlags()) return;
17412 if (!appData.clockMode) return;
17413 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17415 GetTimeMark(&tickStartTM);
17416 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17417 whiteTimeRemaining : blackTimeRemaining);
17419 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17420 whiteNPS = blackNPS = -1;
17421 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17422 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17423 whiteNPS = first.nps;
17424 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17425 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17426 blackNPS = first.nps;
17427 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17428 whiteNPS = second.nps;
17429 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17430 blackNPS = second.nps;
17431 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17433 StartClockTimer(intendedTickLength);
17437 TimeString (long ms)
17439 long second, minute, hour, day;
17441 static char buf[32];
17443 if (ms > 0 && ms <= 9900) {
17444 /* convert milliseconds to tenths, rounding up */
17445 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17447 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17451 /* convert milliseconds to seconds, rounding up */
17452 /* use floating point to avoid strangeness of integer division
17453 with negative dividends on many machines */
17454 second = (long) floor(((double) (ms + 999L)) / 1000.0);
17461 day = second / (60 * 60 * 24);
17462 second = second % (60 * 60 * 24);
17463 hour = second / (60 * 60);
17464 second = second % (60 * 60);
17465 minute = second / 60;
17466 second = second % 60;
17469 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17470 sign, day, hour, minute, second);
17472 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17474 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17481 * This is necessary because some C libraries aren't ANSI C compliant yet.
17484 StrStr (char *string, char *match)
17488 length = strlen(match);
17490 for (i = strlen(string) - length; i >= 0; i--, string++)
17491 if (!strncmp(match, string, length))
17498 StrCaseStr (char *string, char *match)
17502 length = strlen(match);
17504 for (i = strlen(string) - length; i >= 0; i--, string++) {
17505 for (j = 0; j < length; j++) {
17506 if (ToLower(match[j]) != ToLower(string[j]))
17509 if (j == length) return string;
17517 StrCaseCmp (char *s1, char *s2)
17522 c1 = ToLower(*s1++);
17523 c2 = ToLower(*s2++);
17524 if (c1 > c2) return 1;
17525 if (c1 < c2) return -1;
17526 if (c1 == NULLCHAR) return 0;
17534 return isupper(c) ? tolower(c) : c;
17541 return islower(c) ? toupper(c) : c;
17543 #endif /* !_amigados */
17550 if ((ret = (char *) malloc(strlen(s) + 1)))
17552 safeStrCpy(ret, s, strlen(s)+1);
17558 StrSavePtr (char *s, char **savePtr)
17563 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17564 safeStrCpy(*savePtr, s, strlen(s)+1);
17576 clock = time((time_t *)NULL);
17577 tm = localtime(&clock);
17578 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17579 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17580 return StrSave(buf);
17585 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17587 int i, j, fromX, fromY, toX, toY;
17594 whiteToPlay = (gameMode == EditPosition) ?
17595 !blackPlaysFirst : (move % 2 == 0);
17598 /* Piece placement data */
17599 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17600 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17602 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17603 if (boards[move][i][j] == EmptySquare) {
17605 } else { ChessSquare piece = boards[move][i][j];
17606 if (emptycount > 0) {
17607 if(emptycount<10) /* [HGM] can be >= 10 */
17608 *p++ = '0' + emptycount;
17609 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17612 if(PieceToChar(piece) == '+') {
17613 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17615 piece = (ChessSquare)(DEMOTED piece);
17617 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17619 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17620 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17625 if (emptycount > 0) {
17626 if(emptycount<10) /* [HGM] can be >= 10 */
17627 *p++ = '0' + emptycount;
17628 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17635 /* [HGM] print Crazyhouse or Shogi holdings */
17636 if( gameInfo.holdingsWidth ) {
17637 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17639 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17640 piece = boards[move][i][BOARD_WIDTH-1];
17641 if( piece != EmptySquare )
17642 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17643 *p++ = PieceToChar(piece);
17645 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17646 piece = boards[move][BOARD_HEIGHT-i-1][0];
17647 if( piece != EmptySquare )
17648 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17649 *p++ = PieceToChar(piece);
17652 if( q == p ) *p++ = '-';
17658 *p++ = whiteToPlay ? 'w' : 'b';
17661 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17662 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17664 if(nrCastlingRights) {
17666 if(appData.fischerCastling) {
17667 /* [HGM] write directly from rights */
17668 if(boards[move][CASTLING][2] != NoRights &&
17669 boards[move][CASTLING][0] != NoRights )
17670 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17671 if(boards[move][CASTLING][2] != NoRights &&
17672 boards[move][CASTLING][1] != NoRights )
17673 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17674 if(boards[move][CASTLING][5] != NoRights &&
17675 boards[move][CASTLING][3] != NoRights )
17676 *p++ = boards[move][CASTLING][3] + AAA;
17677 if(boards[move][CASTLING][5] != NoRights &&
17678 boards[move][CASTLING][4] != NoRights )
17679 *p++ = boards[move][CASTLING][4] + AAA;
17682 /* [HGM] write true castling rights */
17683 if( nrCastlingRights == 6 ) {
17685 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17686 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
17687 q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17688 boards[move][CASTLING][2] != NoRights );
17689 if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17690 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17691 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17692 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17693 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17697 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17698 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
17699 q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17700 boards[move][CASTLING][5] != NoRights );
17701 if(gameInfo.variant == VariantSChess) {
17702 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17703 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17704 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17705 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17710 if (q == p) *p++ = '-'; /* No castling rights */
17714 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17715 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17716 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17717 /* En passant target square */
17718 if (move > backwardMostMove) {
17719 fromX = moveList[move - 1][0] - AAA;
17720 fromY = moveList[move - 1][1] - ONE;
17721 toX = moveList[move - 1][2] - AAA;
17722 toY = moveList[move - 1][3] - ONE;
17723 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17724 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17725 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17727 /* 2-square pawn move just happened */
17729 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17733 } else if(move == backwardMostMove) {
17734 // [HGM] perhaps we should always do it like this, and forget the above?
17735 if((signed char)boards[move][EP_STATUS] >= 0) {
17736 *p++ = boards[move][EP_STATUS] + AAA;
17737 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17749 { int i = 0, j=move;
17751 /* [HGM] find reversible plies */
17752 if (appData.debugMode) { int k;
17753 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17754 for(k=backwardMostMove; k<=forwardMostMove; k++)
17755 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17759 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17760 if( j == backwardMostMove ) i += initialRulePlies;
17761 sprintf(p, "%d ", i);
17762 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17764 /* Fullmove number */
17765 sprintf(p, "%d", (move / 2) + 1);
17766 } else *--p = NULLCHAR;
17768 return StrSave(buf);
17772 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17774 int i, j, k, w=0, subst=0, shuffle=0;
17776 int emptycount, virgin[BOARD_FILES];
17781 /* Piece placement data */
17782 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17785 if (*p == '/' || *p == ' ' || *p == '[' ) {
17787 emptycount = gameInfo.boardWidth - j;
17788 while (emptycount--)
17789 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17790 if (*p == '/') p++;
17791 else if(autoSize) { // we stumbled unexpectedly into end of board
17792 for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17793 for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17795 appData.NrRanks = gameInfo.boardHeight - i; i=0;
17798 #if(BOARD_FILES >= 10)
17799 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17800 p++; emptycount=10;
17801 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17802 while (emptycount--)
17803 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17805 } else if (*p == '*') {
17806 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17807 } else if (isdigit(*p)) {
17808 emptycount = *p++ - '0';
17809 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17810 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17811 while (emptycount--)
17812 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17813 } else if (*p == '<') {
17814 if(i == BOARD_HEIGHT-1) shuffle = 1;
17815 else if (i != 0 || !shuffle) return FALSE;
17817 } else if (shuffle && *p == '>') {
17818 p++; // for now ignore closing shuffle range, and assume rank-end
17819 } else if (*p == '?') {
17820 if (j >= gameInfo.boardWidth) return FALSE;
17821 if (i != 0 && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
17822 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
17823 } else if (*p == '+' || isalpha(*p)) {
17824 if (j >= gameInfo.boardWidth) return FALSE;
17826 piece = CharToPiece(*++p);
17827 if(piece == EmptySquare) return FALSE; /* unknown piece */
17828 piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17829 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17830 } else piece = CharToPiece(*p++);
17832 if(piece==EmptySquare) return FALSE; /* unknown piece */
17833 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17834 piece = (ChessSquare) (PROMOTED piece);
17835 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17838 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17844 while (*p == '/' || *p == ' ') p++;
17846 if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17848 /* [HGM] by default clear Crazyhouse holdings, if present */
17849 if(gameInfo.holdingsWidth) {
17850 for(i=0; i<BOARD_HEIGHT; i++) {
17851 board[i][0] = EmptySquare; /* black holdings */
17852 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17853 board[i][1] = (ChessSquare) 0; /* black counts */
17854 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17858 /* [HGM] look for Crazyhouse holdings here */
17859 while(*p==' ') p++;
17860 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17861 int swap=0, wcnt=0, bcnt=0;
17863 if(*p == '<') swap++, p++;
17864 if(*p == '-' ) p++; /* empty holdings */ else {
17865 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17866 /* if we would allow FEN reading to set board size, we would */
17867 /* have to add holdings and shift the board read so far here */
17868 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17870 if((int) piece >= (int) BlackPawn ) {
17871 i = (int)piece - (int)BlackPawn;
17872 i = PieceToNumber((ChessSquare)i);
17873 if( i >= gameInfo.holdingsSize ) return FALSE;
17874 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17875 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
17878 i = (int)piece - (int)WhitePawn;
17879 i = PieceToNumber((ChessSquare)i);
17880 if( i >= gameInfo.holdingsSize ) return FALSE;
17881 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
17882 board[i][BOARD_WIDTH-2]++; /* black holdings */
17886 if(subst) { // substitute back-rank question marks by holdings pieces
17887 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
17888 int k, m, n = bcnt + 1;
17889 if(board[0][j] == ClearBoard) {
17890 if(!wcnt) return FALSE;
17892 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
17893 board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
17894 if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
17898 if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
17899 if(!bcnt) return FALSE;
17900 if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
17901 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
17902 board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
17903 if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
17914 if(subst) return FALSE; // substitution requested, but no holdings
17916 while(*p == ' ') p++;
17920 if(appData.colorNickNames) {
17921 if( c == appData.colorNickNames[0] ) c = 'w'; else
17922 if( c == appData.colorNickNames[1] ) c = 'b';
17926 *blackPlaysFirst = FALSE;
17929 *blackPlaysFirst = TRUE;
17935 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17936 /* return the extra info in global variiables */
17938 /* set defaults in case FEN is incomplete */
17939 board[EP_STATUS] = EP_UNKNOWN;
17940 for(i=0; i<nrCastlingRights; i++ ) {
17941 board[CASTLING][i] =
17942 appData.fischerCastling ? NoRights : initialRights[i];
17943 } /* assume possible unless obviously impossible */
17944 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17945 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17946 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17947 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17948 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17949 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17950 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17951 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17954 while(*p==' ') p++;
17955 if(nrCastlingRights) {
17957 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17958 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17959 /* castling indicator present, so default becomes no castlings */
17960 for(i=0; i<nrCastlingRights; i++ ) {
17961 board[CASTLING][i] = NoRights;
17964 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17965 (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
17966 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17967 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
17968 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17970 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17971 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17972 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
17974 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17975 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17976 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17977 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17978 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17979 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17982 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17983 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17984 board[CASTLING][2] = whiteKingFile;
17985 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17986 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17987 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
17990 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17991 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17992 board[CASTLING][2] = whiteKingFile;
17993 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17994 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17995 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
17998 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17999 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18000 board[CASTLING][5] = blackKingFile;
18001 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18002 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18003 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18006 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
18007 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18008 board[CASTLING][5] = blackKingFile;
18009 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18010 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18011 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18014 default: /* FRC castlings */
18015 if(c >= 'a') { /* black rights */
18016 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18017 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18018 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18019 if(i == BOARD_RGHT) break;
18020 board[CASTLING][5] = i;
18022 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
18023 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
18025 board[CASTLING][3] = c;
18027 board[CASTLING][4] = c;
18028 } else { /* white rights */
18029 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18030 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18031 if(board[0][i] == WhiteKing) break;
18032 if(i == BOARD_RGHT) break;
18033 board[CASTLING][2] = i;
18034 c -= AAA - 'a' + 'A';
18035 if(board[0][c] >= WhiteKing) break;
18037 board[CASTLING][0] = c;
18039 board[CASTLING][1] = c;
18043 for(i=0; i<nrCastlingRights; i++)
18044 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18045 if(gameInfo.variant == VariantSChess)
18046 for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18047 if(fischer && shuffle) appData.fischerCastling = TRUE;
18048 if (appData.debugMode) {
18049 fprintf(debugFP, "FEN castling rights:");
18050 for(i=0; i<nrCastlingRights; i++)
18051 fprintf(debugFP, " %d", board[CASTLING][i]);
18052 fprintf(debugFP, "\n");
18055 while(*p==' ') p++;
18058 if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18060 /* read e.p. field in games that know e.p. capture */
18061 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
18062 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18063 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18065 p++; board[EP_STATUS] = EP_NONE;
18067 char c = *p++ - AAA;
18069 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18070 if(*p >= '0' && *p <='9') p++;
18071 board[EP_STATUS] = c;
18076 if(sscanf(p, "%d", &i) == 1) {
18077 FENrulePlies = i; /* 50-move ply counter */
18078 /* (The move number is still ignored) */
18085 EditPositionPasteFEN (char *fen)
18088 Board initial_position;
18090 if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18091 DisplayError(_("Bad FEN position in clipboard"), 0);
18094 int savedBlackPlaysFirst = blackPlaysFirst;
18095 EditPositionEvent();
18096 blackPlaysFirst = savedBlackPlaysFirst;
18097 CopyBoard(boards[0], initial_position);
18098 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18099 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18100 DisplayBothClocks();
18101 DrawPosition(FALSE, boards[currentMove]);
18106 static char cseq[12] = "\\ ";
18109 set_cont_sequence (char *new_seq)
18114 // handle bad attempts to set the sequence
18116 return 0; // acceptable error - no debug
18118 len = strlen(new_seq);
18119 ret = (len > 0) && (len < sizeof(cseq));
18121 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18122 else if (appData.debugMode)
18123 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18128 reformat a source message so words don't cross the width boundary. internal
18129 newlines are not removed. returns the wrapped size (no null character unless
18130 included in source message). If dest is NULL, only calculate the size required
18131 for the dest buffer. lp argument indicats line position upon entry, and it's
18132 passed back upon exit.
18135 wrap (char *dest, char *src, int count, int width, int *lp)
18137 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18139 cseq_len = strlen(cseq);
18140 old_line = line = *lp;
18141 ansi = len = clen = 0;
18143 for (i=0; i < count; i++)
18145 if (src[i] == '\033')
18148 // if we hit the width, back up
18149 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18151 // store i & len in case the word is too long
18152 old_i = i, old_len = len;
18154 // find the end of the last word
18155 while (i && src[i] != ' ' && src[i] != '\n')
18161 // word too long? restore i & len before splitting it
18162 if ((old_i-i+clen) >= width)
18169 if (i && src[i-1] == ' ')
18172 if (src[i] != ' ' && src[i] != '\n')
18179 // now append the newline and continuation sequence
18184 strncpy(dest+len, cseq, cseq_len);
18192 dest[len] = src[i];
18196 if (src[i] == '\n')
18201 if (dest && appData.debugMode)
18203 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18204 count, width, line, len, *lp);
18205 show_bytes(debugFP, src, count);
18206 fprintf(debugFP, "\ndest: ");
18207 show_bytes(debugFP, dest, len);
18208 fprintf(debugFP, "\n");
18210 *lp = dest ? line : old_line;
18215 // [HGM] vari: routines for shelving variations
18216 Boolean modeRestore = FALSE;
18219 PushInner (int firstMove, int lastMove)
18221 int i, j, nrMoves = lastMove - firstMove;
18223 // push current tail of game on stack
18224 savedResult[storedGames] = gameInfo.result;
18225 savedDetails[storedGames] = gameInfo.resultDetails;
18226 gameInfo.resultDetails = NULL;
18227 savedFirst[storedGames] = firstMove;
18228 savedLast [storedGames] = lastMove;
18229 savedFramePtr[storedGames] = framePtr;
18230 framePtr -= nrMoves; // reserve space for the boards
18231 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18232 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18233 for(j=0; j<MOVE_LEN; j++)
18234 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18235 for(j=0; j<2*MOVE_LEN; j++)
18236 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18237 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18238 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18239 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18240 pvInfoList[firstMove+i-1].depth = 0;
18241 commentList[framePtr+i] = commentList[firstMove+i];
18242 commentList[firstMove+i] = NULL;
18246 forwardMostMove = firstMove; // truncate game so we can start variation
18250 PushTail (int firstMove, int lastMove)
18252 if(appData.icsActive) { // only in local mode
18253 forwardMostMove = currentMove; // mimic old ICS behavior
18256 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18258 PushInner(firstMove, lastMove);
18259 if(storedGames == 1) GreyRevert(FALSE);
18260 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18264 PopInner (Boolean annotate)
18267 char buf[8000], moveBuf[20];
18269 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18270 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18271 nrMoves = savedLast[storedGames] - currentMove;
18274 if(!WhiteOnMove(currentMove))
18275 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18276 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18277 for(i=currentMove; i<forwardMostMove; i++) {
18279 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18280 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18281 strcat(buf, moveBuf);
18282 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18283 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18287 for(i=1; i<=nrMoves; i++) { // copy last variation back
18288 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18289 for(j=0; j<MOVE_LEN; j++)
18290 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18291 for(j=0; j<2*MOVE_LEN; j++)
18292 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18293 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18294 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18295 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18296 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18297 commentList[currentMove+i] = commentList[framePtr+i];
18298 commentList[framePtr+i] = NULL;
18300 if(annotate) AppendComment(currentMove+1, buf, FALSE);
18301 framePtr = savedFramePtr[storedGames];
18302 gameInfo.result = savedResult[storedGames];
18303 if(gameInfo.resultDetails != NULL) {
18304 free(gameInfo.resultDetails);
18306 gameInfo.resultDetails = savedDetails[storedGames];
18307 forwardMostMove = currentMove + nrMoves;
18311 PopTail (Boolean annotate)
18313 if(appData.icsActive) return FALSE; // only in local mode
18314 if(!storedGames) return FALSE; // sanity
18315 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18317 PopInner(annotate);
18318 if(currentMove < forwardMostMove) ForwardEvent(); else
18319 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18321 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18327 { // remove all shelved variations
18329 for(i=0; i<storedGames; i++) {
18330 if(savedDetails[i])
18331 free(savedDetails[i]);
18332 savedDetails[i] = NULL;
18334 for(i=framePtr; i<MAX_MOVES; i++) {
18335 if(commentList[i]) free(commentList[i]);
18336 commentList[i] = NULL;
18338 framePtr = MAX_MOVES-1;
18343 LoadVariation (int index, char *text)
18344 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18345 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18346 int level = 0, move;
18348 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18349 // first find outermost bracketing variation
18350 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18351 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18352 if(*p == '{') wait = '}'; else
18353 if(*p == '[') wait = ']'; else
18354 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18355 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18357 if(*p == wait) wait = NULLCHAR; // closing ]} found
18360 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18361 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18362 end[1] = NULLCHAR; // clip off comment beyond variation
18363 ToNrEvent(currentMove-1);
18364 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18365 // kludge: use ParsePV() to append variation to game
18366 move = currentMove;
18367 ParsePV(start, TRUE, TRUE);
18368 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18369 ClearPremoveHighlights();
18371 ToNrEvent(currentMove+1);
18377 char *p, *q, buf[MSG_SIZ];
18378 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18379 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18380 ParseArgsFromString(buf);
18381 ActivateTheme(TRUE); // also redo colors
18385 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18388 q = appData.themeNames;
18389 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18390 if(appData.useBitmaps) {
18391 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18392 appData.liteBackTextureFile, appData.darkBackTextureFile,
18393 appData.liteBackTextureMode,
18394 appData.darkBackTextureMode );
18396 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18397 Col2Text(2), // lightSquareColor
18398 Col2Text(3) ); // darkSquareColor
18400 if(appData.useBorder) {
18401 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18404 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18406 if(appData.useFont) {
18407 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18408 appData.renderPiecesWithFont,
18409 appData.fontToPieceTable,
18410 Col2Text(9), // appData.fontBackColorWhite
18411 Col2Text(10) ); // appData.fontForeColorBlack
18413 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18414 appData.pieceDirectory);
18415 if(!appData.pieceDirectory[0])
18416 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18417 Col2Text(0), // whitePieceColor
18418 Col2Text(1) ); // blackPieceColor
18420 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18421 Col2Text(4), // highlightSquareColor
18422 Col2Text(5) ); // premoveHighlightColor
18423 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18424 if(insert != q) insert[-1] = NULLCHAR;
18425 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18428 ActivateTheme(FALSE);