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
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 chattingPartner = -1;
3076 next_out = i+1; // [HGM] suppress printing in ICS window
3078 if(!suppressKibitz) // [HGM] kibitz
3079 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3080 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3081 int nrDigit = 0, nrAlph = 0, j;
3082 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3083 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3084 parse[parse_pos] = NULLCHAR;
3085 // try to be smart: if it does not look like search info, it should go to
3086 // ICS interaction window after all, not to engine-output window.
3087 for(j=0; j<parse_pos; j++) { // count letters and digits
3088 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3089 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3090 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3092 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3093 int depth=0; float score;
3094 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3095 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3096 pvInfoList[forwardMostMove-1].depth = depth;
3097 pvInfoList[forwardMostMove-1].score = 100*score;
3099 OutputKibitz(suppressKibitz, parse);
3102 if(gameMode == IcsObserving) // restore original ICS messages
3103 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3104 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3106 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3107 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3108 SendToPlayer(tmp, strlen(tmp));
3110 next_out = i+1; // [HGM] suppress printing in ICS window
3112 started = STARTED_NONE;
3114 /* Don't match patterns against characters in comment */
3119 if (started == STARTED_CHATTER) {
3120 if (buf[i] != '\n') {
3121 /* Don't match patterns against characters in chatter */
3125 started = STARTED_NONE;
3126 if(suppressKibitz) next_out = i+1;
3129 /* Kludge to deal with rcmd protocol */
3130 if (firstTime && looking_at(buf, &i, "\001*")) {
3131 DisplayFatalError(&buf[1], 0, 1);
3137 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3140 if (appData.debugMode)
3141 fprintf(debugFP, "ics_type %d\n", ics_type);
3144 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3145 ics_type = ICS_FICS;
3147 if (appData.debugMode)
3148 fprintf(debugFP, "ics_type %d\n", ics_type);
3151 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3152 ics_type = ICS_CHESSNET;
3154 if (appData.debugMode)
3155 fprintf(debugFP, "ics_type %d\n", ics_type);
3160 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3161 looking_at(buf, &i, "Logging you in as \"*\"") ||
3162 looking_at(buf, &i, "will be \"*\""))) {
3163 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3167 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3169 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3170 DisplayIcsInteractionTitle(buf);
3171 have_set_title = TRUE;
3174 /* skip finger notes */
3175 if (started == STARTED_NONE &&
3176 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3177 (buf[i] == '1' && buf[i+1] == '0')) &&
3178 buf[i+2] == ':' && buf[i+3] == ' ') {
3179 started = STARTED_CHATTER;
3185 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3186 if(appData.seekGraph) {
3187 if(soughtPending && MatchSoughtLine(buf+i)) {
3188 i = strstr(buf+i, "rated") - buf;
3189 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3190 next_out = leftover_start = i;
3191 started = STARTED_CHATTER;
3192 suppressKibitz = TRUE;
3195 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3196 && looking_at(buf, &i, "* ads displayed")) {
3197 soughtPending = FALSE;
3202 if(appData.autoRefresh) {
3203 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3204 int s = (ics_type == ICS_ICC); // ICC format differs
3206 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3207 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3208 looking_at(buf, &i, "*% "); // eat prompt
3209 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3210 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3211 next_out = i; // suppress
3214 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3215 char *p = star_match[0];
3217 if(seekGraphUp) RemoveSeekAd(atoi(p));
3218 while(*p && *p++ != ' '); // next
3220 looking_at(buf, &i, "*% "); // eat prompt
3221 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3228 /* skip formula vars */
3229 if (started == STARTED_NONE &&
3230 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3231 started = STARTED_CHATTER;
3236 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3237 if (appData.autoKibitz && started == STARTED_NONE &&
3238 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3239 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3240 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3241 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3242 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3243 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3244 suppressKibitz = TRUE;
3245 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3247 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3248 && (gameMode == IcsPlayingWhite)) ||
3249 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3250 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3251 started = STARTED_CHATTER; // own kibitz we simply discard
3253 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3254 parse_pos = 0; parse[0] = NULLCHAR;
3255 savingComment = TRUE;
3256 suppressKibitz = gameMode != IcsObserving ? 2 :
3257 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3261 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3262 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3263 && atoi(star_match[0])) {
3264 // suppress the acknowledgements of our own autoKibitz
3266 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3267 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3268 SendToPlayer(star_match[0], strlen(star_match[0]));
3269 if(looking_at(buf, &i, "*% ")) // eat prompt
3270 suppressKibitz = FALSE;
3274 } // [HGM] kibitz: end of patch
3276 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3278 // [HGM] chat: intercept tells by users for which we have an open chat window
3280 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3281 looking_at(buf, &i, "* whispers:") ||
3282 looking_at(buf, &i, "* kibitzes:") ||
3283 looking_at(buf, &i, "* shouts:") ||
3284 looking_at(buf, &i, "* c-shouts:") ||
3285 looking_at(buf, &i, "--> * ") ||
3286 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3287 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3288 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3289 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3291 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3292 chattingPartner = -1;
3294 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3295 for(p=0; p<MAX_CHAT; p++) {
3296 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3297 talker[0] = '['; strcat(talker, "] ");
3298 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3299 chattingPartner = p; break;
3302 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3303 for(p=0; p<MAX_CHAT; p++) {
3304 if(!strcmp("kibitzes", chatPartner[p])) {
3305 talker[0] = '['; strcat(talker, "] ");
3306 chattingPartner = p; break;
3309 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3310 for(p=0; p<MAX_CHAT; p++) {
3311 if(!strcmp("whispers", chatPartner[p])) {
3312 talker[0] = '['; strcat(talker, "] ");
3313 chattingPartner = p; break;
3316 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3317 if(buf[i-8] == '-' && buf[i-3] == 't')
3318 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3319 if(!strcmp("c-shouts", chatPartner[p])) {
3320 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3321 chattingPartner = p; break;
3324 if(chattingPartner < 0)
3325 for(p=0; p<MAX_CHAT; p++) {
3326 if(!strcmp("shouts", chatPartner[p])) {
3327 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3328 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3329 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3330 chattingPartner = p; break;
3334 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3335 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3336 talker[0] = 0; Colorize(ColorTell, FALSE);
3337 chattingPartner = p; break;
3339 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3340 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3341 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3342 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3343 started = STARTED_COMMENT;
3344 parse_pos = 0; parse[0] = NULLCHAR;
3345 savingComment = 3 + chattingPartner; // counts as TRUE
3346 suppressKibitz = TRUE;
3349 } // [HGM] chat: end of patch
3352 if (appData.zippyTalk || appData.zippyPlay) {
3353 /* [DM] Backup address for color zippy lines */
3355 if (loggedOn == TRUE)
3356 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3357 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3359 } // [DM] 'else { ' deleted
3361 /* Regular tells and says */
3362 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3363 looking_at(buf, &i, "* (your partner) tells you: ") ||
3364 looking_at(buf, &i, "* says: ") ||
3365 /* Don't color "message" or "messages" output */
3366 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3367 looking_at(buf, &i, "*. * at *:*: ") ||
3368 looking_at(buf, &i, "--* (*:*): ") ||
3369 /* Message notifications (same color as tells) */
3370 looking_at(buf, &i, "* has left a message ") ||
3371 looking_at(buf, &i, "* just sent you a message:\n") ||
3372 /* Whispers and kibitzes */
3373 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3374 looking_at(buf, &i, "* kibitzes: ") ||
3376 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3378 if (tkind == 1 && strchr(star_match[0], ':')) {
3379 /* Avoid "tells you:" spoofs in channels */
3382 if (star_match[0][0] == NULLCHAR ||
3383 strchr(star_match[0], ' ') ||
3384 (tkind == 3 && strchr(star_match[1], ' '))) {
3385 /* Reject bogus matches */
3388 if (appData.colorize) {
3389 if (oldi > next_out) {
3390 SendToPlayer(&buf[next_out], oldi - next_out);
3395 Colorize(ColorTell, FALSE);
3396 curColor = ColorTell;
3399 Colorize(ColorKibitz, FALSE);
3400 curColor = ColorKibitz;
3403 p = strrchr(star_match[1], '(');
3410 Colorize(ColorChannel1, FALSE);
3411 curColor = ColorChannel1;
3413 Colorize(ColorChannel, FALSE);
3414 curColor = ColorChannel;
3418 curColor = ColorNormal;
3422 if (started == STARTED_NONE && appData.autoComment &&
3423 (gameMode == IcsObserving ||
3424 gameMode == IcsPlayingWhite ||
3425 gameMode == IcsPlayingBlack)) {
3426 parse_pos = i - oldi;
3427 memcpy(parse, &buf[oldi], parse_pos);
3428 parse[parse_pos] = NULLCHAR;
3429 started = STARTED_COMMENT;
3430 savingComment = TRUE;
3432 started = STARTED_CHATTER;
3433 savingComment = FALSE;
3440 if (looking_at(buf, &i, "* s-shouts: ") ||
3441 looking_at(buf, &i, "* c-shouts: ")) {
3442 if (appData.colorize) {
3443 if (oldi > next_out) {
3444 SendToPlayer(&buf[next_out], oldi - next_out);
3447 Colorize(ColorSShout, FALSE);
3448 curColor = ColorSShout;
3451 started = STARTED_CHATTER;
3455 if (looking_at(buf, &i, "--->")) {
3460 if (looking_at(buf, &i, "* shouts: ") ||
3461 looking_at(buf, &i, "--> ")) {
3462 if (appData.colorize) {
3463 if (oldi > next_out) {
3464 SendToPlayer(&buf[next_out], oldi - next_out);
3467 Colorize(ColorShout, FALSE);
3468 curColor = ColorShout;
3471 started = STARTED_CHATTER;
3475 if (looking_at( buf, &i, "Challenge:")) {
3476 if (appData.colorize) {
3477 if (oldi > next_out) {
3478 SendToPlayer(&buf[next_out], oldi - next_out);
3481 Colorize(ColorChallenge, FALSE);
3482 curColor = ColorChallenge;
3488 if (looking_at(buf, &i, "* offers you") ||
3489 looking_at(buf, &i, "* offers to be") ||
3490 looking_at(buf, &i, "* would like to") ||
3491 looking_at(buf, &i, "* requests to") ||
3492 looking_at(buf, &i, "Your opponent offers") ||
3493 looking_at(buf, &i, "Your opponent requests")) {
3495 if (appData.colorize) {
3496 if (oldi > next_out) {
3497 SendToPlayer(&buf[next_out], oldi - next_out);
3500 Colorize(ColorRequest, FALSE);
3501 curColor = ColorRequest;
3506 if (looking_at(buf, &i, "* (*) seeking")) {
3507 if (appData.colorize) {
3508 if (oldi > next_out) {
3509 SendToPlayer(&buf[next_out], oldi - next_out);
3512 Colorize(ColorSeek, FALSE);
3513 curColor = ColorSeek;
3518 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3520 if (looking_at(buf, &i, "\\ ")) {
3521 if (prevColor != ColorNormal) {
3522 if (oldi > next_out) {
3523 SendToPlayer(&buf[next_out], oldi - next_out);
3526 Colorize(prevColor, TRUE);
3527 curColor = prevColor;
3529 if (savingComment) {
3530 parse_pos = i - oldi;
3531 memcpy(parse, &buf[oldi], parse_pos);
3532 parse[parse_pos] = NULLCHAR;
3533 started = STARTED_COMMENT;
3534 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3535 chattingPartner = savingComment - 3; // kludge to remember the box
3537 started = STARTED_CHATTER;
3542 if (looking_at(buf, &i, "Black Strength :") ||
3543 looking_at(buf, &i, "<<< style 10 board >>>") ||
3544 looking_at(buf, &i, "<10>") ||
3545 looking_at(buf, &i, "#@#")) {
3546 /* Wrong board style */
3548 SendToICS(ics_prefix);
3549 SendToICS("set style 12\n");
3550 SendToICS(ics_prefix);
3551 SendToICS("refresh\n");
3555 if (looking_at(buf, &i, "login:")) {
3556 if (!have_sent_ICS_logon) {
3558 have_sent_ICS_logon = 1;
3559 else // no init script was found
3560 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3561 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3562 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3567 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3568 (looking_at(buf, &i, "\n<12> ") ||
3569 looking_at(buf, &i, "<12> "))) {
3571 if (oldi > next_out) {
3572 SendToPlayer(&buf[next_out], oldi - next_out);
3575 started = STARTED_BOARD;
3580 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3581 looking_at(buf, &i, "<b1> ")) {
3582 if (oldi > next_out) {
3583 SendToPlayer(&buf[next_out], oldi - next_out);
3586 started = STARTED_HOLDINGS;
3591 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3593 /* Header for a move list -- first line */
3595 switch (ics_getting_history) {
3599 case BeginningOfGame:
3600 /* User typed "moves" or "oldmoves" while we
3601 were idle. Pretend we asked for these
3602 moves and soak them up so user can step
3603 through them and/or save them.
3606 gameMode = IcsObserving;
3609 ics_getting_history = H_GOT_UNREQ_HEADER;
3611 case EditGame: /*?*/
3612 case EditPosition: /*?*/
3613 /* Should above feature work in these modes too? */
3614 /* For now it doesn't */
3615 ics_getting_history = H_GOT_UNWANTED_HEADER;
3618 ics_getting_history = H_GOT_UNWANTED_HEADER;
3623 /* Is this the right one? */
3624 if (gameInfo.white && gameInfo.black &&
3625 strcmp(gameInfo.white, star_match[0]) == 0 &&
3626 strcmp(gameInfo.black, star_match[2]) == 0) {
3628 ics_getting_history = H_GOT_REQ_HEADER;
3631 case H_GOT_REQ_HEADER:
3632 case H_GOT_UNREQ_HEADER:
3633 case H_GOT_UNWANTED_HEADER:
3634 case H_GETTING_MOVES:
3635 /* Should not happen */
3636 DisplayError(_("Error gathering move list: two headers"), 0);
3637 ics_getting_history = H_FALSE;
3641 /* Save player ratings into gameInfo if needed */
3642 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3643 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3644 (gameInfo.whiteRating == -1 ||
3645 gameInfo.blackRating == -1)) {
3647 gameInfo.whiteRating = string_to_rating(star_match[1]);
3648 gameInfo.blackRating = string_to_rating(star_match[3]);
3649 if (appData.debugMode)
3650 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3651 gameInfo.whiteRating, gameInfo.blackRating);
3656 if (looking_at(buf, &i,
3657 "* * match, initial time: * minute*, increment: * second")) {
3658 /* Header for a move list -- second line */
3659 /* Initial board will follow if this is a wild game */
3660 if (gameInfo.event != NULL) free(gameInfo.event);
3661 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3662 gameInfo.event = StrSave(str);
3663 /* [HGM] we switched variant. Translate boards if needed. */
3664 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3668 if (looking_at(buf, &i, "Move ")) {
3669 /* Beginning of a move list */
3670 switch (ics_getting_history) {
3672 /* Normally should not happen */
3673 /* Maybe user hit reset while we were parsing */
3676 /* Happens if we are ignoring a move list that is not
3677 * the one we just requested. Common if the user
3678 * tries to observe two games without turning off
3681 case H_GETTING_MOVES:
3682 /* Should not happen */
3683 DisplayError(_("Error gathering move list: nested"), 0);
3684 ics_getting_history = H_FALSE;
3686 case H_GOT_REQ_HEADER:
3687 ics_getting_history = H_GETTING_MOVES;
3688 started = STARTED_MOVES;
3690 if (oldi > next_out) {
3691 SendToPlayer(&buf[next_out], oldi - next_out);
3694 case H_GOT_UNREQ_HEADER:
3695 ics_getting_history = H_GETTING_MOVES;
3696 started = STARTED_MOVES_NOHIDE;
3699 case H_GOT_UNWANTED_HEADER:
3700 ics_getting_history = H_FALSE;
3706 if (looking_at(buf, &i, "% ") ||
3707 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3708 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3709 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3710 soughtPending = FALSE;
3714 if(suppressKibitz) next_out = i;
3715 savingComment = FALSE;
3719 case STARTED_MOVES_NOHIDE:
3720 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3721 parse[parse_pos + i - oldi] = NULLCHAR;
3722 ParseGameHistory(parse);
3724 if (appData.zippyPlay && first.initDone) {
3725 FeedMovesToProgram(&first, forwardMostMove);
3726 if (gameMode == IcsPlayingWhite) {
3727 if (WhiteOnMove(forwardMostMove)) {
3728 if (first.sendTime) {
3729 if (first.useColors) {
3730 SendToProgram("black\n", &first);
3732 SendTimeRemaining(&first, TRUE);
3734 if (first.useColors) {
3735 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3737 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3738 first.maybeThinking = TRUE;
3740 if (first.usePlayother) {
3741 if (first.sendTime) {
3742 SendTimeRemaining(&first, TRUE);
3744 SendToProgram("playother\n", &first);
3750 } else if (gameMode == IcsPlayingBlack) {
3751 if (!WhiteOnMove(forwardMostMove)) {
3752 if (first.sendTime) {
3753 if (first.useColors) {
3754 SendToProgram("white\n", &first);
3756 SendTimeRemaining(&first, FALSE);
3758 if (first.useColors) {
3759 SendToProgram("black\n", &first);
3761 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3762 first.maybeThinking = TRUE;
3764 if (first.usePlayother) {
3765 if (first.sendTime) {
3766 SendTimeRemaining(&first, FALSE);
3768 SendToProgram("playother\n", &first);
3777 if (gameMode == IcsObserving && ics_gamenum == -1) {
3778 /* Moves came from oldmoves or moves command
3779 while we weren't doing anything else.
3781 currentMove = forwardMostMove;
3782 ClearHighlights();/*!!could figure this out*/
3783 flipView = appData.flipView;
3784 DrawPosition(TRUE, boards[currentMove]);
3785 DisplayBothClocks();
3786 snprintf(str, MSG_SIZ, "%s %s %s",
3787 gameInfo.white, _("vs."), gameInfo.black);
3791 /* Moves were history of an active game */
3792 if (gameInfo.resultDetails != NULL) {
3793 free(gameInfo.resultDetails);
3794 gameInfo.resultDetails = NULL;
3797 HistorySet(parseList, backwardMostMove,
3798 forwardMostMove, currentMove-1);
3799 DisplayMove(currentMove - 1);
3800 if (started == STARTED_MOVES) next_out = i;
3801 started = STARTED_NONE;
3802 ics_getting_history = H_FALSE;
3805 case STARTED_OBSERVE:
3806 started = STARTED_NONE;
3807 SendToICS(ics_prefix);
3808 SendToICS("refresh\n");
3814 if(bookHit) { // [HGM] book: simulate book reply
3815 static char bookMove[MSG_SIZ]; // a bit generous?
3817 programStats.nodes = programStats.depth = programStats.time =
3818 programStats.score = programStats.got_only_move = 0;
3819 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3821 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3822 strcat(bookMove, bookHit);
3823 HandleMachineMove(bookMove, &first);
3828 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3829 started == STARTED_HOLDINGS ||
3830 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3831 /* Accumulate characters in move list or board */
3832 parse[parse_pos++] = buf[i];
3835 /* Start of game messages. Mostly we detect start of game
3836 when the first board image arrives. On some versions
3837 of the ICS, though, we need to do a "refresh" after starting
3838 to observe in order to get the current board right away. */
3839 if (looking_at(buf, &i, "Adding game * to observation list")) {
3840 started = STARTED_OBSERVE;
3844 /* Handle auto-observe */
3845 if (appData.autoObserve &&
3846 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3847 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3849 /* Choose the player that was highlighted, if any. */
3850 if (star_match[0][0] == '\033' ||
3851 star_match[1][0] != '\033') {
3852 player = star_match[0];
3854 player = star_match[2];
3856 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3857 ics_prefix, StripHighlightAndTitle(player));
3860 /* Save ratings from notify string */
3861 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3862 player1Rating = string_to_rating(star_match[1]);
3863 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3864 player2Rating = string_to_rating(star_match[3]);
3866 if (appData.debugMode)
3868 "Ratings from 'Game notification:' %s %d, %s %d\n",
3869 player1Name, player1Rating,
3870 player2Name, player2Rating);
3875 /* Deal with automatic examine mode after a game,
3876 and with IcsObserving -> IcsExamining transition */
3877 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3878 looking_at(buf, &i, "has made you an examiner of game *")) {
3880 int gamenum = atoi(star_match[0]);
3881 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3882 gamenum == ics_gamenum) {
3883 /* We were already playing or observing this game;
3884 no need to refetch history */
3885 gameMode = IcsExamining;
3887 pauseExamForwardMostMove = forwardMostMove;
3888 } else if (currentMove < forwardMostMove) {
3889 ForwardInner(forwardMostMove);
3892 /* I don't think this case really can happen */
3893 SendToICS(ics_prefix);
3894 SendToICS("refresh\n");
3899 /* Error messages */
3900 // if (ics_user_moved) {
3901 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3902 if (looking_at(buf, &i, "Illegal move") ||
3903 looking_at(buf, &i, "Not a legal move") ||
3904 looking_at(buf, &i, "Your king is in check") ||
3905 looking_at(buf, &i, "It isn't your turn") ||
3906 looking_at(buf, &i, "It is not your move")) {
3908 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3909 currentMove = forwardMostMove-1;
3910 DisplayMove(currentMove - 1); /* before DMError */
3911 DrawPosition(FALSE, boards[currentMove]);
3912 SwitchClocks(forwardMostMove-1); // [HGM] race
3913 DisplayBothClocks();
3915 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3921 if (looking_at(buf, &i, "still have time") ||
3922 looking_at(buf, &i, "not out of time") ||
3923 looking_at(buf, &i, "either player is out of time") ||
3924 looking_at(buf, &i, "has timeseal; checking")) {
3925 /* We must have called his flag a little too soon */
3926 whiteFlag = blackFlag = FALSE;
3930 if (looking_at(buf, &i, "added * seconds to") ||
3931 looking_at(buf, &i, "seconds were added to")) {
3932 /* Update the clocks */
3933 SendToICS(ics_prefix);
3934 SendToICS("refresh\n");
3938 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3939 ics_clock_paused = TRUE;
3944 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3945 ics_clock_paused = FALSE;
3950 /* Grab player ratings from the Creating: message.
3951 Note we have to check for the special case when
3952 the ICS inserts things like [white] or [black]. */
3953 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3954 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3956 0 player 1 name (not necessarily white)
3958 2 empty, white, or black (IGNORED)
3959 3 player 2 name (not necessarily black)
3962 The names/ratings are sorted out when the game
3963 actually starts (below).
3965 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3966 player1Rating = string_to_rating(star_match[1]);
3967 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3968 player2Rating = string_to_rating(star_match[4]);
3970 if (appData.debugMode)
3972 "Ratings from 'Creating:' %s %d, %s %d\n",
3973 player1Name, player1Rating,
3974 player2Name, player2Rating);
3979 /* Improved generic start/end-of-game messages */
3980 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3981 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3982 /* If tkind == 0: */
3983 /* star_match[0] is the game number */
3984 /* [1] is the white player's name */
3985 /* [2] is the black player's name */
3986 /* For end-of-game: */
3987 /* [3] is the reason for the game end */
3988 /* [4] is a PGN end game-token, preceded by " " */
3989 /* For start-of-game: */
3990 /* [3] begins with "Creating" or "Continuing" */
3991 /* [4] is " *" or empty (don't care). */
3992 int gamenum = atoi(star_match[0]);
3993 char *whitename, *blackname, *why, *endtoken;
3994 ChessMove endtype = EndOfFile;
3997 whitename = star_match[1];
3998 blackname = star_match[2];
3999 why = star_match[3];
4000 endtoken = star_match[4];
4002 whitename = star_match[1];
4003 blackname = star_match[3];
4004 why = star_match[5];
4005 endtoken = star_match[6];
4008 /* Game start messages */
4009 if (strncmp(why, "Creating ", 9) == 0 ||
4010 strncmp(why, "Continuing ", 11) == 0) {
4011 gs_gamenum = gamenum;
4012 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4013 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4014 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4016 if (appData.zippyPlay) {
4017 ZippyGameStart(whitename, blackname);
4020 partnerBoardValid = FALSE; // [HGM] bughouse
4024 /* Game end messages */
4025 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4026 ics_gamenum != gamenum) {
4029 while (endtoken[0] == ' ') endtoken++;
4030 switch (endtoken[0]) {
4033 endtype = GameUnfinished;
4036 endtype = BlackWins;
4039 if (endtoken[1] == '/')
4040 endtype = GameIsDrawn;
4042 endtype = WhiteWins;
4045 GameEnds(endtype, why, GE_ICS);
4047 if (appData.zippyPlay && first.initDone) {
4048 ZippyGameEnd(endtype, why);
4049 if (first.pr == NoProc) {
4050 /* Start the next process early so that we'll
4051 be ready for the next challenge */
4052 StartChessProgram(&first);
4054 /* Send "new" early, in case this command takes
4055 a long time to finish, so that we'll be ready
4056 for the next challenge. */
4057 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4061 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4065 if (looking_at(buf, &i, "Removing game * from observation") ||
4066 looking_at(buf, &i, "no longer observing game *") ||
4067 looking_at(buf, &i, "Game * (*) has no examiners")) {
4068 if (gameMode == IcsObserving &&
4069 atoi(star_match[0]) == ics_gamenum)
4071 /* icsEngineAnalyze */
4072 if (appData.icsEngineAnalyze) {
4079 ics_user_moved = FALSE;
4084 if (looking_at(buf, &i, "no longer examining game *")) {
4085 if (gameMode == IcsExamining &&
4086 atoi(star_match[0]) == ics_gamenum)
4090 ics_user_moved = FALSE;
4095 /* Advance leftover_start past any newlines we find,
4096 so only partial lines can get reparsed */
4097 if (looking_at(buf, &i, "\n")) {
4098 prevColor = curColor;
4099 if (curColor != ColorNormal) {
4100 if (oldi > next_out) {
4101 SendToPlayer(&buf[next_out], oldi - next_out);
4104 Colorize(ColorNormal, FALSE);
4105 curColor = ColorNormal;
4107 if (started == STARTED_BOARD) {
4108 started = STARTED_NONE;
4109 parse[parse_pos] = NULLCHAR;
4110 ParseBoard12(parse);
4113 /* Send premove here */
4114 if (appData.premove) {
4116 if (currentMove == 0 &&
4117 gameMode == IcsPlayingWhite &&
4118 appData.premoveWhite) {
4119 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4120 if (appData.debugMode)
4121 fprintf(debugFP, "Sending premove:\n");
4123 } else if (currentMove == 1 &&
4124 gameMode == IcsPlayingBlack &&
4125 appData.premoveBlack) {
4126 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4127 if (appData.debugMode)
4128 fprintf(debugFP, "Sending premove:\n");
4130 } else if (gotPremove) {
4132 ClearPremoveHighlights();
4133 if (appData.debugMode)
4134 fprintf(debugFP, "Sending premove:\n");
4135 UserMoveEvent(premoveFromX, premoveFromY,
4136 premoveToX, premoveToY,
4141 /* Usually suppress following prompt */
4142 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4143 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4144 if (looking_at(buf, &i, "*% ")) {
4145 savingComment = FALSE;
4150 } else if (started == STARTED_HOLDINGS) {
4152 char new_piece[MSG_SIZ];
4153 started = STARTED_NONE;
4154 parse[parse_pos] = NULLCHAR;
4155 if (appData.debugMode)
4156 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4157 parse, currentMove);
4158 if (sscanf(parse, " game %d", &gamenum) == 1) {
4159 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4160 if (gameInfo.variant == VariantNormal) {
4161 /* [HGM] We seem to switch variant during a game!
4162 * Presumably no holdings were displayed, so we have
4163 * to move the position two files to the right to
4164 * create room for them!
4166 VariantClass newVariant;
4167 switch(gameInfo.boardWidth) { // base guess on board width
4168 case 9: newVariant = VariantShogi; break;
4169 case 10: newVariant = VariantGreat; break;
4170 default: newVariant = VariantCrazyhouse; break;
4172 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4173 /* Get a move list just to see the header, which
4174 will tell us whether this is really bug or zh */
4175 if (ics_getting_history == H_FALSE) {
4176 ics_getting_history = H_REQUESTED;
4177 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4181 new_piece[0] = NULLCHAR;
4182 sscanf(parse, "game %d white [%s black [%s <- %s",
4183 &gamenum, white_holding, black_holding,
4185 white_holding[strlen(white_holding)-1] = NULLCHAR;
4186 black_holding[strlen(black_holding)-1] = NULLCHAR;
4187 /* [HGM] copy holdings to board holdings area */
4188 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4189 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4190 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4192 if (appData.zippyPlay && first.initDone) {
4193 ZippyHoldings(white_holding, black_holding,
4197 if (tinyLayout || smallLayout) {
4198 char wh[16], bh[16];
4199 PackHolding(wh, white_holding);
4200 PackHolding(bh, black_holding);
4201 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4202 gameInfo.white, gameInfo.black);
4204 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4205 gameInfo.white, white_holding, _("vs."),
4206 gameInfo.black, black_holding);
4208 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4209 DrawPosition(FALSE, boards[currentMove]);
4211 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4212 sscanf(parse, "game %d white [%s black [%s <- %s",
4213 &gamenum, white_holding, black_holding,
4215 white_holding[strlen(white_holding)-1] = NULLCHAR;
4216 black_holding[strlen(black_holding)-1] = NULLCHAR;
4217 /* [HGM] copy holdings to partner-board holdings area */
4218 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4219 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4220 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4221 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4222 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4225 /* Suppress following prompt */
4226 if (looking_at(buf, &i, "*% ")) {
4227 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4228 savingComment = FALSE;
4236 i++; /* skip unparsed character and loop back */
4239 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4240 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4241 // SendToPlayer(&buf[next_out], i - next_out);
4242 started != STARTED_HOLDINGS && leftover_start > next_out) {
4243 SendToPlayer(&buf[next_out], leftover_start - next_out);
4247 leftover_len = buf_len - leftover_start;
4248 /* if buffer ends with something we couldn't parse,
4249 reparse it after appending the next read */
4251 } else if (count == 0) {
4252 RemoveInputSource(isr);
4253 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4255 DisplayFatalError(_("Error reading from ICS"), error, 1);
4260 /* Board style 12 looks like this:
4262 <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
4264 * The "<12> " is stripped before it gets to this routine. The two
4265 * trailing 0's (flip state and clock ticking) are later addition, and
4266 * some chess servers may not have them, or may have only the first.
4267 * Additional trailing fields may be added in the future.
4270 #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"
4272 #define RELATION_OBSERVING_PLAYED 0
4273 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4274 #define RELATION_PLAYING_MYMOVE 1
4275 #define RELATION_PLAYING_NOTMYMOVE -1
4276 #define RELATION_EXAMINING 2
4277 #define RELATION_ISOLATED_BOARD -3
4278 #define RELATION_STARTING_POSITION -4 /* FICS only */
4281 ParseBoard12 (char *string)
4285 char *bookHit = NULL; // [HGM] book
4287 GameMode newGameMode;
4288 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4289 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4290 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4291 char to_play, board_chars[200];
4292 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4293 char black[32], white[32];
4295 int prevMove = currentMove;
4298 int fromX, fromY, toX, toY;
4300 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4301 Boolean weird = FALSE, reqFlag = FALSE;
4303 fromX = fromY = toX = toY = -1;
4307 if (appData.debugMode)
4308 fprintf(debugFP, "Parsing board: %s\n", string);
4310 move_str[0] = NULLCHAR;
4311 elapsed_time[0] = NULLCHAR;
4312 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4314 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4315 if(string[i] == ' ') { ranks++; files = 0; }
4317 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4320 for(j = 0; j <i; j++) board_chars[j] = string[j];
4321 board_chars[i] = '\0';
4324 n = sscanf(string, PATTERN, &to_play, &double_push,
4325 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4326 &gamenum, white, black, &relation, &basetime, &increment,
4327 &white_stren, &black_stren, &white_time, &black_time,
4328 &moveNum, str, elapsed_time, move_str, &ics_flip,
4332 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4333 DisplayError(str, 0);
4337 /* Convert the move number to internal form */
4338 moveNum = (moveNum - 1) * 2;
4339 if (to_play == 'B') moveNum++;
4340 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4341 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4347 case RELATION_OBSERVING_PLAYED:
4348 case RELATION_OBSERVING_STATIC:
4349 if (gamenum == -1) {
4350 /* Old ICC buglet */
4351 relation = RELATION_OBSERVING_STATIC;
4353 newGameMode = IcsObserving;
4355 case RELATION_PLAYING_MYMOVE:
4356 case RELATION_PLAYING_NOTMYMOVE:
4358 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4359 IcsPlayingWhite : IcsPlayingBlack;
4360 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4362 case RELATION_EXAMINING:
4363 newGameMode = IcsExamining;
4365 case RELATION_ISOLATED_BOARD:
4367 /* Just display this board. If user was doing something else,
4368 we will forget about it until the next board comes. */
4369 newGameMode = IcsIdle;
4371 case RELATION_STARTING_POSITION:
4372 newGameMode = gameMode;
4376 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4377 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4378 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4379 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4380 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4381 static int lastBgGame = -1;
4383 for (k = 0; k < ranks; k++) {
4384 for (j = 0; j < files; j++)
4385 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4386 if(gameInfo.holdingsWidth > 1) {
4387 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4388 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4391 CopyBoard(partnerBoard, board);
4392 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4393 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4394 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4395 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4396 if(toSqr = strchr(str, '-')) {
4397 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4398 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4399 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4400 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4401 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4402 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4404 DisplayWhiteClock(white_time*fac, to_play == 'W');
4405 DisplayBlackClock(black_time*fac, to_play != 'W');
4406 activePartner = to_play;
4407 if(gamenum != lastBgGame) {
4409 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4412 lastBgGame = gamenum;
4413 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4414 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4415 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4416 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4417 if(!twoBoards) DisplayMessage(partnerStatus, "");
4418 partnerBoardValid = TRUE;
4422 if(appData.dualBoard && appData.bgObserve) {
4423 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4424 SendToICS(ics_prefix), SendToICS("pobserve\n");
4425 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4427 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4432 /* Modify behavior for initial board display on move listing
4435 switch (ics_getting_history) {
4439 case H_GOT_REQ_HEADER:
4440 case H_GOT_UNREQ_HEADER:
4441 /* This is the initial position of the current game */
4442 gamenum = ics_gamenum;
4443 moveNum = 0; /* old ICS bug workaround */
4444 if (to_play == 'B') {
4445 startedFromSetupPosition = TRUE;
4446 blackPlaysFirst = TRUE;
4448 if (forwardMostMove == 0) forwardMostMove = 1;
4449 if (backwardMostMove == 0) backwardMostMove = 1;
4450 if (currentMove == 0) currentMove = 1;
4452 newGameMode = gameMode;
4453 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4455 case H_GOT_UNWANTED_HEADER:
4456 /* This is an initial board that we don't want */
4458 case H_GETTING_MOVES:
4459 /* Should not happen */
4460 DisplayError(_("Error gathering move list: extra board"), 0);
4461 ics_getting_history = H_FALSE;
4465 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4466 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4467 weird && (int)gameInfo.variant < (int)VariantShogi) {
4468 /* [HGM] We seem to have switched variant unexpectedly
4469 * Try to guess new variant from board size
4471 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4472 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4473 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4474 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4475 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4476 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4477 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4478 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4479 /* Get a move list just to see the header, which
4480 will tell us whether this is really bug or zh */
4481 if (ics_getting_history == H_FALSE) {
4482 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4483 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4488 /* Take action if this is the first board of a new game, or of a
4489 different game than is currently being displayed. */
4490 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4491 relation == RELATION_ISOLATED_BOARD) {
4493 /* Forget the old game and get the history (if any) of the new one */
4494 if (gameMode != BeginningOfGame) {
4498 if (appData.autoRaiseBoard) BoardToTop();
4500 if (gamenum == -1) {
4501 newGameMode = IcsIdle;
4502 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4503 appData.getMoveList && !reqFlag) {
4504 /* Need to get game history */
4505 ics_getting_history = H_REQUESTED;
4506 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4510 /* Initially flip the board to have black on the bottom if playing
4511 black or if the ICS flip flag is set, but let the user change
4512 it with the Flip View button. */
4513 flipView = appData.autoFlipView ?
4514 (newGameMode == IcsPlayingBlack) || ics_flip :
4517 /* Done with values from previous mode; copy in new ones */
4518 gameMode = newGameMode;
4520 ics_gamenum = gamenum;
4521 if (gamenum == gs_gamenum) {
4522 int klen = strlen(gs_kind);
4523 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4524 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4525 gameInfo.event = StrSave(str);
4527 gameInfo.event = StrSave("ICS game");
4529 gameInfo.site = StrSave(appData.icsHost);
4530 gameInfo.date = PGNDate();
4531 gameInfo.round = StrSave("-");
4532 gameInfo.white = StrSave(white);
4533 gameInfo.black = StrSave(black);
4534 timeControl = basetime * 60 * 1000;
4536 timeIncrement = increment * 1000;
4537 movesPerSession = 0;
4538 gameInfo.timeControl = TimeControlTagValue();
4539 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4540 if (appData.debugMode) {
4541 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4542 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4543 setbuf(debugFP, NULL);
4546 gameInfo.outOfBook = NULL;
4548 /* Do we have the ratings? */
4549 if (strcmp(player1Name, white) == 0 &&
4550 strcmp(player2Name, black) == 0) {
4551 if (appData.debugMode)
4552 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4553 player1Rating, player2Rating);
4554 gameInfo.whiteRating = player1Rating;
4555 gameInfo.blackRating = player2Rating;
4556 } else if (strcmp(player2Name, white) == 0 &&
4557 strcmp(player1Name, black) == 0) {
4558 if (appData.debugMode)
4559 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4560 player2Rating, player1Rating);
4561 gameInfo.whiteRating = player2Rating;
4562 gameInfo.blackRating = player1Rating;
4564 player1Name[0] = player2Name[0] = NULLCHAR;
4566 /* Silence shouts if requested */
4567 if (appData.quietPlay &&
4568 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4569 SendToICS(ics_prefix);
4570 SendToICS("set shout 0\n");
4574 /* Deal with midgame name changes */
4576 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4577 if (gameInfo.white) free(gameInfo.white);
4578 gameInfo.white = StrSave(white);
4580 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4581 if (gameInfo.black) free(gameInfo.black);
4582 gameInfo.black = StrSave(black);
4586 /* Throw away game result if anything actually changes in examine mode */
4587 if (gameMode == IcsExamining && !newGame) {
4588 gameInfo.result = GameUnfinished;
4589 if (gameInfo.resultDetails != NULL) {
4590 free(gameInfo.resultDetails);
4591 gameInfo.resultDetails = NULL;
4595 /* In pausing && IcsExamining mode, we ignore boards coming
4596 in if they are in a different variation than we are. */
4597 if (pauseExamInvalid) return;
4598 if (pausing && gameMode == IcsExamining) {
4599 if (moveNum <= pauseExamForwardMostMove) {
4600 pauseExamInvalid = TRUE;
4601 forwardMostMove = pauseExamForwardMostMove;
4606 if (appData.debugMode) {
4607 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4609 /* Parse the board */
4610 for (k = 0; k < ranks; k++) {
4611 for (j = 0; j < files; j++)
4612 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4613 if(gameInfo.holdingsWidth > 1) {
4614 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4615 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4618 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4619 board[5][BOARD_RGHT+1] = WhiteAngel;
4620 board[6][BOARD_RGHT+1] = WhiteMarshall;
4621 board[1][0] = BlackMarshall;
4622 board[2][0] = BlackAngel;
4623 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4625 CopyBoard(boards[moveNum], board);
4626 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4628 startedFromSetupPosition =
4629 !CompareBoards(board, initialPosition);
4630 if(startedFromSetupPosition)
4631 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4634 /* [HGM] Set castling rights. Take the outermost Rooks,
4635 to make it also work for FRC opening positions. Note that board12
4636 is really defective for later FRC positions, as it has no way to
4637 indicate which Rook can castle if they are on the same side of King.
4638 For the initial position we grant rights to the outermost Rooks,
4639 and remember thos rights, and we then copy them on positions
4640 later in an FRC game. This means WB might not recognize castlings with
4641 Rooks that have moved back to their original position as illegal,
4642 but in ICS mode that is not its job anyway.
4644 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4645 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4647 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4648 if(board[0][i] == WhiteRook) j = i;
4649 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4650 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4651 if(board[0][i] == WhiteRook) j = i;
4652 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4653 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4654 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4655 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4656 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4657 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4658 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4660 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4661 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4662 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4663 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4664 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4665 if(board[BOARD_HEIGHT-1][k] == bKing)
4666 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4667 if(gameInfo.variant == VariantTwoKings) {
4668 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4669 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4670 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4673 r = boards[moveNum][CASTLING][0] = initialRights[0];
4674 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4675 r = boards[moveNum][CASTLING][1] = initialRights[1];
4676 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4677 r = boards[moveNum][CASTLING][3] = initialRights[3];
4678 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4679 r = boards[moveNum][CASTLING][4] = initialRights[4];
4680 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4681 /* wildcastle kludge: always assume King has rights */
4682 r = boards[moveNum][CASTLING][2] = initialRights[2];
4683 r = boards[moveNum][CASTLING][5] = initialRights[5];
4685 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4686 boards[moveNum][EP_STATUS] = EP_NONE;
4687 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4688 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4689 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4692 if (ics_getting_history == H_GOT_REQ_HEADER ||
4693 ics_getting_history == H_GOT_UNREQ_HEADER) {
4694 /* This was an initial position from a move list, not
4695 the current position */
4699 /* Update currentMove and known move number limits */
4700 newMove = newGame || moveNum > forwardMostMove;
4703 forwardMostMove = backwardMostMove = currentMove = moveNum;
4704 if (gameMode == IcsExamining && moveNum == 0) {
4705 /* Workaround for ICS limitation: we are not told the wild
4706 type when starting to examine a game. But if we ask for
4707 the move list, the move list header will tell us */
4708 ics_getting_history = H_REQUESTED;
4709 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4712 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4713 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4715 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4716 /* [HGM] applied this also to an engine that is silently watching */
4717 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4718 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4719 gameInfo.variant == currentlyInitializedVariant) {
4720 takeback = forwardMostMove - moveNum;
4721 for (i = 0; i < takeback; i++) {
4722 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4723 SendToProgram("undo\n", &first);
4728 forwardMostMove = moveNum;
4729 if (!pausing || currentMove > forwardMostMove)
4730 currentMove = forwardMostMove;
4732 /* New part of history that is not contiguous with old part */
4733 if (pausing && gameMode == IcsExamining) {
4734 pauseExamInvalid = TRUE;
4735 forwardMostMove = pauseExamForwardMostMove;
4738 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4740 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4741 // [HGM] when we will receive the move list we now request, it will be
4742 // fed to the engine from the first move on. So if the engine is not
4743 // in the initial position now, bring it there.
4744 InitChessProgram(&first, 0);
4747 ics_getting_history = H_REQUESTED;
4748 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4751 forwardMostMove = backwardMostMove = currentMove = moveNum;
4754 /* Update the clocks */
4755 if (strchr(elapsed_time, '.')) {
4757 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4758 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4760 /* Time is in seconds */
4761 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4762 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4767 if (appData.zippyPlay && newGame &&
4768 gameMode != IcsObserving && gameMode != IcsIdle &&
4769 gameMode != IcsExamining)
4770 ZippyFirstBoard(moveNum, basetime, increment);
4773 /* Put the move on the move list, first converting
4774 to canonical algebraic form. */
4776 if (appData.debugMode) {
4777 int f = forwardMostMove;
4778 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4779 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4780 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4781 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4782 fprintf(debugFP, "moveNum = %d\n", moveNum);
4783 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4784 setbuf(debugFP, NULL);
4786 if (moveNum <= backwardMostMove) {
4787 /* We don't know what the board looked like before
4789 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4790 strcat(parseList[moveNum - 1], " ");
4791 strcat(parseList[moveNum - 1], elapsed_time);
4792 moveList[moveNum - 1][0] = NULLCHAR;
4793 } else if (strcmp(move_str, "none") == 0) {
4794 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4795 /* Again, we don't know what the board looked like;
4796 this is really the start of the game. */
4797 parseList[moveNum - 1][0] = NULLCHAR;
4798 moveList[moveNum - 1][0] = NULLCHAR;
4799 backwardMostMove = moveNum;
4800 startedFromSetupPosition = TRUE;
4801 fromX = fromY = toX = toY = -1;
4803 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4804 // So we parse the long-algebraic move string in stead of the SAN move
4805 int valid; char buf[MSG_SIZ], *prom;
4807 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4808 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4809 // str looks something like "Q/a1-a2"; kill the slash
4811 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4812 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4813 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4814 strcat(buf, prom); // long move lacks promo specification!
4815 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4816 if(appData.debugMode)
4817 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4818 safeStrCpy(move_str, buf, MSG_SIZ);
4820 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4821 &fromX, &fromY, &toX, &toY, &promoChar)
4822 || ParseOneMove(buf, moveNum - 1, &moveType,
4823 &fromX, &fromY, &toX, &toY, &promoChar);
4824 // end of long SAN patch
4826 (void) CoordsToAlgebraic(boards[moveNum - 1],
4827 PosFlags(moveNum - 1),
4828 fromY, fromX, toY, toX, promoChar,
4829 parseList[moveNum-1]);
4830 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4836 if(!IS_SHOGI(gameInfo.variant))
4837 strcat(parseList[moveNum - 1], "+");
4840 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4841 strcat(parseList[moveNum - 1], "#");
4844 strcat(parseList[moveNum - 1], " ");
4845 strcat(parseList[moveNum - 1], elapsed_time);
4846 /* currentMoveString is set as a side-effect of ParseOneMove */
4847 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4848 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4849 strcat(moveList[moveNum - 1], "\n");
4851 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4852 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4853 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4854 ChessSquare old, new = boards[moveNum][k][j];
4855 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4856 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4857 if(old == new) continue;
4858 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4859 else if(new == WhiteWazir || new == BlackWazir) {
4860 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4861 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4862 else boards[moveNum][k][j] = old; // preserve type of Gold
4863 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4864 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4867 /* Move from ICS was illegal!? Punt. */
4868 if (appData.debugMode) {
4869 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4870 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4872 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4873 strcat(parseList[moveNum - 1], " ");
4874 strcat(parseList[moveNum - 1], elapsed_time);
4875 moveList[moveNum - 1][0] = NULLCHAR;
4876 fromX = fromY = toX = toY = -1;
4879 if (appData.debugMode) {
4880 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4881 setbuf(debugFP, NULL);
4885 /* Send move to chess program (BEFORE animating it). */
4886 if (appData.zippyPlay && !newGame && newMove &&
4887 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4889 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4890 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4891 if (moveList[moveNum - 1][0] == NULLCHAR) {
4892 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4894 DisplayError(str, 0);
4896 if (first.sendTime) {
4897 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4899 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4900 if (firstMove && !bookHit) {
4902 if (first.useColors) {
4903 SendToProgram(gameMode == IcsPlayingWhite ?
4905 "black\ngo\n", &first);
4907 SendToProgram("go\n", &first);
4909 first.maybeThinking = TRUE;
4912 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4913 if (moveList[moveNum - 1][0] == NULLCHAR) {
4914 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4915 DisplayError(str, 0);
4917 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4918 SendMoveToProgram(moveNum - 1, &first);
4925 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4926 /* If move comes from a remote source, animate it. If it
4927 isn't remote, it will have already been animated. */
4928 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4929 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4931 if (!pausing && appData.highlightLastMove) {
4932 SetHighlights(fromX, fromY, toX, toY);
4936 /* Start the clocks */
4937 whiteFlag = blackFlag = FALSE;
4938 appData.clockMode = !(basetime == 0 && increment == 0);
4940 ics_clock_paused = TRUE;
4942 } else if (ticking == 1) {
4943 ics_clock_paused = FALSE;
4945 if (gameMode == IcsIdle ||
4946 relation == RELATION_OBSERVING_STATIC ||
4947 relation == RELATION_EXAMINING ||
4949 DisplayBothClocks();
4953 /* Display opponents and material strengths */
4954 if (gameInfo.variant != VariantBughouse &&
4955 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4956 if (tinyLayout || smallLayout) {
4957 if(gameInfo.variant == VariantNormal)
4958 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4959 gameInfo.white, white_stren, gameInfo.black, black_stren,
4960 basetime, increment);
4962 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4963 gameInfo.white, white_stren, gameInfo.black, black_stren,
4964 basetime, increment, (int) gameInfo.variant);
4966 if(gameInfo.variant == VariantNormal)
4967 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4968 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4969 basetime, increment);
4971 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4972 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4973 basetime, increment, VariantName(gameInfo.variant));
4976 if (appData.debugMode) {
4977 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4982 /* Display the board */
4983 if (!pausing && !appData.noGUI) {
4985 if (appData.premove)
4987 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4988 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4989 ClearPremoveHighlights();
4991 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4992 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4993 DrawPosition(j, boards[currentMove]);
4995 DisplayMove(moveNum - 1);
4996 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4997 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4998 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4999 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5003 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5005 if(bookHit) { // [HGM] book: simulate book reply
5006 static char bookMove[MSG_SIZ]; // a bit generous?
5008 programStats.nodes = programStats.depth = programStats.time =
5009 programStats.score = programStats.got_only_move = 0;
5010 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5012 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5013 strcat(bookMove, bookHit);
5014 HandleMachineMove(bookMove, &first);
5023 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5024 ics_getting_history = H_REQUESTED;
5025 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5031 SendToBoth (char *msg)
5032 { // to make it easy to keep two engines in step in dual analysis
5033 SendToProgram(msg, &first);
5034 if(second.analyzing) SendToProgram(msg, &second);
5038 AnalysisPeriodicEvent (int force)
5040 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5041 && !force) || !appData.periodicUpdates)
5044 /* Send . command to Crafty to collect stats */
5047 /* Don't send another until we get a response (this makes
5048 us stop sending to old Crafty's which don't understand
5049 the "." command (sending illegal cmds resets node count & time,
5050 which looks bad)) */
5051 programStats.ok_to_send = 0;
5055 ics_update_width (int new_width)
5057 ics_printf("set width %d\n", new_width);
5061 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5065 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5066 if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5067 sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5068 SendToProgram(buf, cps);
5071 // null move in variant where engine does not understand it (for analysis purposes)
5072 SendBoard(cps, moveNum + 1); // send position after move in stead.
5075 if (cps->useUsermove) {
5076 SendToProgram("usermove ", cps);
5080 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5081 int len = space - parseList[moveNum];
5082 memcpy(buf, parseList[moveNum], len);
5084 buf[len] = NULLCHAR;
5086 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5088 SendToProgram(buf, cps);
5090 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5091 AlphaRank(moveList[moveNum], 4);
5092 SendToProgram(moveList[moveNum], cps);
5093 AlphaRank(moveList[moveNum], 4); // and back
5095 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5096 * the engine. It would be nice to have a better way to identify castle
5098 if(appData.fischerCastling && cps->useOOCastle) {
5099 int fromX = moveList[moveNum][0] - AAA;
5100 int fromY = moveList[moveNum][1] - ONE;
5101 int toX = moveList[moveNum][2] - AAA;
5102 int toY = moveList[moveNum][3] - ONE;
5103 if((boards[moveNum][fromY][fromX] == WhiteKing
5104 && boards[moveNum][toY][toX] == WhiteRook)
5105 || (boards[moveNum][fromY][fromX] == BlackKing
5106 && boards[moveNum][toY][toX] == BlackRook)) {
5107 if(toX > fromX) SendToProgram("O-O\n", cps);
5108 else SendToProgram("O-O-O\n", cps);
5110 else SendToProgram(moveList[moveNum], cps);
5112 if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5113 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5114 moveList[moveNum][5], moveList[moveNum][6] - '0',
5115 moveList[moveNum][5], moveList[moveNum][6] - '0',
5116 moveList[moveNum][2], moveList[moveNum][3] - '0');
5117 SendToProgram(buf, cps);
5119 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5120 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5121 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5122 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5123 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5125 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5126 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5127 SendToProgram(buf, cps);
5129 else SendToProgram(moveList[moveNum], cps);
5130 /* End of additions by Tord */
5133 /* [HGM] setting up the opening has brought engine in force mode! */
5134 /* Send 'go' if we are in a mode where machine should play. */
5135 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5136 (gameMode == TwoMachinesPlay ||
5138 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5140 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5141 SendToProgram("go\n", cps);
5142 if (appData.debugMode) {
5143 fprintf(debugFP, "(extra)\n");
5146 setboardSpoiledMachineBlack = 0;
5150 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5152 char user_move[MSG_SIZ];
5155 if(gameInfo.variant == VariantSChess && promoChar) {
5156 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5157 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5158 } else suffix[0] = NULLCHAR;
5162 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5163 (int)moveType, fromX, fromY, toX, toY);
5164 DisplayError(user_move + strlen("say "), 0);
5166 case WhiteKingSideCastle:
5167 case BlackKingSideCastle:
5168 case WhiteQueenSideCastleWild:
5169 case BlackQueenSideCastleWild:
5171 case WhiteHSideCastleFR:
5172 case BlackHSideCastleFR:
5174 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5176 case WhiteQueenSideCastle:
5177 case BlackQueenSideCastle:
5178 case WhiteKingSideCastleWild:
5179 case BlackKingSideCastleWild:
5181 case WhiteASideCastleFR:
5182 case BlackASideCastleFR:
5184 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5186 case WhiteNonPromotion:
5187 case BlackNonPromotion:
5188 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5190 case WhitePromotion:
5191 case BlackPromotion:
5192 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5193 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5194 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5195 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5196 PieceToChar(WhiteFerz));
5197 else if(gameInfo.variant == VariantGreat)
5198 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5199 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5200 PieceToChar(WhiteMan));
5202 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5203 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5209 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5210 ToUpper(PieceToChar((ChessSquare) fromX)),
5211 AAA + toX, ONE + toY);
5213 case IllegalMove: /* could be a variant we don't quite understand */
5214 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5216 case WhiteCapturesEnPassant:
5217 case BlackCapturesEnPassant:
5218 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5219 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5222 SendToICS(user_move);
5223 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5224 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5229 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5230 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5231 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5232 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5233 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5236 if(gameMode != IcsExamining) { // is this ever not the case?
5237 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5239 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5240 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5241 } else { // on FICS we must first go to general examine mode
5242 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5244 if(gameInfo.variant != VariantNormal) {
5245 // try figure out wild number, as xboard names are not always valid on ICS
5246 for(i=1; i<=36; i++) {
5247 snprintf(buf, MSG_SIZ, "wild/%d", i);
5248 if(StringToVariant(buf) == gameInfo.variant) break;
5250 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5251 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5252 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5253 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5254 SendToICS(ics_prefix);
5256 if(startedFromSetupPosition || backwardMostMove != 0) {
5257 fen = PositionToFEN(backwardMostMove, NULL, 1);
5258 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5259 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5261 } else { // FICS: everything has to set by separate bsetup commands
5262 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5263 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5265 if(!WhiteOnMove(backwardMostMove)) {
5266 SendToICS("bsetup tomove black\n");
5268 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5269 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5271 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5272 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5274 i = boards[backwardMostMove][EP_STATUS];
5275 if(i >= 0) { // set e.p.
5276 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5282 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5283 SendToICS("bsetup done\n"); // switch to normal examining.
5285 for(i = backwardMostMove; i<last; i++) {
5287 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5288 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5289 int len = strlen(moveList[i]);
5290 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5291 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5295 SendToICS(ics_prefix);
5296 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5299 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5302 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5304 if (rf == DROP_RANK) {
5305 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5306 sprintf(move, "%c@%c%c\n",
5307 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5309 if (promoChar == 'x' || promoChar == NULLCHAR) {
5310 sprintf(move, "%c%c%c%c\n",
5311 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5312 if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5314 sprintf(move, "%c%c%c%c%c\n",
5315 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5321 ProcessICSInitScript (FILE *f)
5325 while (fgets(buf, MSG_SIZ, f)) {
5326 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5333 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5335 static ClickType lastClickType;
5338 Partner (ChessSquare *p)
5339 { // change piece into promotion partner if one shogi-promotes to the other
5340 int stride = gameInfo.variant == VariantChu ? 22 : 11;
5341 ChessSquare partner;
5342 partner = (*p/stride & 1 ? *p - stride : *p + stride);
5343 if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5351 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5352 static int toggleFlag;
5353 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5354 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5355 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5356 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5357 if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5358 if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5360 if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5361 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5362 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5363 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5364 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5365 if(!step) step = -1;
5366 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5367 !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5368 appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5369 (promoSweep == WhiteLion || promoSweep == BlackLion)));
5371 int victim = boards[currentMove][toY][toX];
5372 boards[currentMove][toY][toX] = promoSweep;
5373 DrawPosition(FALSE, boards[currentMove]);
5374 boards[currentMove][toY][toX] = victim;
5376 ChangeDragPiece(promoSweep);
5380 PromoScroll (int x, int y)
5384 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5385 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5386 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5387 if(!step) return FALSE;
5388 lastX = x; lastY = y;
5389 if((promoSweep < BlackPawn) == flipView) step = -step;
5390 if(step > 0) selectFlag = 1;
5391 if(!selectFlag) Sweep(step);
5396 NextPiece (int step)
5398 ChessSquare piece = boards[currentMove][toY][toX];
5401 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5402 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5403 if(!step) step = -1;
5404 } while(PieceToChar(pieceSweep) == '.');
5405 boards[currentMove][toY][toX] = pieceSweep;
5406 DrawPosition(FALSE, boards[currentMove]);
5407 boards[currentMove][toY][toX] = piece;
5409 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5411 AlphaRank (char *move, int n)
5413 // char *p = move, c; int x, y;
5415 if (appData.debugMode) {
5416 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5420 move[2]>='0' && move[2]<='9' &&
5421 move[3]>='a' && move[3]<='x' ) {
5423 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5424 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5426 if(move[0]>='0' && move[0]<='9' &&
5427 move[1]>='a' && move[1]<='x' &&
5428 move[2]>='0' && move[2]<='9' &&
5429 move[3]>='a' && move[3]<='x' ) {
5430 /* input move, Shogi -> normal */
5431 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5432 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5433 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5434 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5437 move[3]>='0' && move[3]<='9' &&
5438 move[2]>='a' && move[2]<='x' ) {
5440 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5441 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5444 move[0]>='a' && move[0]<='x' &&
5445 move[3]>='0' && move[3]<='9' &&
5446 move[2]>='a' && move[2]<='x' ) {
5447 /* output move, normal -> Shogi */
5448 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5449 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5450 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5451 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5452 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5454 if (appData.debugMode) {
5455 fprintf(debugFP, " out = '%s'\n", move);
5459 char yy_textstr[8000];
5461 /* Parser for moves from gnuchess, ICS, or user typein box */
5463 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5465 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5467 switch (*moveType) {
5468 case WhitePromotion:
5469 case BlackPromotion:
5470 case WhiteNonPromotion:
5471 case BlackNonPromotion:
5474 case WhiteCapturesEnPassant:
5475 case BlackCapturesEnPassant:
5476 case WhiteKingSideCastle:
5477 case WhiteQueenSideCastle:
5478 case BlackKingSideCastle:
5479 case BlackQueenSideCastle:
5480 case WhiteKingSideCastleWild:
5481 case WhiteQueenSideCastleWild:
5482 case BlackKingSideCastleWild:
5483 case BlackQueenSideCastleWild:
5484 /* Code added by Tord: */
5485 case WhiteHSideCastleFR:
5486 case WhiteASideCastleFR:
5487 case BlackHSideCastleFR:
5488 case BlackASideCastleFR:
5489 /* End of code added by Tord */
5490 case IllegalMove: /* bug or odd chess variant */
5491 *fromX = currentMoveString[0] - AAA;
5492 *fromY = currentMoveString[1] - ONE;
5493 *toX = currentMoveString[2] - AAA;
5494 *toY = currentMoveString[3] - ONE;
5495 *promoChar = currentMoveString[4];
5496 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5497 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5498 if (appData.debugMode) {
5499 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5501 *fromX = *fromY = *toX = *toY = 0;
5504 if (appData.testLegality) {
5505 return (*moveType != IllegalMove);
5507 return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5508 // [HGM] lion: if this is a double move we are less critical
5509 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5514 *fromX = *moveType == WhiteDrop ?
5515 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5516 (int) CharToPiece(ToLower(currentMoveString[0]));
5518 *toX = currentMoveString[2] - AAA;
5519 *toY = currentMoveString[3] - ONE;
5520 *promoChar = NULLCHAR;
5524 case ImpossibleMove:
5534 if (appData.debugMode) {
5535 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5538 *fromX = *fromY = *toX = *toY = 0;
5539 *promoChar = NULLCHAR;
5544 Boolean pushed = FALSE;
5545 char *lastParseAttempt;
5548 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5549 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5550 int fromX, fromY, toX, toY; char promoChar;
5555 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5556 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5557 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5560 endPV = forwardMostMove;
5562 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5563 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5564 lastParseAttempt = pv;
5565 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5566 if(!valid && nr == 0 &&
5567 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5568 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5569 // Hande case where played move is different from leading PV move
5570 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5571 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5572 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5573 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5574 endPV += 2; // if position different, keep this
5575 moveList[endPV-1][0] = fromX + AAA;
5576 moveList[endPV-1][1] = fromY + ONE;
5577 moveList[endPV-1][2] = toX + AAA;
5578 moveList[endPV-1][3] = toY + ONE;
5579 parseList[endPV-1][0] = NULLCHAR;
5580 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5583 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5584 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5585 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5586 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5587 valid++; // allow comments in PV
5591 if(endPV+1 > framePtr) break; // no space, truncate
5594 CopyBoard(boards[endPV], boards[endPV-1]);
5595 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5596 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5597 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5598 CoordsToAlgebraic(boards[endPV - 1],
5599 PosFlags(endPV - 1),
5600 fromY, fromX, toY, toX, promoChar,
5601 parseList[endPV - 1]);
5603 if(atEnd == 2) return; // used hidden, for PV conversion
5604 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5605 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5606 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5607 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5608 DrawPosition(TRUE, boards[currentMove]);
5612 MultiPV (ChessProgramState *cps)
5613 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5615 for(i=0; i<cps->nrOptions; i++)
5616 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5621 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5624 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5626 int startPV, multi, lineStart, origIndex = index;
5627 char *p, buf2[MSG_SIZ];
5628 ChessProgramState *cps = (pane ? &second : &first);
5630 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5631 lastX = x; lastY = y;
5632 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5633 lineStart = startPV = index;
5634 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5635 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5637 do{ while(buf[index] && buf[index] != '\n') index++;
5638 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5640 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5641 int n = cps->option[multi].value;
5642 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5643 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5644 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5645 cps->option[multi].value = n;
5648 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5649 ExcludeClick(origIndex - lineStart);
5651 } else if(!strncmp(buf+lineStart, "dep\t", 4)) { // column headers clicked
5652 Collapse(origIndex - lineStart);
5655 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5656 *start = startPV; *end = index-1;
5657 extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5664 static char buf[10*MSG_SIZ];
5665 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5667 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5668 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5669 for(i = forwardMostMove; i<endPV; i++){
5670 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5671 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5674 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5675 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5676 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5682 LoadPV (int x, int y)
5683 { // called on right mouse click to load PV
5684 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5685 lastX = x; lastY = y;
5686 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5694 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5695 if(endPV < 0) return;
5696 if(appData.autoCopyPV) CopyFENToClipboard();
5698 if(extendGame && currentMove > forwardMostMove) {
5699 Boolean saveAnimate = appData.animate;
5701 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5702 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5703 } else storedGames--; // abandon shelved tail of original game
5706 forwardMostMove = currentMove;
5707 currentMove = oldFMM;
5708 appData.animate = FALSE;
5709 ToNrEvent(forwardMostMove);
5710 appData.animate = saveAnimate;
5712 currentMove = forwardMostMove;
5713 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5714 ClearPremoveHighlights();
5715 DrawPosition(TRUE, boards[currentMove]);
5719 MovePV (int x, int y, int h)
5720 { // step through PV based on mouse coordinates (called on mouse move)
5721 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5723 // we must somehow check if right button is still down (might be released off board!)
5724 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5725 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5726 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5728 lastX = x; lastY = y;
5730 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5731 if(endPV < 0) return;
5732 if(y < margin) step = 1; else
5733 if(y > h - margin) step = -1;
5734 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5735 currentMove += step;
5736 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5737 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5738 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5739 DrawPosition(FALSE, boards[currentMove]);
5743 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5744 // All positions will have equal probability, but the current method will not provide a unique
5745 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5751 int piecesLeft[(int)BlackPawn];
5752 int seed, nrOfShuffles;
5755 GetPositionNumber ()
5756 { // sets global variable seed
5759 seed = appData.defaultFrcPosition;
5760 if(seed < 0) { // randomize based on time for negative FRC position numbers
5761 for(i=0; i<50; i++) seed += random();
5762 seed = random() ^ random() >> 8 ^ random() << 8;
5763 if(seed<0) seed = -seed;
5768 put (Board board, int pieceType, int rank, int n, int shade)
5769 // put the piece on the (n-1)-th empty squares of the given shade
5773 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5774 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5775 board[rank][i] = (ChessSquare) pieceType;
5776 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5778 piecesLeft[pieceType]--;
5787 AddOnePiece (Board board, int pieceType, int rank, int shade)
5788 // calculate where the next piece goes, (any empty square), and put it there
5792 i = seed % squaresLeft[shade];
5793 nrOfShuffles *= squaresLeft[shade];
5794 seed /= squaresLeft[shade];
5795 put(board, pieceType, rank, i, shade);
5799 AddTwoPieces (Board board, int pieceType, int rank)
5800 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5802 int i, n=squaresLeft[ANY], j=n-1, k;
5804 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5805 i = seed % k; // pick one
5808 while(i >= j) i -= j--;
5809 j = n - 1 - j; i += j;
5810 put(board, pieceType, rank, j, ANY);
5811 put(board, pieceType, rank, i, ANY);
5815 SetUpShuffle (Board board, int number)
5819 GetPositionNumber(); nrOfShuffles = 1;
5821 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5822 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5823 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5825 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5827 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5828 p = (int) board[0][i];
5829 if(p < (int) BlackPawn) piecesLeft[p] ++;
5830 board[0][i] = EmptySquare;
5833 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5834 // shuffles restricted to allow normal castling put KRR first
5835 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5836 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5837 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5838 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5839 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5840 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5841 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5842 put(board, WhiteRook, 0, 0, ANY);
5843 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5846 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5847 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5848 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5849 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5850 while(piecesLeft[p] >= 2) {
5851 AddOnePiece(board, p, 0, LITE);
5852 AddOnePiece(board, p, 0, DARK);
5854 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5857 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5858 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5859 // but we leave King and Rooks for last, to possibly obey FRC restriction
5860 if(p == (int)WhiteRook) continue;
5861 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5862 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5865 // now everything is placed, except perhaps King (Unicorn) and Rooks
5867 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5868 // Last King gets castling rights
5869 while(piecesLeft[(int)WhiteUnicorn]) {
5870 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5871 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5874 while(piecesLeft[(int)WhiteKing]) {
5875 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5876 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5881 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5882 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5885 // Only Rooks can be left; simply place them all
5886 while(piecesLeft[(int)WhiteRook]) {
5887 i = put(board, WhiteRook, 0, 0, ANY);
5888 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5891 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5893 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5896 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5897 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5900 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5904 SetCharTable (char *table, const char * map)
5905 /* [HGM] moved here from winboard.c because of its general usefulness */
5906 /* Basically a safe strcpy that uses the last character as King */
5908 int result = FALSE; int NrPieces;
5910 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5911 && NrPieces >= 12 && !(NrPieces&1)) {
5912 int i; /* [HGM] Accept even length from 12 to 34 */
5914 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5915 for( i=0; i<NrPieces/2-1; i++ ) {
5917 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5919 table[(int) WhiteKing] = map[NrPieces/2-1];
5920 table[(int) BlackKing] = map[NrPieces-1];
5929 Prelude (Board board)
5930 { // [HGM] superchess: random selection of exo-pieces
5931 int i, j, k; ChessSquare p;
5932 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5934 GetPositionNumber(); // use FRC position number
5936 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5937 SetCharTable(pieceToChar, appData.pieceToCharTable);
5938 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5939 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5942 j = seed%4; seed /= 4;
5943 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5944 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5945 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5946 j = seed%3 + (seed%3 >= j); seed /= 3;
5947 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5948 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5949 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5950 j = seed%3; seed /= 3;
5951 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5952 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5953 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5954 j = seed%2 + (seed%2 >= j); seed /= 2;
5955 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5956 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5957 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5958 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5959 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5960 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5961 put(board, exoPieces[0], 0, 0, ANY);
5962 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5966 InitPosition (int redraw)
5968 ChessSquare (* pieces)[BOARD_FILES];
5969 int i, j, pawnRow=1, pieceRows=1, overrule,
5970 oldx = gameInfo.boardWidth,
5971 oldy = gameInfo.boardHeight,
5972 oldh = gameInfo.holdingsWidth;
5975 if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5977 /* [AS] Initialize pv info list [HGM] and game status */
5979 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5980 pvInfoList[i].depth = 0;
5981 boards[i][EP_STATUS] = EP_NONE;
5982 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5985 initialRulePlies = 0; /* 50-move counter start */
5987 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5988 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5992 /* [HGM] logic here is completely changed. In stead of full positions */
5993 /* the initialized data only consist of the two backranks. The switch */
5994 /* selects which one we will use, which is than copied to the Board */
5995 /* initialPosition, which for the rest is initialized by Pawns and */
5996 /* empty squares. This initial position is then copied to boards[0], */
5997 /* possibly after shuffling, so that it remains available. */
5999 gameInfo.holdingsWidth = 0; /* default board sizes */
6000 gameInfo.boardWidth = 8;
6001 gameInfo.boardHeight = 8;
6002 gameInfo.holdingsSize = 0;
6003 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6004 for(i=0; i<BOARD_FILES-2; i++)
6005 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6006 initialPosition[EP_STATUS] = EP_NONE;
6007 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6008 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6009 SetCharTable(pieceNickName, appData.pieceNickNames);
6010 else SetCharTable(pieceNickName, "............");
6013 switch (gameInfo.variant) {
6014 case VariantFischeRandom:
6015 shuffleOpenings = TRUE;
6016 appData.fischerCastling = TRUE;
6019 case VariantShatranj:
6020 pieces = ShatranjArray;
6021 nrCastlingRights = 0;
6022 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6025 pieces = makrukArray;
6026 nrCastlingRights = 0;
6027 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6030 pieces = aseanArray;
6031 nrCastlingRights = 0;
6032 SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6034 case VariantTwoKings:
6035 pieces = twoKingsArray;
6038 pieces = GrandArray;
6039 nrCastlingRights = 0;
6040 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6041 gameInfo.boardWidth = 10;
6042 gameInfo.boardHeight = 10;
6043 gameInfo.holdingsSize = 7;
6045 case VariantCapaRandom:
6046 shuffleOpenings = TRUE;
6047 appData.fischerCastling = TRUE;
6048 case VariantCapablanca:
6049 pieces = CapablancaArray;
6050 gameInfo.boardWidth = 10;
6051 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6054 pieces = GothicArray;
6055 gameInfo.boardWidth = 10;
6056 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6059 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6060 gameInfo.holdingsSize = 7;
6061 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6064 pieces = JanusArray;
6065 gameInfo.boardWidth = 10;
6066 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6067 nrCastlingRights = 6;
6068 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6069 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6070 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6071 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6072 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6073 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6076 pieces = FalconArray;
6077 gameInfo.boardWidth = 10;
6078 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6080 case VariantXiangqi:
6081 pieces = XiangqiArray;
6082 gameInfo.boardWidth = 9;
6083 gameInfo.boardHeight = 10;
6084 nrCastlingRights = 0;
6085 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6088 pieces = ShogiArray;
6089 gameInfo.boardWidth = 9;
6090 gameInfo.boardHeight = 9;
6091 gameInfo.holdingsSize = 7;
6092 nrCastlingRights = 0;
6093 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6096 pieces = ChuArray; pieceRows = 3;
6097 gameInfo.boardWidth = 12;
6098 gameInfo.boardHeight = 12;
6099 nrCastlingRights = 0;
6100 SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6101 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6103 case VariantCourier:
6104 pieces = CourierArray;
6105 gameInfo.boardWidth = 12;
6106 nrCastlingRights = 0;
6107 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6109 case VariantKnightmate:
6110 pieces = KnightmateArray;
6111 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6113 case VariantSpartan:
6114 pieces = SpartanArray;
6115 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6119 SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6121 case VariantChuChess:
6122 pieces = ChuChessArray;
6123 gameInfo.boardWidth = 10;
6124 gameInfo.boardHeight = 10;
6125 SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6128 pieces = fairyArray;
6129 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6132 pieces = GreatArray;
6133 gameInfo.boardWidth = 10;
6134 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6135 gameInfo.holdingsSize = 8;
6139 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6140 gameInfo.holdingsSize = 8;
6141 startedFromSetupPosition = TRUE;
6143 case VariantCrazyhouse:
6144 case VariantBughouse:
6146 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6147 gameInfo.holdingsSize = 5;
6149 case VariantWildCastle:
6151 /* !!?shuffle with kings guaranteed to be on d or e file */
6152 shuffleOpenings = 1;
6154 case VariantNoCastle:
6156 nrCastlingRights = 0;
6157 /* !!?unconstrained back-rank shuffle */
6158 shuffleOpenings = 1;
6163 if(appData.NrFiles >= 0) {
6164 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6165 gameInfo.boardWidth = appData.NrFiles;
6167 if(appData.NrRanks >= 0) {
6168 gameInfo.boardHeight = appData.NrRanks;
6170 if(appData.holdingsSize >= 0) {
6171 i = appData.holdingsSize;
6172 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6173 gameInfo.holdingsSize = i;
6175 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6176 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6177 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6179 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6180 if(pawnRow < 1) pawnRow = 1;
6181 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6182 gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6183 if(gameInfo.variant == VariantChu) pawnRow = 3;
6185 /* User pieceToChar list overrules defaults */
6186 if(appData.pieceToCharTable != NULL)
6187 SetCharTable(pieceToChar, appData.pieceToCharTable);
6189 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6191 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6192 s = (ChessSquare) 0; /* account holding counts in guard band */
6193 for( i=0; i<BOARD_HEIGHT; i++ )
6194 initialPosition[i][j] = s;
6196 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6197 initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6198 initialPosition[pawnRow][j] = WhitePawn;
6199 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6200 if(gameInfo.variant == VariantXiangqi) {
6202 initialPosition[pawnRow][j] =
6203 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6204 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6205 initialPosition[2][j] = WhiteCannon;
6206 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6210 if(gameInfo.variant == VariantChu) {
6211 if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6212 initialPosition[pawnRow+1][j] = WhiteCobra,
6213 initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6214 for(i=1; i<pieceRows; i++) {
6215 initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6216 initialPosition[BOARD_HEIGHT-1-i][j] = pieces[2*i+1][j-gameInfo.holdingsWidth];
6219 if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6220 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6221 initialPosition[0][j] = WhiteRook;
6222 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6225 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] = pieces[1][j-gameInfo.holdingsWidth];
6227 if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6228 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6231 initialPosition[1][j] = WhiteBishop;
6232 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6234 initialPosition[1][j] = WhiteRook;
6235 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6238 if( nrCastlingRights == -1) {
6239 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6240 /* This sets default castling rights from none to normal corners */
6241 /* Variants with other castling rights must set them themselves above */
6242 nrCastlingRights = 6;
6244 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6245 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6246 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6247 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6248 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6249 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6252 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6253 if(gameInfo.variant == VariantGreat) { // promotion commoners
6254 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6255 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6256 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6257 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6259 if( gameInfo.variant == VariantSChess ) {
6260 initialPosition[1][0] = BlackMarshall;
6261 initialPosition[2][0] = BlackAngel;
6262 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6263 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6264 initialPosition[1][1] = initialPosition[2][1] =
6265 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6267 if (appData.debugMode) {
6268 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6270 if(shuffleOpenings) {
6271 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6272 startedFromSetupPosition = TRUE;
6274 if(startedFromPositionFile) {
6275 /* [HGM] loadPos: use PositionFile for every new game */
6276 CopyBoard(initialPosition, filePosition);
6277 for(i=0; i<nrCastlingRights; i++)
6278 initialRights[i] = filePosition[CASTLING][i];
6279 startedFromSetupPosition = TRUE;
6282 CopyBoard(boards[0], initialPosition);
6284 if(oldx != gameInfo.boardWidth ||
6285 oldy != gameInfo.boardHeight ||
6286 oldv != gameInfo.variant ||
6287 oldh != gameInfo.holdingsWidth
6289 InitDrawingSizes(-2 ,0);
6291 oldv = gameInfo.variant;
6293 DrawPosition(TRUE, boards[currentMove]);
6297 SendBoard (ChessProgramState *cps, int moveNum)
6299 char message[MSG_SIZ];
6301 if (cps->useSetboard) {
6302 char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6303 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6304 SendToProgram(message, cps);
6309 int i, j, left=0, right=BOARD_WIDTH;
6310 /* Kludge to set black to move, avoiding the troublesome and now
6311 * deprecated "black" command.
6313 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6314 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6316 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6318 SendToProgram("edit\n", cps);
6319 SendToProgram("#\n", cps);
6320 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6321 bp = &boards[moveNum][i][left];
6322 for (j = left; j < right; j++, bp++) {
6323 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6324 if ((int) *bp < (int) BlackPawn) {
6325 if(j == BOARD_RGHT+1)
6326 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6327 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6328 if(message[0] == '+' || message[0] == '~') {
6329 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6330 PieceToChar((ChessSquare)(DEMOTED *bp)),
6333 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6334 message[1] = BOARD_RGHT - 1 - j + '1';
6335 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6337 SendToProgram(message, cps);
6342 SendToProgram("c\n", cps);
6343 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6344 bp = &boards[moveNum][i][left];
6345 for (j = left; j < right; j++, bp++) {
6346 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6347 if (((int) *bp != (int) EmptySquare)
6348 && ((int) *bp >= (int) BlackPawn)) {
6349 if(j == BOARD_LEFT-2)
6350 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6351 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6353 if(message[0] == '+' || message[0] == '~') {
6354 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6355 PieceToChar((ChessSquare)(DEMOTED *bp)),
6358 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6359 message[1] = BOARD_RGHT - 1 - j + '1';
6360 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6362 SendToProgram(message, cps);
6367 SendToProgram(".\n", cps);
6369 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6372 char exclusionHeader[MSG_SIZ];
6373 int exCnt, excludePtr;
6374 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6375 static Exclusion excluTab[200];
6376 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6382 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6383 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6389 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6390 excludePtr = 24; exCnt = 0;
6395 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6396 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6397 char buf[2*MOVE_LEN], *p;
6398 Exclusion *e = excluTab;
6400 for(i=0; i<exCnt; i++)
6401 if(e[i].ff == fromX && e[i].fr == fromY &&
6402 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6403 if(i == exCnt) { // was not in exclude list; add it
6404 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6405 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6406 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6409 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6410 excludePtr++; e[i].mark = excludePtr++;
6411 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6414 exclusionHeader[e[i].mark] = state;
6418 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6419 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6423 if((signed char)promoChar == -1) { // kludge to indicate best move
6424 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6425 return 1; // if unparsable, abort
6427 // update exclusion map (resolving toggle by consulting existing state)
6428 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6430 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6431 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6432 excludeMap[k] |= 1<<j;
6433 else excludeMap[k] &= ~(1<<j);
6435 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6437 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6438 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6440 return (state == '+');
6444 ExcludeClick (int index)
6447 Exclusion *e = excluTab;
6448 if(index < 25) { // none, best or tail clicked
6449 if(index < 13) { // none: include all
6450 WriteMap(0); // clear map
6451 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6452 SendToBoth("include all\n"); // and inform engine
6453 } else if(index > 18) { // tail
6454 if(exclusionHeader[19] == '-') { // tail was excluded
6455 SendToBoth("include all\n");
6456 WriteMap(0); // clear map completely
6457 // now re-exclude selected moves
6458 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6459 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6460 } else { // tail was included or in mixed state
6461 SendToBoth("exclude all\n");
6462 WriteMap(0xFF); // fill map completely
6463 // now re-include selected moves
6464 j = 0; // count them
6465 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6466 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6467 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6470 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6473 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6474 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6475 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6482 DefaultPromoChoice (int white)
6485 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6486 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6487 result = WhiteFerz; // no choice
6488 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6489 result= WhiteKing; // in Suicide Q is the last thing we want
6490 else if(gameInfo.variant == VariantSpartan)
6491 result = white ? WhiteQueen : WhiteAngel;
6492 else result = WhiteQueen;
6493 if(!white) result = WHITE_TO_BLACK result;
6497 static int autoQueen; // [HGM] oneclick
6500 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6502 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6503 /* [HGM] add Shogi promotions */
6504 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6505 ChessSquare piece, partner;
6509 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6510 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6512 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6513 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6516 piece = boards[currentMove][fromY][fromX];
6517 if(gameInfo.variant == VariantChu) {
6518 int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6519 promotionZoneSize = BOARD_HEIGHT/3;
6520 highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6521 } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6522 promotionZoneSize = BOARD_HEIGHT/3;
6523 highestPromotingPiece = (int)WhiteAlfil;
6524 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6525 promotionZoneSize = 3;
6528 // Treat Lance as Pawn when it is not representing Amazon or Lance
6529 if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6530 if(piece == WhiteLance) piece = WhitePawn; else
6531 if(piece == BlackLance) piece = BlackPawn;
6534 // next weed out all moves that do not touch the promotion zone at all
6535 if((int)piece >= BlackPawn) {
6536 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6538 if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6539 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6541 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6542 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6543 if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6547 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6549 // weed out mandatory Shogi promotions
6550 if(gameInfo.variant == VariantShogi) {
6551 if(piece >= BlackPawn) {
6552 if(toY == 0 && piece == BlackPawn ||
6553 toY == 0 && piece == BlackQueen ||
6554 toY <= 1 && piece == BlackKnight) {
6559 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6560 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6561 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6568 // weed out obviously illegal Pawn moves
6569 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6570 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6571 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6572 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6573 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6574 // note we are not allowed to test for valid (non-)capture, due to premove
6577 // we either have a choice what to promote to, or (in Shogi) whether to promote
6578 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6579 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6580 ChessSquare p=BlackFerz; // no choice
6581 while(p < EmptySquare) { //but make sure we use piece that exists
6582 *promoChoice = PieceToChar(p++);
6583 if(*promoChoice != '.') break;
6587 // no sense asking what we must promote to if it is going to explode...
6588 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6589 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6592 // give caller the default choice even if we will not make it
6593 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6594 partner = piece; // pieces can promote if the pieceToCharTable says so
6595 if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6596 else if(Partner(&partner)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6597 if( sweepSelect && gameInfo.variant != VariantGreat
6598 && gameInfo.variant != VariantGrand
6599 && gameInfo.variant != VariantSuper) return FALSE;
6600 if(autoQueen) return FALSE; // predetermined
6602 // suppress promotion popup on illegal moves that are not premoves
6603 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6604 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6605 if(appData.testLegality && !premove) {
6606 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6607 fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6608 if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6609 if(moveType != WhitePromotion && moveType != BlackPromotion)
6617 InPalace (int row, int column)
6618 { /* [HGM] for Xiangqi */
6619 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6620 column < (BOARD_WIDTH + 4)/2 &&
6621 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6626 PieceForSquare (int x, int y)
6628 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6631 return boards[currentMove][y][x];
6635 OKToStartUserMove (int x, int y)
6637 ChessSquare from_piece;
6640 if (matchMode) return FALSE;
6641 if (gameMode == EditPosition) return TRUE;
6643 if (x >= 0 && y >= 0)
6644 from_piece = boards[currentMove][y][x];
6646 from_piece = EmptySquare;
6648 if (from_piece == EmptySquare) return FALSE;
6650 white_piece = (int)from_piece >= (int)WhitePawn &&
6651 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6655 case TwoMachinesPlay:
6663 case MachinePlaysWhite:
6664 case IcsPlayingBlack:
6665 if (appData.zippyPlay) return FALSE;
6667 DisplayMoveError(_("You are playing Black"));
6672 case MachinePlaysBlack:
6673 case IcsPlayingWhite:
6674 if (appData.zippyPlay) return FALSE;
6676 DisplayMoveError(_("You are playing White"));
6681 case PlayFromGameFile:
6682 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6684 if (!white_piece && WhiteOnMove(currentMove)) {
6685 DisplayMoveError(_("It is White's turn"));
6688 if (white_piece && !WhiteOnMove(currentMove)) {
6689 DisplayMoveError(_("It is Black's turn"));
6692 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6693 /* Editing correspondence game history */
6694 /* Could disallow this or prompt for confirmation */
6699 case BeginningOfGame:
6700 if (appData.icsActive) return FALSE;
6701 if (!appData.noChessProgram) {
6703 DisplayMoveError(_("You are playing White"));
6710 if (!white_piece && WhiteOnMove(currentMove)) {
6711 DisplayMoveError(_("It is White's turn"));
6714 if (white_piece && !WhiteOnMove(currentMove)) {
6715 DisplayMoveError(_("It is Black's turn"));
6724 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6725 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6726 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6727 && gameMode != AnalyzeFile && gameMode != Training) {
6728 DisplayMoveError(_("Displayed position is not current"));
6735 OnlyMove (int *x, int *y, Boolean captures)
6737 DisambiguateClosure cl;
6738 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6740 case MachinePlaysBlack:
6741 case IcsPlayingWhite:
6742 case BeginningOfGame:
6743 if(!WhiteOnMove(currentMove)) return FALSE;
6745 case MachinePlaysWhite:
6746 case IcsPlayingBlack:
6747 if(WhiteOnMove(currentMove)) return FALSE;
6754 cl.pieceIn = EmptySquare;
6759 cl.promoCharIn = NULLCHAR;
6760 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6761 if( cl.kind == NormalMove ||
6762 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6763 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6764 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6771 if(cl.kind != ImpossibleMove) return FALSE;
6772 cl.pieceIn = EmptySquare;
6777 cl.promoCharIn = NULLCHAR;
6778 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6779 if( cl.kind == NormalMove ||
6780 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6781 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6782 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6787 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6793 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6794 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6795 int lastLoadGameUseList = FALSE;
6796 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6797 ChessMove lastLoadGameStart = EndOfFile;
6801 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6805 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6807 /* Check if the user is playing in turn. This is complicated because we
6808 let the user "pick up" a piece before it is his turn. So the piece he
6809 tried to pick up may have been captured by the time he puts it down!
6810 Therefore we use the color the user is supposed to be playing in this
6811 test, not the color of the piece that is currently on the starting
6812 square---except in EditGame mode, where the user is playing both
6813 sides; fortunately there the capture race can't happen. (It can
6814 now happen in IcsExamining mode, but that's just too bad. The user
6815 will get a somewhat confusing message in that case.)
6820 case TwoMachinesPlay:
6824 /* We switched into a game mode where moves are not accepted,
6825 perhaps while the mouse button was down. */
6828 case MachinePlaysWhite:
6829 /* User is moving for Black */
6830 if (WhiteOnMove(currentMove)) {
6831 DisplayMoveError(_("It is White's turn"));
6836 case MachinePlaysBlack:
6837 /* User is moving for White */
6838 if (!WhiteOnMove(currentMove)) {
6839 DisplayMoveError(_("It is Black's turn"));
6844 case PlayFromGameFile:
6845 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6848 case BeginningOfGame:
6851 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6852 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6853 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6854 /* User is moving for Black */
6855 if (WhiteOnMove(currentMove)) {
6856 DisplayMoveError(_("It is White's turn"));
6860 /* User is moving for White */
6861 if (!WhiteOnMove(currentMove)) {
6862 DisplayMoveError(_("It is Black's turn"));
6868 case IcsPlayingBlack:
6869 /* User is moving for Black */
6870 if (WhiteOnMove(currentMove)) {
6871 if (!appData.premove) {
6872 DisplayMoveError(_("It is White's turn"));
6873 } else if (toX >= 0 && toY >= 0) {
6876 premoveFromX = fromX;
6877 premoveFromY = fromY;
6878 premovePromoChar = promoChar;
6880 if (appData.debugMode)
6881 fprintf(debugFP, "Got premove: fromX %d,"
6882 "fromY %d, toX %d, toY %d\n",
6883 fromX, fromY, toX, toY);
6889 case IcsPlayingWhite:
6890 /* User is moving for White */
6891 if (!WhiteOnMove(currentMove)) {
6892 if (!appData.premove) {
6893 DisplayMoveError(_("It is Black's turn"));
6894 } else if (toX >= 0 && toY >= 0) {
6897 premoveFromX = fromX;
6898 premoveFromY = fromY;
6899 premovePromoChar = promoChar;
6901 if (appData.debugMode)
6902 fprintf(debugFP, "Got premove: fromX %d,"
6903 "fromY %d, toX %d, toY %d\n",
6904 fromX, fromY, toX, toY);
6914 /* EditPosition, empty square, or different color piece;
6915 click-click move is possible */
6916 if (toX == -2 || toY == -2) {
6917 boards[0][fromY][fromX] = EmptySquare;
6918 DrawPosition(FALSE, boards[currentMove]);
6920 } else if (toX >= 0 && toY >= 0) {
6921 boards[0][toY][toX] = boards[0][fromY][fromX];
6922 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6923 if(boards[0][fromY][0] != EmptySquare) {
6924 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6925 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6928 if(fromX == BOARD_RGHT+1) {
6929 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6930 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6931 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6934 boards[0][fromY][fromX] = gatingPiece;
6935 DrawPosition(FALSE, boards[currentMove]);
6941 if(toX < 0 || toY < 0) return;
6942 pup = boards[currentMove][toY][toX];
6944 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6945 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6946 if( pup != EmptySquare ) return;
6947 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6948 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6949 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6950 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6951 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6952 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6953 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6957 /* [HGM] always test for legality, to get promotion info */
6958 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6959 fromY, fromX, toY, toX, promoChar);
6961 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6963 /* [HGM] but possibly ignore an IllegalMove result */
6964 if (appData.testLegality) {
6965 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6966 DisplayMoveError(_("Illegal move"));
6971 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6972 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6973 ClearPremoveHighlights(); // was included
6974 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
6978 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6981 /* Common tail of UserMoveEvent and DropMenuEvent */
6983 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6987 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6988 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6989 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6990 if(WhiteOnMove(currentMove)) {
6991 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6993 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6997 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6998 move type in caller when we know the move is a legal promotion */
6999 if(moveType == NormalMove && promoChar)
7000 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7002 /* [HGM] <popupFix> The following if has been moved here from
7003 UserMoveEvent(). Because it seemed to belong here (why not allow
7004 piece drops in training games?), and because it can only be
7005 performed after it is known to what we promote. */
7006 if (gameMode == Training) {
7007 /* compare the move played on the board to the next move in the
7008 * game. If they match, display the move and the opponent's response.
7009 * If they don't match, display an error message.
7013 CopyBoard(testBoard, boards[currentMove]);
7014 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7016 if (CompareBoards(testBoard, boards[currentMove+1])) {
7017 ForwardInner(currentMove+1);
7019 /* Autoplay the opponent's response.
7020 * if appData.animate was TRUE when Training mode was entered,
7021 * the response will be animated.
7023 saveAnimate = appData.animate;
7024 appData.animate = animateTraining;
7025 ForwardInner(currentMove+1);
7026 appData.animate = saveAnimate;
7028 /* check for the end of the game */
7029 if (currentMove >= forwardMostMove) {
7030 gameMode = PlayFromGameFile;
7032 SetTrainingModeOff();
7033 DisplayInformation(_("End of game"));
7036 DisplayError(_("Incorrect move"), 0);
7041 /* Ok, now we know that the move is good, so we can kill
7042 the previous line in Analysis Mode */
7043 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7044 && currentMove < forwardMostMove) {
7045 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7046 else forwardMostMove = currentMove;
7051 /* If we need the chess program but it's dead, restart it */
7052 ResurrectChessProgram();
7054 /* A user move restarts a paused game*/
7058 thinkOutput[0] = NULLCHAR;
7060 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7062 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7063 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7067 if (gameMode == BeginningOfGame) {
7068 if (appData.noChessProgram) {
7069 gameMode = EditGame;
7073 gameMode = MachinePlaysBlack;
7076 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7078 if (first.sendName) {
7079 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7080 SendToProgram(buf, &first);
7087 /* Relay move to ICS or chess engine */
7088 if (appData.icsActive) {
7089 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7090 gameMode == IcsExamining) {
7091 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7092 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7094 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7096 // also send plain move, in case ICS does not understand atomic claims
7097 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7101 if (first.sendTime && (gameMode == BeginningOfGame ||
7102 gameMode == MachinePlaysWhite ||
7103 gameMode == MachinePlaysBlack)) {
7104 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7106 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7107 // [HGM] book: if program might be playing, let it use book
7108 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7109 first.maybeThinking = TRUE;
7110 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7111 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7112 SendBoard(&first, currentMove+1);
7113 if(second.analyzing) {
7114 if(!second.useSetboard) SendToProgram("undo\n", &second);
7115 SendBoard(&second, currentMove+1);
7118 SendMoveToProgram(forwardMostMove-1, &first);
7119 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7121 if (currentMove == cmailOldMove + 1) {
7122 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7126 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7130 if(appData.testLegality)
7131 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7137 if (WhiteOnMove(currentMove)) {
7138 GameEnds(BlackWins, "Black mates", GE_PLAYER);
7140 GameEnds(WhiteWins, "White mates", GE_PLAYER);
7144 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7149 case MachinePlaysBlack:
7150 case MachinePlaysWhite:
7151 /* disable certain menu options while machine is thinking */
7152 SetMachineThinkingEnables();
7159 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7160 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7162 if(bookHit) { // [HGM] book: simulate book reply
7163 static char bookMove[MSG_SIZ]; // a bit generous?
7165 programStats.nodes = programStats.depth = programStats.time =
7166 programStats.score = programStats.got_only_move = 0;
7167 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7169 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7170 strcat(bookMove, bookHit);
7171 HandleMachineMove(bookMove, &first);
7177 MarkByFEN(char *fen)
7180 if(!appData.markers || !appData.highlightDragging) return;
7181 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7182 r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7186 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7187 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7188 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7189 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7190 if(*fen == 'T') marker[r][f++] = 0; else
7191 if(*fen == 'Y') marker[r][f++] = 1; else
7192 if(*fen == 'G') marker[r][f++] = 3; else
7193 if(*fen == 'B') marker[r][f++] = 4; else
7194 if(*fen == 'C') marker[r][f++] = 5; else
7195 if(*fen == 'M') marker[r][f++] = 6; else
7196 if(*fen == 'W') marker[r][f++] = 7; else
7197 if(*fen == 'D') marker[r][f++] = 8; else
7198 if(*fen == 'R') marker[r][f++] = 2; else {
7199 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7202 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7206 DrawPosition(TRUE, NULL);
7209 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7212 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7214 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7215 Markers *m = (Markers *) closure;
7216 if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7217 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7218 || kind == WhiteCapturesEnPassant
7219 || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7220 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7223 static int hoverSavedValid;
7226 MarkTargetSquares (int clear)
7229 if(clear) { // no reason to ever suppress clearing
7230 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7231 hoverSavedValid = 0;
7232 if(!sum) return; // nothing was cleared,no redraw needed
7235 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7236 !appData.testLegality || gameMode == EditPosition) return;
7237 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7238 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7239 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7241 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7244 DrawPosition(FALSE, NULL);
7248 Explode (Board board, int fromX, int fromY, int toX, int toY)
7250 if(gameInfo.variant == VariantAtomic &&
7251 (board[toY][toX] != EmptySquare || // capture?
7252 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7253 board[fromY][fromX] == BlackPawn )
7255 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7261 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7264 CanPromote (ChessSquare piece, int y)
7266 int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7267 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7268 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7269 if(IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantXiangqi ||
7270 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7271 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7272 gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) return FALSE;
7273 return (piece == BlackPawn && y <= zone ||
7274 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7275 piece == BlackLance && y == 1 ||
7276 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7280 HoverEvent (int xPix, int yPix, int x, int y)
7282 static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7284 if(!first.highlight) return;
7285 if(fromX != oldFromX || fromY != oldFromY) oldX = oldY = -1; // kludge to fake entry on from-click
7286 if(x == oldX && y == oldY) return; // only do something if we enter new square
7287 oldFromX = fromX; oldFromY = fromY;
7288 if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7289 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7290 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7291 hoverSavedValid = 1;
7292 } else if(oldX != x || oldY != y) {
7293 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7294 if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7295 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7296 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7297 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7299 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7300 SendToProgram(buf, &first);
7303 // SetHighlights(fromX, fromY, x, y);
7307 void ReportClick(char *action, int x, int y)
7309 char buf[MSG_SIZ]; // Inform engine of what user does
7311 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7312 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7313 if(!first.highlight || gameMode == EditPosition) return;
7314 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7315 SendToProgram(buf, &first);
7319 LeftClick (ClickType clickType, int xPix, int yPix)
7322 Boolean saveAnimate;
7323 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7324 char promoChoice = NULLCHAR;
7326 static TimeMark lastClickTime, prevClickTime;
7328 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7330 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7332 if (clickType == Press) ErrorPopDown();
7333 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7335 x = EventToSquare(xPix, BOARD_WIDTH);
7336 y = EventToSquare(yPix, BOARD_HEIGHT);
7337 if (!flipView && y >= 0) {
7338 y = BOARD_HEIGHT - 1 - y;
7340 if (flipView && x >= 0) {
7341 x = BOARD_WIDTH - 1 - x;
7344 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7345 defaultPromoChoice = promoSweep;
7346 promoSweep = EmptySquare; // terminate sweep
7347 promoDefaultAltered = TRUE;
7348 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7351 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7352 if(clickType == Release) return; // ignore upclick of click-click destination
7353 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7354 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7355 if(gameInfo.holdingsWidth &&
7356 (WhiteOnMove(currentMove)
7357 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7358 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7359 // click in right holdings, for determining promotion piece
7360 ChessSquare p = boards[currentMove][y][x];
7361 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7362 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7363 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7364 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7369 DrawPosition(FALSE, boards[currentMove]);
7373 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7374 if(clickType == Press
7375 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7376 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7377 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7380 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7381 // could be static click on premove from-square: abort premove
7383 ClearPremoveHighlights();
7386 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7387 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7389 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7390 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7391 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7392 defaultPromoChoice = DefaultPromoChoice(side);
7395 autoQueen = appData.alwaysPromoteToQueen;
7399 gatingPiece = EmptySquare;
7400 if (clickType != Press) {
7401 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7402 DragPieceEnd(xPix, yPix); dragging = 0;
7403 DrawPosition(FALSE, NULL);
7407 doubleClick = FALSE;
7408 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7409 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7411 fromX = x; fromY = y; toX = toY = killX = killY = -1;
7412 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7413 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7414 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7416 if (OKToStartUserMove(fromX, fromY)) {
7418 ReportClick("lift", x, y);
7419 MarkTargetSquares(0);
7420 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7421 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7422 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7423 promoSweep = defaultPromoChoice;
7424 selectFlag = 0; lastX = xPix; lastY = yPix;
7425 Sweep(0); // Pawn that is going to promote: preview promotion piece
7426 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7428 if (appData.highlightDragging) {
7429 SetHighlights(fromX, fromY, -1, -1);
7433 } else fromX = fromY = -1;
7439 if (clickType == Press && gameMode != EditPosition) {
7444 // ignore off-board to clicks
7445 if(y < 0 || x < 0) return;
7447 /* Check if clicking again on the same color piece */
7448 fromP = boards[currentMove][fromY][fromX];
7449 toP = boards[currentMove][y][x];
7450 frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7451 if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7452 ((WhitePawn <= fromP && fromP <= WhiteKing &&
7453 WhitePawn <= toP && toP <= WhiteKing &&
7454 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7455 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7456 (BlackPawn <= fromP && fromP <= BlackKing &&
7457 BlackPawn <= toP && toP <= BlackKing &&
7458 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7459 !(fromP == BlackKing && toP == BlackRook && frc)))) {
7460 /* Clicked again on same color piece -- changed his mind */
7461 second = (x == fromX && y == fromY);
7463 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7464 second = FALSE; // first double-click rather than scond click
7465 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7467 promoDefaultAltered = FALSE;
7468 MarkTargetSquares(1);
7469 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7470 if (appData.highlightDragging) {
7471 SetHighlights(x, y, -1, -1);
7475 if (OKToStartUserMove(x, y)) {
7476 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7477 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7478 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7479 gatingPiece = boards[currentMove][fromY][fromX];
7480 else gatingPiece = doubleClick ? fromP : EmptySquare;
7482 fromY = y; dragging = 1;
7483 ReportClick("lift", x, y);
7484 MarkTargetSquares(0);
7485 DragPieceBegin(xPix, yPix, FALSE);
7486 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7487 promoSweep = defaultPromoChoice;
7488 selectFlag = 0; lastX = xPix; lastY = yPix;
7489 Sweep(0); // Pawn that is going to promote: preview promotion piece
7493 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7496 // ignore clicks on holdings
7497 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7500 if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7501 DragPieceEnd(xPix, yPix); dragging = 0;
7503 // a deferred attempt to click-click move an empty square on top of a piece
7504 boards[currentMove][y][x] = EmptySquare;
7506 DrawPosition(FALSE, boards[currentMove]);
7507 fromX = fromY = -1; clearFlag = 0;
7510 if (appData.animateDragging) {
7511 /* Undo animation damage if any */
7512 DrawPosition(FALSE, NULL);
7514 if (second || sweepSelecting) {
7515 /* Second up/down in same square; just abort move */
7516 if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7517 second = sweepSelecting = 0;
7519 gatingPiece = EmptySquare;
7520 MarkTargetSquares(1);
7523 ClearPremoveHighlights();
7525 /* First upclick in same square; start click-click mode */
7526 SetHighlights(x, y, -1, -1);
7533 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7534 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7535 DisplayMessage(_("only marked squares are legal"),"");
7536 DrawPosition(TRUE, NULL);
7537 return; // ignore to-click
7540 /* we now have a different from- and (possibly off-board) to-square */
7541 /* Completed move */
7542 if(!sweepSelecting) {
7547 piece = boards[currentMove][fromY][fromX];
7549 saveAnimate = appData.animate;
7550 if (clickType == Press) {
7551 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7552 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7553 // must be Edit Position mode with empty-square selected
7554 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7555 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7558 if(dragging == 2) { // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7561 if(x == killX && y == killY) { // second click on this square, which was selected as first-leg target
7562 killX = killY = -1; // this informs us no second leg is coming, so treat as to-click without intermediate
7564 if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7565 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7566 if(appData.sweepSelect) {
7567 promoSweep = defaultPromoChoice;
7568 if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7569 selectFlag = 0; lastX = xPix; lastY = yPix;
7570 Sweep(0); // Pawn that is going to promote: preview promotion piece
7572 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7573 MarkTargetSquares(1);
7575 return; // promo popup appears on up-click
7577 /* Finish clickclick move */
7578 if (appData.animate || appData.highlightLastMove) {
7579 SetHighlights(fromX, fromY, toX, toY);
7583 } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7584 sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7585 if (appData.animate || appData.highlightLastMove) {
7586 SetHighlights(fromX, fromY, toX, toY);
7592 // [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
7593 /* Finish drag move */
7594 if (appData.highlightLastMove) {
7595 SetHighlights(fromX, fromY, toX, toY);
7600 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7601 if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7602 dragging *= 2; // flag button-less dragging if we are dragging
7603 MarkTargetSquares(1);
7604 if(x == killX && y == killY) killX = killY = -1; else {
7605 killX = x; killY = y; //remeber this square as intermediate
7606 ReportClick("put", x, y); // and inform engine
7607 ReportClick("lift", x, y);
7608 MarkTargetSquares(0);
7612 DragPieceEnd(xPix, yPix); dragging = 0;
7613 /* Don't animate move and drag both */
7614 appData.animate = FALSE;
7617 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7618 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7619 ChessSquare piece = boards[currentMove][fromY][fromX];
7620 if(gameMode == EditPosition && piece != EmptySquare &&
7621 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7624 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7625 n = PieceToNumber(piece - (int)BlackPawn);
7626 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7627 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7628 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7630 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7631 n = PieceToNumber(piece);
7632 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7633 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7634 boards[currentMove][n][BOARD_WIDTH-2]++;
7636 boards[currentMove][fromY][fromX] = EmptySquare;
7640 MarkTargetSquares(1);
7641 DrawPosition(TRUE, boards[currentMove]);
7645 // off-board moves should not be highlighted
7646 if(x < 0 || y < 0) ClearHighlights();
7647 else ReportClick("put", x, y);
7649 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7651 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7652 SetHighlights(fromX, fromY, toX, toY);
7653 MarkTargetSquares(1);
7654 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7655 // [HGM] super: promotion to captured piece selected from holdings
7656 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7657 promotionChoice = TRUE;
7658 // kludge follows to temporarily execute move on display, without promoting yet
7659 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7660 boards[currentMove][toY][toX] = p;
7661 DrawPosition(FALSE, boards[currentMove]);
7662 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7663 boards[currentMove][toY][toX] = q;
7664 DisplayMessage("Click in holdings to choose piece", "");
7667 PromotionPopUp(promoChoice);
7669 int oldMove = currentMove;
7670 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7671 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7672 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7673 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7674 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7675 DrawPosition(TRUE, boards[currentMove]);
7676 MarkTargetSquares(1);
7679 appData.animate = saveAnimate;
7680 if (appData.animate || appData.animateDragging) {
7681 /* Undo animation damage if needed */
7682 DrawPosition(FALSE, NULL);
7687 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7688 { // front-end-free part taken out of PieceMenuPopup
7689 int whichMenu; int xSqr, ySqr;
7691 if(seekGraphUp) { // [HGM] seekgraph
7692 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7693 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7697 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7698 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7699 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7700 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7701 if(action == Press) {
7702 originalFlip = flipView;
7703 flipView = !flipView; // temporarily flip board to see game from partners perspective
7704 DrawPosition(TRUE, partnerBoard);
7705 DisplayMessage(partnerStatus, "");
7707 } else if(action == Release) {
7708 flipView = originalFlip;
7709 DrawPosition(TRUE, boards[currentMove]);
7715 xSqr = EventToSquare(x, BOARD_WIDTH);
7716 ySqr = EventToSquare(y, BOARD_HEIGHT);
7717 if (action == Release) {
7718 if(pieceSweep != EmptySquare) {
7719 EditPositionMenuEvent(pieceSweep, toX, toY);
7720 pieceSweep = EmptySquare;
7721 } else UnLoadPV(); // [HGM] pv
7723 if (action != Press) return -2; // return code to be ignored
7726 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7728 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7729 if (xSqr < 0 || ySqr < 0) return -1;
7730 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7731 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7732 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7733 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7737 if(!appData.icsEngineAnalyze) return -1;
7738 case IcsPlayingWhite:
7739 case IcsPlayingBlack:
7740 if(!appData.zippyPlay) goto noZip;
7743 case MachinePlaysWhite:
7744 case MachinePlaysBlack:
7745 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7746 if (!appData.dropMenu) {
7748 return 2; // flag front-end to grab mouse events
7750 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7751 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7754 if (xSqr < 0 || ySqr < 0) return -1;
7755 if (!appData.dropMenu || appData.testLegality &&
7756 gameInfo.variant != VariantBughouse &&
7757 gameInfo.variant != VariantCrazyhouse) return -1;
7758 whichMenu = 1; // drop menu
7764 if (((*fromX = xSqr) < 0) ||
7765 ((*fromY = ySqr) < 0)) {
7766 *fromX = *fromY = -1;
7770 *fromX = BOARD_WIDTH - 1 - *fromX;
7772 *fromY = BOARD_HEIGHT - 1 - *fromY;
7778 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7780 // char * hint = lastHint;
7781 FrontEndProgramStats stats;
7783 stats.which = cps == &first ? 0 : 1;
7784 stats.depth = cpstats->depth;
7785 stats.nodes = cpstats->nodes;
7786 stats.score = cpstats->score;
7787 stats.time = cpstats->time;
7788 stats.pv = cpstats->movelist;
7789 stats.hint = lastHint;
7790 stats.an_move_index = 0;
7791 stats.an_move_count = 0;
7793 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7794 stats.hint = cpstats->move_name;
7795 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7796 stats.an_move_count = cpstats->nr_moves;
7799 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
7801 SetProgramStats( &stats );
7805 ClearEngineOutputPane (int which)
7807 static FrontEndProgramStats dummyStats;
7808 dummyStats.which = which;
7809 dummyStats.pv = "#";
7810 SetProgramStats( &dummyStats );
7813 #define MAXPLAYERS 500
7816 TourneyStandings (int display)
7818 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7819 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7820 char result, *p, *names[MAXPLAYERS];
7822 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7823 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7824 names[0] = p = strdup(appData.participants);
7825 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7827 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7829 while(result = appData.results[nr]) {
7830 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7831 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7832 wScore = bScore = 0;
7834 case '+': wScore = 2; break;
7835 case '-': bScore = 2; break;
7836 case '=': wScore = bScore = 1; break;
7838 case '*': return strdup("busy"); // tourney not finished
7846 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7847 for(w=0; w<nPlayers; w++) {
7849 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7850 ranking[w] = b; points[w] = bScore; score[b] = -2;
7852 p = malloc(nPlayers*34+1);
7853 for(w=0; w<nPlayers && w<display; w++)
7854 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7860 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7861 { // count all piece types
7863 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7864 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7865 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7868 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7869 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7870 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7871 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7872 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7873 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7878 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7880 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7881 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7883 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7884 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7885 if(myPawns == 2 && nMine == 3) // KPP
7886 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7887 if(myPawns == 1 && nMine == 2) // KP
7888 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7889 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7890 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7891 if(myPawns) return FALSE;
7892 if(pCnt[WhiteRook+side])
7893 return pCnt[BlackRook-side] ||
7894 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7895 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7896 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7897 if(pCnt[WhiteCannon+side]) {
7898 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7899 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7901 if(pCnt[WhiteKnight+side])
7902 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7907 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7909 VariantClass v = gameInfo.variant;
7911 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7912 if(v == VariantShatranj) return TRUE; // always winnable through baring
7913 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7914 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7916 if(v == VariantXiangqi) {
7917 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7919 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7920 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7921 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7922 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7923 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7924 if(stale) // we have at least one last-rank P plus perhaps C
7925 return majors // KPKX
7926 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7928 return pCnt[WhiteFerz+side] // KCAK
7929 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7930 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7931 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7933 } else if(v == VariantKnightmate) {
7934 if(nMine == 1) return FALSE;
7935 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7936 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7937 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7939 if(nMine == 1) return FALSE; // bare King
7940 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
7941 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7942 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7943 // by now we have King + 1 piece (or multiple Bishops on the same color)
7944 if(pCnt[WhiteKnight+side])
7945 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7946 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7947 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7949 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7950 if(pCnt[WhiteAlfil+side])
7951 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7952 if(pCnt[WhiteWazir+side])
7953 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7960 CompareWithRights (Board b1, Board b2)
7963 if(!CompareBoards(b1, b2)) return FALSE;
7964 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7965 /* compare castling rights */
7966 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7967 rights++; /* King lost rights, while rook still had them */
7968 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7969 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7970 rights++; /* but at least one rook lost them */
7972 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7974 if( b1[CASTLING][5] != NoRights ) {
7975 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7982 Adjudicate (ChessProgramState *cps)
7983 { // [HGM] some adjudications useful with buggy engines
7984 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7985 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7986 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7987 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7988 int k, drop, count = 0; static int bare = 1;
7989 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7990 Boolean canAdjudicate = !appData.icsActive;
7992 // most tests only when we understand the game, i.e. legality-checking on
7993 if( appData.testLegality )
7994 { /* [HGM] Some more adjudications for obstinate engines */
7995 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7996 static int moveCount = 6;
7998 char *reason = NULL;
8000 /* Count what is on board. */
8001 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8003 /* Some material-based adjudications that have to be made before stalemate test */
8004 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8005 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8006 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8007 if(canAdjudicate && appData.checkMates) {
8009 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8010 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8011 "Xboard adjudication: King destroyed", GE_XBOARD );
8016 /* Bare King in Shatranj (loses) or Losers (wins) */
8017 if( nrW == 1 || nrB == 1) {
8018 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8019 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
8020 if(canAdjudicate && appData.checkMates) {
8022 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8023 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8024 "Xboard adjudication: Bare king", GE_XBOARD );
8028 if( gameInfo.variant == VariantShatranj && --bare < 0)
8030 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8031 if(canAdjudicate && appData.checkMates) {
8032 /* but only adjudicate if adjudication enabled */
8034 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8035 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8036 "Xboard adjudication: Bare king", GE_XBOARD );
8043 // don't wait for engine to announce game end if we can judge ourselves
8044 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8046 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8047 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
8048 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8049 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8052 reason = "Xboard adjudication: 3rd check";
8053 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8064 reason = "Xboard adjudication: Stalemate";
8065 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8066 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
8067 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8068 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
8069 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8070 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8071 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8072 EP_CHECKMATE : EP_WINS);
8073 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8074 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8078 reason = "Xboard adjudication: Checkmate";
8079 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8080 if(gameInfo.variant == VariantShogi) {
8081 if(forwardMostMove > backwardMostMove
8082 && moveList[forwardMostMove-1][1] == '@'
8083 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8084 reason = "XBoard adjudication: pawn-drop mate";
8085 boards[forwardMostMove][EP_STATUS] = EP_WINS;
8091 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8093 result = GameIsDrawn; break;
8095 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8097 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8101 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8103 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8104 GameEnds( result, reason, GE_XBOARD );
8108 /* Next absolutely insufficient mating material. */
8109 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8110 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8111 { /* includes KBK, KNK, KK of KBKB with like Bishops */
8113 /* always flag draws, for judging claims */
8114 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8116 if(canAdjudicate && appData.materialDraws) {
8117 /* but only adjudicate them if adjudication enabled */
8118 if(engineOpponent) {
8119 SendToProgram("force\n", engineOpponent); // suppress reply
8120 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8122 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8127 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8128 if(gameInfo.variant == VariantXiangqi ?
8129 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8131 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8132 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
8133 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
8134 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8136 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8137 { /* if the first 3 moves do not show a tactical win, declare draw */
8138 if(engineOpponent) {
8139 SendToProgram("force\n", engineOpponent); // suppress reply
8140 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8142 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8145 } else moveCount = 6;
8148 // Repetition draws and 50-move rule can be applied independently of legality testing
8150 /* Check for rep-draws */
8152 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8153 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8154 for(k = forwardMostMove-2;
8155 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8156 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8157 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8160 if(CompareBoards(boards[k], boards[forwardMostMove])) {
8161 /* compare castling rights */
8162 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8163 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8164 rights++; /* King lost rights, while rook still had them */
8165 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8166 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8167 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8168 rights++; /* but at least one rook lost them */
8170 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8171 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8173 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8174 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8175 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8178 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8179 && appData.drawRepeats > 1) {
8180 /* adjudicate after user-specified nr of repeats */
8181 int result = GameIsDrawn;
8182 char *details = "XBoard adjudication: repetition draw";
8183 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8184 // [HGM] xiangqi: check for forbidden perpetuals
8185 int m, ourPerpetual = 1, hisPerpetual = 1;
8186 for(m=forwardMostMove; m>k; m-=2) {
8187 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8188 ourPerpetual = 0; // the current mover did not always check
8189 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8190 hisPerpetual = 0; // the opponent did not always check
8192 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8193 ourPerpetual, hisPerpetual);
8194 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8195 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8196 details = "Xboard adjudication: perpetual checking";
8198 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8199 break; // (or we would have caught him before). Abort repetition-checking loop.
8201 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8202 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8204 details = "Xboard adjudication: repetition";
8206 } else // it must be XQ
8207 // Now check for perpetual chases
8208 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8209 hisPerpetual = PerpetualChase(k, forwardMostMove);
8210 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8211 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8212 static char resdet[MSG_SIZ];
8213 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8215 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8217 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8218 break; // Abort repetition-checking loop.
8220 // if neither of us is checking or chasing all the time, or both are, it is draw
8222 if(engineOpponent) {
8223 SendToProgram("force\n", engineOpponent); // suppress reply
8224 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8226 GameEnds( result, details, GE_XBOARD );
8229 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8230 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8234 /* Now we test for 50-move draws. Determine ply count */
8235 count = forwardMostMove;
8236 /* look for last irreversble move */
8237 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8239 /* if we hit starting position, add initial plies */
8240 if( count == backwardMostMove )
8241 count -= initialRulePlies;
8242 count = forwardMostMove - count;
8243 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8244 // adjust reversible move counter for checks in Xiangqi
8245 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8246 if(i < backwardMostMove) i = backwardMostMove;
8247 while(i <= forwardMostMove) {
8248 lastCheck = inCheck; // check evasion does not count
8249 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8250 if(inCheck || lastCheck) count--; // check does not count
8255 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8256 /* this is used to judge if draw claims are legal */
8257 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8258 if(engineOpponent) {
8259 SendToProgram("force\n", engineOpponent); // suppress reply
8260 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8262 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8266 /* if draw offer is pending, treat it as a draw claim
8267 * when draw condition present, to allow engines a way to
8268 * claim draws before making their move to avoid a race
8269 * condition occurring after their move
8271 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8273 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8274 p = "Draw claim: 50-move rule";
8275 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8276 p = "Draw claim: 3-fold repetition";
8277 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8278 p = "Draw claim: insufficient mating material";
8279 if( p != NULL && canAdjudicate) {
8280 if(engineOpponent) {
8281 SendToProgram("force\n", engineOpponent); // suppress reply
8282 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8284 GameEnds( GameIsDrawn, p, GE_XBOARD );
8289 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8290 if(engineOpponent) {
8291 SendToProgram("force\n", engineOpponent); // suppress reply
8292 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8294 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8300 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8301 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8302 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8307 int pieces[10], squares[10], cnt=0, r, f, res;
8309 static PPROBE_EGBB probeBB;
8310 if(!appData.testLegality) return 10;
8311 if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8312 if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8313 if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8314 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8315 ChessSquare piece = boards[forwardMostMove][r][f];
8316 int black = (piece >= BlackPawn);
8317 int type = piece - black*BlackPawn;
8318 if(piece == EmptySquare) continue;
8319 if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8320 if(type == WhiteKing) type = WhiteQueen + 1;
8321 type = egbbCode[type];
8322 squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8323 pieces[cnt] = type + black*6;
8324 if(++cnt > 5) return 11;
8326 pieces[cnt] = squares[cnt] = 0;
8328 if(loaded == 2) return 13; // loading failed before
8330 loaded = 2; // prepare for failure
8331 char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8334 if(!path) return 13; // no egbb installed
8335 strncpy(buf, path + 8, MSG_SIZ);
8336 if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8337 snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8338 lib = LoadLibrary(buf);
8339 if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8340 loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8341 probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8342 if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8343 p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8344 loaded = 1; // success!
8346 res = probeBB(forwardMostMove & 1, pieces, squares);
8347 return res > 0 ? 1 : res < 0 ? -1 : 0;
8351 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8352 { // [HGM] book: this routine intercepts moves to simulate book replies
8353 char *bookHit = NULL;
8355 if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8357 snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8358 SendToProgram(buf, cps);
8360 //first determine if the incoming move brings opponent into his book
8361 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8362 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8363 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8364 if(bookHit != NULL && !cps->bookSuspend) {
8365 // make sure opponent is not going to reply after receiving move to book position
8366 SendToProgram("force\n", cps);
8367 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8369 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8370 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8371 // now arrange restart after book miss
8373 // after a book hit we never send 'go', and the code after the call to this routine
8374 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8375 char buf[MSG_SIZ], *move = bookHit;
8377 int fromX, fromY, toX, toY;
8381 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8382 &fromX, &fromY, &toX, &toY, &promoChar)) {
8383 (void) CoordsToAlgebraic(boards[forwardMostMove],
8384 PosFlags(forwardMostMove),
8385 fromY, fromX, toY, toX, promoChar, move);
8387 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8391 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8392 SendToProgram(buf, cps);
8393 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8394 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8395 SendToProgram("go\n", cps);
8396 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8397 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8398 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8399 SendToProgram("go\n", cps);
8400 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8402 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8406 LoadError (char *errmess, ChessProgramState *cps)
8407 { // unloads engine and switches back to -ncp mode if it was first
8408 if(cps->initDone) return FALSE;
8409 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8410 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8413 appData.noChessProgram = TRUE;
8414 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8415 gameMode = BeginningOfGame; ModeHighlight();
8418 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8419 DisplayMessage("", ""); // erase waiting message
8420 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8425 ChessProgramState *savedState;
8427 DeferredBookMove (void)
8429 if(savedState->lastPing != savedState->lastPong)
8430 ScheduleDelayedEvent(DeferredBookMove, 10);
8432 HandleMachineMove(savedMessage, savedState);
8435 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8436 static ChessProgramState *stalledEngine;
8437 static char stashedInputMove[MSG_SIZ];
8440 HandleMachineMove (char *message, ChessProgramState *cps)
8442 static char firstLeg[20];
8443 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8444 char realname[MSG_SIZ];
8445 int fromX, fromY, toX, toY;
8447 char promoChar, roar;
8449 int machineWhite, oldError;
8452 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8453 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8454 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8455 DisplayError(_("Invalid pairing from pairing engine"), 0);
8458 pairingReceived = 1;
8460 return; // Skim the pairing messages here.
8463 oldError = cps->userError; cps->userError = 0;
8465 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8467 * Kludge to ignore BEL characters
8469 while (*message == '\007') message++;
8472 * [HGM] engine debug message: ignore lines starting with '#' character
8474 if(cps->debug && *message == '#') return;
8477 * Look for book output
8479 if (cps == &first && bookRequested) {
8480 if (message[0] == '\t' || message[0] == ' ') {
8481 /* Part of the book output is here; append it */
8482 strcat(bookOutput, message);
8483 strcat(bookOutput, " \n");
8485 } else if (bookOutput[0] != NULLCHAR) {
8486 /* All of book output has arrived; display it */
8487 char *p = bookOutput;
8488 while (*p != NULLCHAR) {
8489 if (*p == '\t') *p = ' ';
8492 DisplayInformation(bookOutput);
8493 bookRequested = FALSE;
8494 /* Fall through to parse the current output */
8499 * Look for machine move.
8501 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8502 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8504 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8505 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8506 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8507 stalledEngine = cps;
8508 if(appData.ponderNextMove) { // bring opponent out of ponder
8509 if(gameMode == TwoMachinesPlay) {
8510 if(cps->other->pause)
8511 PauseEngine(cps->other);
8513 SendToProgram("easy\n", cps->other);
8520 /* This method is only useful on engines that support ping */
8521 if (cps->lastPing != cps->lastPong) {
8522 if (gameMode == BeginningOfGame) {
8523 /* Extra move from before last new; ignore */
8524 if (appData.debugMode) {
8525 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8528 if (appData.debugMode) {
8529 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8530 cps->which, gameMode);
8533 SendToProgram("undo\n", cps);
8539 case BeginningOfGame:
8540 /* Extra move from before last reset; ignore */
8541 if (appData.debugMode) {
8542 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8549 /* Extra move after we tried to stop. The mode test is
8550 not a reliable way of detecting this problem, but it's
8551 the best we can do on engines that don't support ping.
8553 if (appData.debugMode) {
8554 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8555 cps->which, gameMode);
8557 SendToProgram("undo\n", cps);
8560 case MachinePlaysWhite:
8561 case IcsPlayingWhite:
8562 machineWhite = TRUE;
8565 case MachinePlaysBlack:
8566 case IcsPlayingBlack:
8567 machineWhite = FALSE;
8570 case TwoMachinesPlay:
8571 machineWhite = (cps->twoMachinesColor[0] == 'w');
8574 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8575 if (appData.debugMode) {
8577 "Ignoring move out of turn by %s, gameMode %d"
8578 ", forwardMost %d\n",
8579 cps->which, gameMode, forwardMostMove);
8584 if(cps->alphaRank) AlphaRank(machineMove, 4);
8586 // [HGM] lion: (some very limited) support for Alien protocol
8588 if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8589 safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8591 } else if(firstLeg[0]) { // there was a previous leg;
8592 // only support case where same piece makes two step (and don't even test that!)
8593 char buf[20], *p = machineMove+1, *q = buf+1, f;
8594 safeStrCpy(buf, machineMove, 20);
8595 while(isdigit(*q)) q++; // find start of to-square
8596 safeStrCpy(machineMove, firstLeg, 20);
8597 while(isdigit(*p)) p++;
8598 safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8599 sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8600 firstLeg[0] = NULLCHAR;
8603 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8604 &fromX, &fromY, &toX, &toY, &promoChar)) {
8605 /* Machine move could not be parsed; ignore it. */
8606 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8607 machineMove, _(cps->which));
8608 DisplayMoveError(buf1);
8609 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8610 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8611 if (gameMode == TwoMachinesPlay) {
8612 GameEnds(machineWhite ? BlackWins : WhiteWins,
8618 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8619 /* So we have to redo legality test with true e.p. status here, */
8620 /* to make sure an illegal e.p. capture does not slip through, */
8621 /* to cause a forfeit on a justified illegal-move complaint */
8622 /* of the opponent. */
8623 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8625 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8626 fromY, fromX, toY, toX, promoChar);
8627 if(moveType == IllegalMove) {
8628 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8629 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8630 GameEnds(machineWhite ? BlackWins : WhiteWins,
8633 } else if(!appData.fischerCastling)
8634 /* [HGM] Kludge to handle engines that send FRC-style castling
8635 when they shouldn't (like TSCP-Gothic) */
8637 case WhiteASideCastleFR:
8638 case BlackASideCastleFR:
8640 currentMoveString[2]++;
8642 case WhiteHSideCastleFR:
8643 case BlackHSideCastleFR:
8645 currentMoveString[2]--;
8647 default: ; // nothing to do, but suppresses warning of pedantic compilers
8650 hintRequested = FALSE;
8651 lastHint[0] = NULLCHAR;
8652 bookRequested = FALSE;
8653 /* Program may be pondering now */
8654 cps->maybeThinking = TRUE;
8655 if (cps->sendTime == 2) cps->sendTime = 1;
8656 if (cps->offeredDraw) cps->offeredDraw--;
8658 /* [AS] Save move info*/
8659 pvInfoList[ forwardMostMove ].score = programStats.score;
8660 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8661 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8663 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8665 /* Test suites abort the 'game' after one move */
8666 if(*appData.finger) {
8668 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8669 if(!f) f = fopen(appData.finger, "w");
8670 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8671 else { DisplayFatalError("Bad output file", errno, 0); return; }
8673 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8676 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8677 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8680 while( count < adjudicateLossPlies ) {
8681 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8684 score = -score; /* Flip score for winning side */
8687 if( score > adjudicateLossThreshold ) {
8694 if( count >= adjudicateLossPlies ) {
8695 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8697 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8698 "Xboard adjudication",
8705 if(Adjudicate(cps)) {
8706 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8707 return; // [HGM] adjudicate: for all automatic game ends
8711 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8713 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8714 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8716 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8718 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8720 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8721 char buf[3*MSG_SIZ];
8723 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8724 programStats.score / 100.,
8726 programStats.time / 100.,
8727 (unsigned int)programStats.nodes,
8728 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8729 programStats.movelist);
8731 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8736 /* [AS] Clear stats for next move */
8737 ClearProgramStats();
8738 thinkOutput[0] = NULLCHAR;
8739 hiddenThinkOutputState = 0;
8742 if (gameMode == TwoMachinesPlay) {
8743 /* [HGM] relaying draw offers moved to after reception of move */
8744 /* and interpreting offer as claim if it brings draw condition */
8745 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8746 SendToProgram("draw\n", cps->other);
8748 if (cps->other->sendTime) {
8749 SendTimeRemaining(cps->other,
8750 cps->other->twoMachinesColor[0] == 'w');
8752 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8753 if (firstMove && !bookHit) {
8755 if (cps->other->useColors) {
8756 SendToProgram(cps->other->twoMachinesColor, cps->other);
8758 SendToProgram("go\n", cps->other);
8760 cps->other->maybeThinking = TRUE;
8763 roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8765 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8767 if (!pausing && appData.ringBellAfterMoves) {
8768 if(!roar) RingBell();
8772 * Reenable menu items that were disabled while
8773 * machine was thinking
8775 if (gameMode != TwoMachinesPlay)
8776 SetUserThinkingEnables();
8778 // [HGM] book: after book hit opponent has received move and is now in force mode
8779 // force the book reply into it, and then fake that it outputted this move by jumping
8780 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8782 static char bookMove[MSG_SIZ]; // a bit generous?
8784 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8785 strcat(bookMove, bookHit);
8788 programStats.nodes = programStats.depth = programStats.time =
8789 programStats.score = programStats.got_only_move = 0;
8790 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8792 if(cps->lastPing != cps->lastPong) {
8793 savedMessage = message; // args for deferred call
8795 ScheduleDelayedEvent(DeferredBookMove, 10);
8804 /* Set special modes for chess engines. Later something general
8805 * could be added here; for now there is just one kludge feature,
8806 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8807 * when "xboard" is given as an interactive command.
8809 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8810 cps->useSigint = FALSE;
8811 cps->useSigterm = FALSE;
8813 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8814 ParseFeatures(message+8, cps);
8815 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8818 if (!strncmp(message, "setup ", 6) &&
8819 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8820 NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8821 ) { // [HGM] allow first engine to define opening position
8822 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8823 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8825 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8826 if(startedFromSetupPosition) return;
8827 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8829 while(message[s] && message[s++] != ' ');
8830 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8831 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8832 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8833 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
8834 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8835 if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8838 ParseFEN(boards[0], &dummy, message+s, FALSE);
8839 DrawPosition(TRUE, boards[0]);
8840 startedFromSetupPosition = TRUE;
8843 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8844 * want this, I was asked to put it in, and obliged.
8846 if (!strncmp(message, "setboard ", 9)) {
8847 Board initial_position;
8849 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8851 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8852 DisplayError(_("Bad FEN received from engine"), 0);
8856 CopyBoard(boards[0], initial_position);
8857 initialRulePlies = FENrulePlies;
8858 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8859 else gameMode = MachinePlaysBlack;
8860 DrawPosition(FALSE, boards[currentMove]);
8866 * Look for communication commands
8868 if (!strncmp(message, "telluser ", 9)) {
8869 if(message[9] == '\\' && message[10] == '\\')
8870 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8872 DisplayNote(message + 9);
8875 if (!strncmp(message, "tellusererror ", 14)) {
8877 if(message[14] == '\\' && message[15] == '\\')
8878 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8880 DisplayError(message + 14, 0);
8883 if (!strncmp(message, "tellopponent ", 13)) {
8884 if (appData.icsActive) {
8886 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8890 DisplayNote(message + 13);
8894 if (!strncmp(message, "tellothers ", 11)) {
8895 if (appData.icsActive) {
8897 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8900 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8903 if (!strncmp(message, "tellall ", 8)) {
8904 if (appData.icsActive) {
8906 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8910 DisplayNote(message + 8);
8914 if (strncmp(message, "warning", 7) == 0) {
8915 /* Undocumented feature, use tellusererror in new code */
8916 DisplayError(message, 0);
8919 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8920 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8921 strcat(realname, " query");
8922 AskQuestion(realname, buf2, buf1, cps->pr);
8925 /* Commands from the engine directly to ICS. We don't allow these to be
8926 * sent until we are logged on. Crafty kibitzes have been known to
8927 * interfere with the login process.
8930 if (!strncmp(message, "tellics ", 8)) {
8931 SendToICS(message + 8);
8935 if (!strncmp(message, "tellicsnoalias ", 15)) {
8936 SendToICS(ics_prefix);
8937 SendToICS(message + 15);
8941 /* The following are for backward compatibility only */
8942 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8943 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8944 SendToICS(ics_prefix);
8950 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8951 if(initPing == cps->lastPong) {
8952 if(gameInfo.variant == VariantUnknown) {
8953 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8954 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8955 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8961 if(!strncmp(message, "highlight ", 10)) {
8962 if(appData.testLegality && appData.markers) return;
8963 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8966 if(!strncmp(message, "click ", 6)) {
8967 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8968 if(appData.testLegality || !appData.oneClick) return;
8969 sscanf(message+6, "%c%d%c", &f, &y, &c);
8970 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8971 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8972 x = x*squareSize + (x+1)*lineGap + squareSize/2;
8973 y = y*squareSize + (y+1)*lineGap + squareSize/2;
8974 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8975 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8976 LeftClick(Release, lastLeftX, lastLeftY);
8977 controlKey = (c == ',');
8978 LeftClick(Press, x, y);
8979 LeftClick(Release, x, y);
8980 first.highlight = f;
8984 * If the move is illegal, cancel it and redraw the board.
8985 * Also deal with other error cases. Matching is rather loose
8986 * here to accommodate engines written before the spec.
8988 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8989 strncmp(message, "Error", 5) == 0) {
8990 if (StrStr(message, "name") ||
8991 StrStr(message, "rating") || StrStr(message, "?") ||
8992 StrStr(message, "result") || StrStr(message, "board") ||
8993 StrStr(message, "bk") || StrStr(message, "computer") ||
8994 StrStr(message, "variant") || StrStr(message, "hint") ||
8995 StrStr(message, "random") || StrStr(message, "depth") ||
8996 StrStr(message, "accepted")) {
8999 if (StrStr(message, "protover")) {
9000 /* Program is responding to input, so it's apparently done
9001 initializing, and this error message indicates it is
9002 protocol version 1. So we don't need to wait any longer
9003 for it to initialize and send feature commands. */
9004 FeatureDone(cps, 1);
9005 cps->protocolVersion = 1;
9008 cps->maybeThinking = FALSE;
9010 if (StrStr(message, "draw")) {
9011 /* Program doesn't have "draw" command */
9012 cps->sendDrawOffers = 0;
9015 if (cps->sendTime != 1 &&
9016 (StrStr(message, "time") || StrStr(message, "otim"))) {
9017 /* Program apparently doesn't have "time" or "otim" command */
9021 if (StrStr(message, "analyze")) {
9022 cps->analysisSupport = FALSE;
9023 cps->analyzing = FALSE;
9024 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9025 EditGameEvent(); // [HGM] try to preserve loaded game
9026 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9027 DisplayError(buf2, 0);
9030 if (StrStr(message, "(no matching move)st")) {
9031 /* Special kludge for GNU Chess 4 only */
9032 cps->stKludge = TRUE;
9033 SendTimeControl(cps, movesPerSession, timeControl,
9034 timeIncrement, appData.searchDepth,
9038 if (StrStr(message, "(no matching move)sd")) {
9039 /* Special kludge for GNU Chess 4 only */
9040 cps->sdKludge = TRUE;
9041 SendTimeControl(cps, movesPerSession, timeControl,
9042 timeIncrement, appData.searchDepth,
9046 if (!StrStr(message, "llegal")) {
9049 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9050 gameMode == IcsIdle) return;
9051 if (forwardMostMove <= backwardMostMove) return;
9052 if (pausing) PauseEvent();
9053 if(appData.forceIllegal) {
9054 // [HGM] illegal: machine refused move; force position after move into it
9055 SendToProgram("force\n", cps);
9056 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9057 // we have a real problem now, as SendBoard will use the a2a3 kludge
9058 // when black is to move, while there might be nothing on a2 or black
9059 // might already have the move. So send the board as if white has the move.
9060 // But first we must change the stm of the engine, as it refused the last move
9061 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9062 if(WhiteOnMove(forwardMostMove)) {
9063 SendToProgram("a7a6\n", cps); // for the engine black still had the move
9064 SendBoard(cps, forwardMostMove); // kludgeless board
9066 SendToProgram("a2a3\n", cps); // for the engine white still had the move
9067 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9068 SendBoard(cps, forwardMostMove+1); // kludgeless board
9070 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9071 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9072 gameMode == TwoMachinesPlay)
9073 SendToProgram("go\n", cps);
9076 if (gameMode == PlayFromGameFile) {
9077 /* Stop reading this game file */
9078 gameMode = EditGame;
9081 /* [HGM] illegal-move claim should forfeit game when Xboard */
9082 /* only passes fully legal moves */
9083 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9084 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9085 "False illegal-move claim", GE_XBOARD );
9086 return; // do not take back move we tested as valid
9088 currentMove = forwardMostMove-1;
9089 DisplayMove(currentMove-1); /* before DisplayMoveError */
9090 SwitchClocks(forwardMostMove-1); // [HGM] race
9091 DisplayBothClocks();
9092 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9093 parseList[currentMove], _(cps->which));
9094 DisplayMoveError(buf1);
9095 DrawPosition(FALSE, boards[currentMove]);
9097 SetUserThinkingEnables();
9100 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9101 /* Program has a broken "time" command that
9102 outputs a string not ending in newline.
9108 * If chess program startup fails, exit with an error message.
9109 * Attempts to recover here are futile. [HGM] Well, we try anyway
9111 if ((StrStr(message, "unknown host") != NULL)
9112 || (StrStr(message, "No remote directory") != NULL)
9113 || (StrStr(message, "not found") != NULL)
9114 || (StrStr(message, "No such file") != NULL)
9115 || (StrStr(message, "can't alloc") != NULL)
9116 || (StrStr(message, "Permission denied") != NULL)) {
9118 cps->maybeThinking = FALSE;
9119 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9120 _(cps->which), cps->program, cps->host, message);
9121 RemoveInputSource(cps->isr);
9122 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9123 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9124 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9130 * Look for hint output
9132 if (sscanf(message, "Hint: %s", buf1) == 1) {
9133 if (cps == &first && hintRequested) {
9134 hintRequested = FALSE;
9135 if (ParseOneMove(buf1, forwardMostMove, &moveType,
9136 &fromX, &fromY, &toX, &toY, &promoChar)) {
9137 (void) CoordsToAlgebraic(boards[forwardMostMove],
9138 PosFlags(forwardMostMove),
9139 fromY, fromX, toY, toX, promoChar, buf1);
9140 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9141 DisplayInformation(buf2);
9143 /* Hint move could not be parsed!? */
9144 snprintf(buf2, sizeof(buf2),
9145 _("Illegal hint move \"%s\"\nfrom %s chess program"),
9146 buf1, _(cps->which));
9147 DisplayError(buf2, 0);
9150 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9156 * Ignore other messages if game is not in progress
9158 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9159 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9162 * look for win, lose, draw, or draw offer
9164 if (strncmp(message, "1-0", 3) == 0) {
9165 char *p, *q, *r = "";
9166 p = strchr(message, '{');
9174 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9176 } else if (strncmp(message, "0-1", 3) == 0) {
9177 char *p, *q, *r = "";
9178 p = strchr(message, '{');
9186 /* Kludge for Arasan 4.1 bug */
9187 if (strcmp(r, "Black resigns") == 0) {
9188 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9191 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9193 } else if (strncmp(message, "1/2", 3) == 0) {
9194 char *p, *q, *r = "";
9195 p = strchr(message, '{');
9204 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9207 } else if (strncmp(message, "White resign", 12) == 0) {
9208 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9210 } else if (strncmp(message, "Black resign", 12) == 0) {
9211 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9213 } else if (strncmp(message, "White matches", 13) == 0 ||
9214 strncmp(message, "Black matches", 13) == 0 ) {
9215 /* [HGM] ignore GNUShogi noises */
9217 } else if (strncmp(message, "White", 5) == 0 &&
9218 message[5] != '(' &&
9219 StrStr(message, "Black") == NULL) {
9220 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9222 } else if (strncmp(message, "Black", 5) == 0 &&
9223 message[5] != '(') {
9224 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9226 } else if (strcmp(message, "resign") == 0 ||
9227 strcmp(message, "computer resigns") == 0) {
9229 case MachinePlaysBlack:
9230 case IcsPlayingBlack:
9231 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9233 case MachinePlaysWhite:
9234 case IcsPlayingWhite:
9235 GameEnds(BlackWins, "White resigns", GE_ENGINE);
9237 case TwoMachinesPlay:
9238 if (cps->twoMachinesColor[0] == 'w')
9239 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9241 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9248 } else if (strncmp(message, "opponent mates", 14) == 0) {
9250 case MachinePlaysBlack:
9251 case IcsPlayingBlack:
9252 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9254 case MachinePlaysWhite:
9255 case IcsPlayingWhite:
9256 GameEnds(BlackWins, "Black mates", GE_ENGINE);
9258 case TwoMachinesPlay:
9259 if (cps->twoMachinesColor[0] == 'w')
9260 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9262 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9269 } else if (strncmp(message, "computer mates", 14) == 0) {
9271 case MachinePlaysBlack:
9272 case IcsPlayingBlack:
9273 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9275 case MachinePlaysWhite:
9276 case IcsPlayingWhite:
9277 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9279 case TwoMachinesPlay:
9280 if (cps->twoMachinesColor[0] == 'w')
9281 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9283 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9290 } else if (strncmp(message, "checkmate", 9) == 0) {
9291 if (WhiteOnMove(forwardMostMove)) {
9292 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9294 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9297 } else if (strstr(message, "Draw") != NULL ||
9298 strstr(message, "game is a draw") != NULL) {
9299 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9301 } else if (strstr(message, "offer") != NULL &&
9302 strstr(message, "draw") != NULL) {
9304 if (appData.zippyPlay && first.initDone) {
9305 /* Relay offer to ICS */
9306 SendToICS(ics_prefix);
9307 SendToICS("draw\n");
9310 cps->offeredDraw = 2; /* valid until this engine moves twice */
9311 if (gameMode == TwoMachinesPlay) {
9312 if (cps->other->offeredDraw) {
9313 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9314 /* [HGM] in two-machine mode we delay relaying draw offer */
9315 /* until after we also have move, to see if it is really claim */
9317 } else if (gameMode == MachinePlaysWhite ||
9318 gameMode == MachinePlaysBlack) {
9319 if (userOfferedDraw) {
9320 DisplayInformation(_("Machine accepts your draw offer"));
9321 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9323 DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9330 * Look for thinking output
9332 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9333 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9335 int plylev, mvleft, mvtot, curscore, time;
9336 char mvname[MOVE_LEN];
9340 int prefixHint = FALSE;
9341 mvname[0] = NULLCHAR;
9344 case MachinePlaysBlack:
9345 case IcsPlayingBlack:
9346 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9348 case MachinePlaysWhite:
9349 case IcsPlayingWhite:
9350 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9355 case IcsObserving: /* [DM] icsEngineAnalyze */
9356 if (!appData.icsEngineAnalyze) ignore = TRUE;
9358 case TwoMachinesPlay:
9359 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9369 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9371 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9372 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9374 if (plyext != ' ' && plyext != '\t') {
9378 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9379 if( cps->scoreIsAbsolute &&
9380 ( gameMode == MachinePlaysBlack ||
9381 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9382 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9383 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9384 !WhiteOnMove(currentMove)
9387 curscore = -curscore;
9390 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9392 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9395 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9396 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9397 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9398 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9399 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9400 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9404 /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9405 DisplayError(_("failed writing PV"), 0);
9408 tempStats.depth = plylev;
9409 tempStats.nodes = nodes;
9410 tempStats.time = time;
9411 tempStats.score = curscore;
9412 tempStats.got_only_move = 0;
9414 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9417 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9418 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9419 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9420 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9421 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9422 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9423 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9424 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9427 /* Buffer overflow protection */
9428 if (pv[0] != NULLCHAR) {
9429 if (strlen(pv) >= sizeof(tempStats.movelist)
9430 && appData.debugMode) {
9432 "PV is too long; using the first %u bytes.\n",
9433 (unsigned) sizeof(tempStats.movelist) - 1);
9436 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9438 sprintf(tempStats.movelist, " no PV\n");
9441 if (tempStats.seen_stat) {
9442 tempStats.ok_to_send = 1;
9445 if (strchr(tempStats.movelist, '(') != NULL) {
9446 tempStats.line_is_book = 1;
9447 tempStats.nr_moves = 0;
9448 tempStats.moves_left = 0;
9450 tempStats.line_is_book = 0;
9453 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9454 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9456 SendProgramStatsToFrontend( cps, &tempStats );
9459 [AS] Protect the thinkOutput buffer from overflow... this
9460 is only useful if buf1 hasn't overflowed first!
9462 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9464 (gameMode == TwoMachinesPlay ?
9465 ToUpper(cps->twoMachinesColor[0]) : ' '),
9466 ((double) curscore) / 100.0,
9467 prefixHint ? lastHint : "",
9468 prefixHint ? " " : "" );
9470 if( buf1[0] != NULLCHAR ) {
9471 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9473 if( strlen(pv) > max_len ) {
9474 if( appData.debugMode) {
9475 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9477 pv[max_len+1] = '\0';
9480 strcat( thinkOutput, pv);
9483 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9484 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9485 DisplayMove(currentMove - 1);
9489 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9490 /* crafty (9.25+) says "(only move) <move>"
9491 * if there is only 1 legal move
9493 sscanf(p, "(only move) %s", buf1);
9494 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9495 sprintf(programStats.movelist, "%s (only move)", buf1);
9496 programStats.depth = 1;
9497 programStats.nr_moves = 1;
9498 programStats.moves_left = 1;
9499 programStats.nodes = 1;
9500 programStats.time = 1;
9501 programStats.got_only_move = 1;
9503 /* Not really, but we also use this member to
9504 mean "line isn't going to change" (Crafty
9505 isn't searching, so stats won't change) */
9506 programStats.line_is_book = 1;
9508 SendProgramStatsToFrontend( cps, &programStats );
9510 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9511 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9512 DisplayMove(currentMove - 1);
9515 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9516 &time, &nodes, &plylev, &mvleft,
9517 &mvtot, mvname) >= 5) {
9518 /* The stat01: line is from Crafty (9.29+) in response
9519 to the "." command */
9520 programStats.seen_stat = 1;
9521 cps->maybeThinking = TRUE;
9523 if (programStats.got_only_move || !appData.periodicUpdates)
9526 programStats.depth = plylev;
9527 programStats.time = time;
9528 programStats.nodes = nodes;
9529 programStats.moves_left = mvleft;
9530 programStats.nr_moves = mvtot;
9531 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9532 programStats.ok_to_send = 1;
9533 programStats.movelist[0] = '\0';
9535 SendProgramStatsToFrontend( cps, &programStats );
9539 } else if (strncmp(message,"++",2) == 0) {
9540 /* Crafty 9.29+ outputs this */
9541 programStats.got_fail = 2;
9544 } else if (strncmp(message,"--",2) == 0) {
9545 /* Crafty 9.29+ outputs this */
9546 programStats.got_fail = 1;
9549 } else if (thinkOutput[0] != NULLCHAR &&
9550 strncmp(message, " ", 4) == 0) {
9551 unsigned message_len;
9554 while (*p && *p == ' ') p++;
9556 message_len = strlen( p );
9558 /* [AS] Avoid buffer overflow */
9559 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9560 strcat(thinkOutput, " ");
9561 strcat(thinkOutput, p);
9564 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9565 strcat(programStats.movelist, " ");
9566 strcat(programStats.movelist, p);
9569 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9570 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9571 DisplayMove(currentMove - 1);
9579 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9580 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9582 ChessProgramStats cpstats;
9584 if (plyext != ' ' && plyext != '\t') {
9588 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9589 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9590 curscore = -curscore;
9593 cpstats.depth = plylev;
9594 cpstats.nodes = nodes;
9595 cpstats.time = time;
9596 cpstats.score = curscore;
9597 cpstats.got_only_move = 0;
9598 cpstats.movelist[0] = '\0';
9600 if (buf1[0] != NULLCHAR) {
9601 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9604 cpstats.ok_to_send = 0;
9605 cpstats.line_is_book = 0;
9606 cpstats.nr_moves = 0;
9607 cpstats.moves_left = 0;
9609 SendProgramStatsToFrontend( cps, &cpstats );
9616 /* Parse a game score from the character string "game", and
9617 record it as the history of the current game. The game
9618 score is NOT assumed to start from the standard position.
9619 The display is not updated in any way.
9622 ParseGameHistory (char *game)
9625 int fromX, fromY, toX, toY, boardIndex;
9630 if (appData.debugMode)
9631 fprintf(debugFP, "Parsing game history: %s\n", game);
9633 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9634 gameInfo.site = StrSave(appData.icsHost);
9635 gameInfo.date = PGNDate();
9636 gameInfo.round = StrSave("-");
9638 /* Parse out names of players */
9639 while (*game == ' ') game++;
9641 while (*game != ' ') *p++ = *game++;
9643 gameInfo.white = StrSave(buf);
9644 while (*game == ' ') game++;
9646 while (*game != ' ' && *game != '\n') *p++ = *game++;
9648 gameInfo.black = StrSave(buf);
9651 boardIndex = blackPlaysFirst ? 1 : 0;
9654 yyboardindex = boardIndex;
9655 moveType = (ChessMove) Myylex();
9657 case IllegalMove: /* maybe suicide chess, etc. */
9658 if (appData.debugMode) {
9659 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9660 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9661 setbuf(debugFP, NULL);
9663 case WhitePromotion:
9664 case BlackPromotion:
9665 case WhiteNonPromotion:
9666 case BlackNonPromotion:
9669 case WhiteCapturesEnPassant:
9670 case BlackCapturesEnPassant:
9671 case WhiteKingSideCastle:
9672 case WhiteQueenSideCastle:
9673 case BlackKingSideCastle:
9674 case BlackQueenSideCastle:
9675 case WhiteKingSideCastleWild:
9676 case WhiteQueenSideCastleWild:
9677 case BlackKingSideCastleWild:
9678 case BlackQueenSideCastleWild:
9680 case WhiteHSideCastleFR:
9681 case WhiteASideCastleFR:
9682 case BlackHSideCastleFR:
9683 case BlackASideCastleFR:
9685 fromX = currentMoveString[0] - AAA;
9686 fromY = currentMoveString[1] - ONE;
9687 toX = currentMoveString[2] - AAA;
9688 toY = currentMoveString[3] - ONE;
9689 promoChar = currentMoveString[4];
9693 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9694 fromX = moveType == WhiteDrop ?
9695 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9696 (int) CharToPiece(ToLower(currentMoveString[0]));
9698 toX = currentMoveString[2] - AAA;
9699 toY = currentMoveString[3] - ONE;
9700 promoChar = NULLCHAR;
9704 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9705 if (appData.debugMode) {
9706 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9707 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9708 setbuf(debugFP, NULL);
9710 DisplayError(buf, 0);
9712 case ImpossibleMove:
9714 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9715 if (appData.debugMode) {
9716 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9717 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9718 setbuf(debugFP, NULL);
9720 DisplayError(buf, 0);
9723 if (boardIndex < backwardMostMove) {
9724 /* Oops, gap. How did that happen? */
9725 DisplayError(_("Gap in move list"), 0);
9728 backwardMostMove = blackPlaysFirst ? 1 : 0;
9729 if (boardIndex > forwardMostMove) {
9730 forwardMostMove = boardIndex;
9734 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9735 strcat(parseList[boardIndex-1], " ");
9736 strcat(parseList[boardIndex-1], yy_text);
9748 case GameUnfinished:
9749 if (gameMode == IcsExamining) {
9750 if (boardIndex < backwardMostMove) {
9751 /* Oops, gap. How did that happen? */
9754 backwardMostMove = blackPlaysFirst ? 1 : 0;
9757 gameInfo.result = moveType;
9758 p = strchr(yy_text, '{');
9759 if (p == NULL) p = strchr(yy_text, '(');
9762 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9764 q = strchr(p, *p == '{' ? '}' : ')');
9765 if (q != NULL) *q = NULLCHAR;
9768 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9769 gameInfo.resultDetails = StrSave(p);
9772 if (boardIndex >= forwardMostMove &&
9773 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9774 backwardMostMove = blackPlaysFirst ? 1 : 0;
9777 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9778 fromY, fromX, toY, toX, promoChar,
9779 parseList[boardIndex]);
9780 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9781 /* currentMoveString is set as a side-effect of yylex */
9782 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9783 strcat(moveList[boardIndex], "\n");
9785 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9786 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9792 if(!IS_SHOGI(gameInfo.variant))
9793 strcat(parseList[boardIndex - 1], "+");
9797 strcat(parseList[boardIndex - 1], "#");
9804 /* Apply a move to the given board */
9806 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9808 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9809 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9811 /* [HGM] compute & store e.p. status and castling rights for new position */
9812 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9814 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9815 oldEP = (signed char)board[EP_STATUS];
9816 board[EP_STATUS] = EP_NONE;
9818 if (fromY == DROP_RANK) {
9820 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9821 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9824 piece = board[toY][toX] = (ChessSquare) fromX;
9826 // ChessSquare victim;
9829 if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9830 // victim = board[killY][killX],
9831 board[killY][killX] = EmptySquare,
9832 board[EP_STATUS] = EP_CAPTURE;
9834 if( board[toY][toX] != EmptySquare ) {
9835 board[EP_STATUS] = EP_CAPTURE;
9836 if( (fromX != toX || fromY != toY) && // not igui!
9837 (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9838 captured == BlackLion && board[fromY][fromX] != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules
9839 board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9843 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9844 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9845 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9847 if( board[fromY][fromX] == WhitePawn ) {
9848 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9849 board[EP_STATUS] = EP_PAWN_MOVE;
9851 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9852 gameInfo.variant != VariantBerolina || toX < fromX)
9853 board[EP_STATUS] = toX | berolina;
9854 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9855 gameInfo.variant != VariantBerolina || toX > fromX)
9856 board[EP_STATUS] = toX;
9859 if( board[fromY][fromX] == BlackPawn ) {
9860 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9861 board[EP_STATUS] = EP_PAWN_MOVE;
9862 if( toY-fromY== -2) {
9863 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9864 gameInfo.variant != VariantBerolina || toX < fromX)
9865 board[EP_STATUS] = toX | berolina;
9866 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9867 gameInfo.variant != VariantBerolina || toX > fromX)
9868 board[EP_STATUS] = toX;
9872 for(i=0; i<nrCastlingRights; i++) {
9873 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9874 board[CASTLING][i] == toX && castlingRank[i] == toY
9875 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9878 if(gameInfo.variant == VariantSChess) { // update virginity
9879 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9880 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9881 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
9882 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
9885 if (fromX == toX && fromY == toY) return;
9887 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9888 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9889 if(gameInfo.variant == VariantKnightmate)
9890 king += (int) WhiteUnicorn - (int) WhiteKing;
9892 /* Code added by Tord: */
9893 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9894 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9895 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9896 board[fromY][fromX] = EmptySquare;
9897 board[toY][toX] = EmptySquare;
9898 if((toX > fromX) != (piece == WhiteRook)) {
9899 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9901 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9903 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9904 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9905 board[fromY][fromX] = EmptySquare;
9906 board[toY][toX] = EmptySquare;
9907 if((toX > fromX) != (piece == BlackRook)) {
9908 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9910 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9912 /* End of code added by Tord */
9914 } else if (board[fromY][fromX] == king
9915 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9916 && toY == fromY && toX > fromX+1) {
9917 board[fromY][fromX] = EmptySquare;
9918 board[toY][toX] = king;
9919 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9920 board[fromY][BOARD_RGHT-1] = EmptySquare;
9921 } else if (board[fromY][fromX] == king
9922 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9923 && toY == fromY && toX < fromX-1) {
9924 board[fromY][fromX] = EmptySquare;
9925 board[toY][toX] = king;
9926 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9927 board[fromY][BOARD_LEFT] = EmptySquare;
9928 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9929 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9930 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9932 /* white pawn promotion */
9933 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9934 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9935 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9936 board[fromY][fromX] = EmptySquare;
9937 } else if ((fromY >= BOARD_HEIGHT>>1)
9938 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9940 && gameInfo.variant != VariantXiangqi
9941 && gameInfo.variant != VariantBerolina
9942 && (board[fromY][fromX] == WhitePawn)
9943 && (board[toY][toX] == EmptySquare)) {
9944 board[fromY][fromX] = EmptySquare;
9945 board[toY][toX] = WhitePawn;
9946 captured = board[toY - 1][toX];
9947 board[toY - 1][toX] = EmptySquare;
9948 } else if ((fromY == BOARD_HEIGHT-4)
9950 && gameInfo.variant == VariantBerolina
9951 && (board[fromY][fromX] == WhitePawn)
9952 && (board[toY][toX] == EmptySquare)) {
9953 board[fromY][fromX] = EmptySquare;
9954 board[toY][toX] = WhitePawn;
9955 if(oldEP & EP_BEROLIN_A) {
9956 captured = board[fromY][fromX-1];
9957 board[fromY][fromX-1] = EmptySquare;
9958 }else{ captured = board[fromY][fromX+1];
9959 board[fromY][fromX+1] = EmptySquare;
9961 } else if (board[fromY][fromX] == king
9962 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9963 && toY == fromY && toX > fromX+1) {
9964 board[fromY][fromX] = EmptySquare;
9965 board[toY][toX] = king;
9966 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9967 board[fromY][BOARD_RGHT-1] = EmptySquare;
9968 } else if (board[fromY][fromX] == king
9969 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9970 && toY == fromY && toX < fromX-1) {
9971 board[fromY][fromX] = EmptySquare;
9972 board[toY][toX] = king;
9973 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9974 board[fromY][BOARD_LEFT] = EmptySquare;
9975 } else if (fromY == 7 && fromX == 3
9976 && board[fromY][fromX] == BlackKing
9977 && toY == 7 && toX == 5) {
9978 board[fromY][fromX] = EmptySquare;
9979 board[toY][toX] = BlackKing;
9980 board[fromY][7] = EmptySquare;
9981 board[toY][4] = BlackRook;
9982 } else if (fromY == 7 && fromX == 3
9983 && board[fromY][fromX] == BlackKing
9984 && toY == 7 && toX == 1) {
9985 board[fromY][fromX] = EmptySquare;
9986 board[toY][toX] = BlackKing;
9987 board[fromY][0] = EmptySquare;
9988 board[toY][2] = BlackRook;
9989 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9990 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9991 && toY < promoRank && promoChar
9993 /* black pawn promotion */
9994 board[toY][toX] = CharToPiece(ToLower(promoChar));
9995 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9996 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9997 board[fromY][fromX] = EmptySquare;
9998 } else if ((fromY < BOARD_HEIGHT>>1)
9999 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
10001 && gameInfo.variant != VariantXiangqi
10002 && gameInfo.variant != VariantBerolina
10003 && (board[fromY][fromX] == BlackPawn)
10004 && (board[toY][toX] == EmptySquare)) {
10005 board[fromY][fromX] = EmptySquare;
10006 board[toY][toX] = BlackPawn;
10007 captured = board[toY + 1][toX];
10008 board[toY + 1][toX] = EmptySquare;
10009 } else if ((fromY == 3)
10011 && gameInfo.variant == VariantBerolina
10012 && (board[fromY][fromX] == BlackPawn)
10013 && (board[toY][toX] == EmptySquare)) {
10014 board[fromY][fromX] = EmptySquare;
10015 board[toY][toX] = BlackPawn;
10016 if(oldEP & EP_BEROLIN_A) {
10017 captured = board[fromY][fromX-1];
10018 board[fromY][fromX-1] = EmptySquare;
10019 }else{ captured = board[fromY][fromX+1];
10020 board[fromY][fromX+1] = EmptySquare;
10023 ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10024 board[fromY][fromX] = EmptySquare;
10025 board[toY][toX] = piece;
10029 if (gameInfo.holdingsWidth != 0) {
10031 /* !!A lot more code needs to be written to support holdings */
10032 /* [HGM] OK, so I have written it. Holdings are stored in the */
10033 /* penultimate board files, so they are automaticlly stored */
10034 /* in the game history. */
10035 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10036 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10037 /* Delete from holdings, by decreasing count */
10038 /* and erasing image if necessary */
10039 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10040 if(p < (int) BlackPawn) { /* white drop */
10041 p -= (int)WhitePawn;
10042 p = PieceToNumber((ChessSquare)p);
10043 if(p >= gameInfo.holdingsSize) p = 0;
10044 if(--board[p][BOARD_WIDTH-2] <= 0)
10045 board[p][BOARD_WIDTH-1] = EmptySquare;
10046 if((int)board[p][BOARD_WIDTH-2] < 0)
10047 board[p][BOARD_WIDTH-2] = 0;
10048 } else { /* black drop */
10049 p -= (int)BlackPawn;
10050 p = PieceToNumber((ChessSquare)p);
10051 if(p >= gameInfo.holdingsSize) p = 0;
10052 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10053 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10054 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10055 board[BOARD_HEIGHT-1-p][1] = 0;
10058 if (captured != EmptySquare && gameInfo.holdingsSize > 0
10059 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
10060 /* [HGM] holdings: Add to holdings, if holdings exist */
10061 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10062 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10063 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10065 p = (int) captured;
10066 if (p >= (int) BlackPawn) {
10067 p -= (int)BlackPawn;
10068 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10069 /* in Shogi restore piece to its original first */
10070 captured = (ChessSquare) (DEMOTED captured);
10073 p = PieceToNumber((ChessSquare)p);
10074 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10075 board[p][BOARD_WIDTH-2]++;
10076 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10078 p -= (int)WhitePawn;
10079 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10080 captured = (ChessSquare) (DEMOTED captured);
10083 p = PieceToNumber((ChessSquare)p);
10084 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10085 board[BOARD_HEIGHT-1-p][1]++;
10086 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10089 } else if (gameInfo.variant == VariantAtomic) {
10090 if (captured != EmptySquare) {
10092 for (y = toY-1; y <= toY+1; y++) {
10093 for (x = toX-1; x <= toX+1; x++) {
10094 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10095 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10096 board[y][x] = EmptySquare;
10100 board[toY][toX] = EmptySquare;
10104 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10105 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10107 if(promoChar == '+') {
10108 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10109 board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10110 if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10111 board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10112 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10113 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10114 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10115 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10116 board[toY][toX] = newPiece;
10118 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10119 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10120 // [HGM] superchess: take promotion piece out of holdings
10121 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10122 if((int)piece < (int)BlackPawn) { // determine stm from piece color
10123 if(!--board[k][BOARD_WIDTH-2])
10124 board[k][BOARD_WIDTH-1] = EmptySquare;
10126 if(!--board[BOARD_HEIGHT-1-k][1])
10127 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10132 /* Updates forwardMostMove */
10134 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10136 int x = toX, y = toY;
10137 char *s = parseList[forwardMostMove];
10138 ChessSquare p = boards[forwardMostMove][toY][toX];
10139 // forwardMostMove++; // [HGM] bare: moved downstream
10141 if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10142 (void) CoordsToAlgebraic(boards[forwardMostMove],
10143 PosFlags(forwardMostMove),
10144 fromY, fromX, y, x, promoChar,
10146 if(killX >= 0 && killY >= 0)
10147 sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10149 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10150 int timeLeft; static int lastLoadFlag=0; int king, piece;
10151 piece = boards[forwardMostMove][fromY][fromX];
10152 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10153 if(gameInfo.variant == VariantKnightmate)
10154 king += (int) WhiteUnicorn - (int) WhiteKing;
10155 if(forwardMostMove == 0) {
10156 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10157 fprintf(serverMoves, "%s;", UserName());
10158 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10159 fprintf(serverMoves, "%s;", second.tidy);
10160 fprintf(serverMoves, "%s;", first.tidy);
10161 if(gameMode == MachinePlaysWhite)
10162 fprintf(serverMoves, "%s;", UserName());
10163 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10164 fprintf(serverMoves, "%s;", second.tidy);
10165 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10166 lastLoadFlag = loadFlag;
10168 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10169 // print castling suffix
10170 if( toY == fromY && piece == king ) {
10172 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10174 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10177 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10178 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
10179 boards[forwardMostMove][toY][toX] == EmptySquare
10180 && fromX != toX && fromY != toY)
10181 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10182 // promotion suffix
10183 if(promoChar != NULLCHAR) {
10184 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10185 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10186 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10187 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10190 char buf[MOVE_LEN*2], *p; int len;
10191 fprintf(serverMoves, "/%d/%d",
10192 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10193 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10194 else timeLeft = blackTimeRemaining/1000;
10195 fprintf(serverMoves, "/%d", timeLeft);
10196 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10197 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10198 if(p = strchr(buf, '=')) *p = NULLCHAR;
10199 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10200 fprintf(serverMoves, "/%s", buf);
10202 fflush(serverMoves);
10205 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10206 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10209 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10210 if (commentList[forwardMostMove+1] != NULL) {
10211 free(commentList[forwardMostMove+1]);
10212 commentList[forwardMostMove+1] = NULL;
10214 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10215 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10216 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10217 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10218 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10219 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10220 adjustedClock = FALSE;
10221 gameInfo.result = GameUnfinished;
10222 if (gameInfo.resultDetails != NULL) {
10223 free(gameInfo.resultDetails);
10224 gameInfo.resultDetails = NULL;
10226 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10227 moveList[forwardMostMove - 1]);
10228 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10234 if(!IS_SHOGI(gameInfo.variant))
10235 strcat(parseList[forwardMostMove - 1], "+");
10239 strcat(parseList[forwardMostMove - 1], "#");
10244 /* Updates currentMove if not pausing */
10246 ShowMove (int fromX, int fromY, int toX, int toY)
10248 int instant = (gameMode == PlayFromGameFile) ?
10249 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10250 if(appData.noGUI) return;
10251 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10253 if (forwardMostMove == currentMove + 1) {
10254 AnimateMove(boards[forwardMostMove - 1],
10255 fromX, fromY, toX, toY);
10258 currentMove = forwardMostMove;
10261 killX = killY = -1; // [HGM] lion: used up
10263 if (instant) return;
10265 DisplayMove(currentMove - 1);
10266 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10267 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10268 SetHighlights(fromX, fromY, toX, toY);
10271 DrawPosition(FALSE, boards[currentMove]);
10272 DisplayBothClocks();
10273 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10277 SendEgtPath (ChessProgramState *cps)
10278 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10279 char buf[MSG_SIZ], name[MSG_SIZ], *p;
10281 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10284 char c, *q = name+1, *r, *s;
10286 name[0] = ','; // extract next format name from feature and copy with prefixed ','
10287 while(*p && *p != ',') *q++ = *p++;
10288 *q++ = ':'; *q = 0;
10289 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10290 strcmp(name, ",nalimov:") == 0 ) {
10291 // take nalimov path from the menu-changeable option first, if it is defined
10292 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10293 SendToProgram(buf,cps); // send egtbpath command for nalimov
10295 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10296 (s = StrStr(appData.egtFormats, name)) != NULL) {
10297 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10298 s = r = StrStr(s, ":") + 1; // beginning of path info
10299 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10300 c = *r; *r = 0; // temporarily null-terminate path info
10301 *--q = 0; // strip of trailig ':' from name
10302 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10304 SendToProgram(buf,cps); // send egtbpath command for this format
10306 if(*p == ',') p++; // read away comma to position for next format name
10311 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10313 int width = 8, height = 8, holdings = 0; // most common sizes
10314 if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10315 // correct the deviations default for each variant
10316 if( v == VariantXiangqi ) width = 9, height = 10;
10317 if( v == VariantShogi ) width = 9, height = 9, holdings = 7;
10318 if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10319 if( v == VariantCapablanca || v == VariantCapaRandom ||
10320 v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10322 if( v == VariantCourier ) width = 12;
10323 if( v == VariantSuper ) holdings = 8;
10324 if( v == VariantGreat ) width = 10, holdings = 8;
10325 if( v == VariantSChess ) holdings = 7;
10326 if( v == VariantGrand ) width = 10, height = 10, holdings = 7;
10327 if( v == VariantChuChess) width = 10, height = 10;
10328 if( v == VariantChu ) width = 12, height = 12;
10329 return boardWidth >= 0 && boardWidth != width || // -1 is default,
10330 boardHeight >= 0 && boardHeight != height || // and thus by definition OK
10331 holdingsSize >= 0 && holdingsSize != holdings;
10334 char variantError[MSG_SIZ];
10337 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10338 { // returns error message (recognizable by upper-case) if engine does not support the variant
10339 char *p, *variant = VariantName(v);
10340 static char b[MSG_SIZ];
10341 if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10342 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10343 holdingsSize, variant); // cook up sized variant name
10344 /* [HGM] varsize: try first if this deviant size variant is specifically known */
10345 if(StrStr(list, b) == NULL) {
10346 // specific sized variant not known, check if general sizing allowed
10347 if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10348 snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10349 boardWidth, boardHeight, holdingsSize, engine);
10352 /* [HGM] here we really should compare with the maximum supported board size */
10354 } else snprintf(b, MSG_SIZ,"%s", variant);
10355 if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10356 p = StrStr(list, b);
10357 while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10359 // occurs not at all in list, or only as sub-string
10360 snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10361 if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10362 int l = strlen(variantError);
10364 while(p != list && p[-1] != ',') p--;
10365 q = strchr(p, ',');
10366 if(q) *q = NULLCHAR;
10367 snprintf(variantError + l, MSG_SIZ - l, _(", but %s is"), p);
10376 InitChessProgram (ChessProgramState *cps, int setup)
10377 /* setup needed to setup FRC opening position */
10379 char buf[MSG_SIZ], *b;
10380 if (appData.noChessProgram) return;
10381 hintRequested = FALSE;
10382 bookRequested = FALSE;
10384 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10385 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10386 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10387 if(cps->memSize) { /* [HGM] memory */
10388 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10389 SendToProgram(buf, cps);
10391 SendEgtPath(cps); /* [HGM] EGT */
10392 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10393 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10394 SendToProgram(buf, cps);
10397 SendToProgram(cps->initString, cps);
10398 if (gameInfo.variant != VariantNormal &&
10399 gameInfo.variant != VariantLoadable
10400 /* [HGM] also send variant if board size non-standard */
10401 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10403 b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10404 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10406 DisplayFatalError(variantError, 0, 1);
10410 snprintf(buf, MSG_SIZ, "variant %s\n", b);
10411 SendToProgram(buf, cps);
10413 currentlyInitializedVariant = gameInfo.variant;
10415 /* [HGM] send opening position in FRC to first engine */
10417 SendToProgram("force\n", cps);
10419 /* engine is now in force mode! Set flag to wake it up after first move. */
10420 setboardSpoiledMachineBlack = 1;
10423 if (cps->sendICS) {
10424 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10425 SendToProgram(buf, cps);
10427 cps->maybeThinking = FALSE;
10428 cps->offeredDraw = 0;
10429 if (!appData.icsActive) {
10430 SendTimeControl(cps, movesPerSession, timeControl,
10431 timeIncrement, appData.searchDepth,
10434 if (appData.showThinking
10435 // [HGM] thinking: four options require thinking output to be sent
10436 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10438 SendToProgram("post\n", cps);
10440 SendToProgram("hard\n", cps);
10441 if (!appData.ponderNextMove) {
10442 /* Warning: "easy" is a toggle in GNU Chess, so don't send
10443 it without being sure what state we are in first. "hard"
10444 is not a toggle, so that one is OK.
10446 SendToProgram("easy\n", cps);
10448 if (cps->usePing) {
10449 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10450 SendToProgram(buf, cps);
10452 cps->initDone = TRUE;
10453 ClearEngineOutputPane(cps == &second);
10458 ResendOptions (ChessProgramState *cps)
10459 { // send the stored value of the options
10462 Option *opt = cps->option;
10463 for(i=0; i<cps->nrOptions; i++, opt++) {
10464 switch(opt->type) {
10468 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10471 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10474 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10480 SendToProgram(buf, cps);
10485 StartChessProgram (ChessProgramState *cps)
10490 if (appData.noChessProgram) return;
10491 cps->initDone = FALSE;
10493 if (strcmp(cps->host, "localhost") == 0) {
10494 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10495 } else if (*appData.remoteShell == NULLCHAR) {
10496 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10498 if (*appData.remoteUser == NULLCHAR) {
10499 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10502 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10503 cps->host, appData.remoteUser, cps->program);
10505 err = StartChildProcess(buf, "", &cps->pr);
10509 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10510 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10511 if(cps != &first) return;
10512 appData.noChessProgram = TRUE;
10515 // DisplayFatalError(buf, err, 1);
10516 // cps->pr = NoProc;
10517 // cps->isr = NULL;
10521 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10522 if (cps->protocolVersion > 1) {
10523 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10524 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10525 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10526 cps->comboCnt = 0; // and values of combo boxes
10528 SendToProgram(buf, cps);
10529 if(cps->reload) ResendOptions(cps);
10531 SendToProgram("xboard\n", cps);
10536 TwoMachinesEventIfReady P((void))
10538 static int curMess = 0;
10539 if (first.lastPing != first.lastPong) {
10540 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10541 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10544 if (second.lastPing != second.lastPong) {
10545 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10546 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10549 DisplayMessage("", ""); curMess = 0;
10550 TwoMachinesEvent();
10554 MakeName (char *template)
10558 static char buf[MSG_SIZ];
10562 clock = time((time_t *)NULL);
10563 tm = localtime(&clock);
10565 while(*p++ = *template++) if(p[-1] == '%') {
10566 switch(*template++) {
10567 case 0: *p = 0; return buf;
10568 case 'Y': i = tm->tm_year+1900; break;
10569 case 'y': i = tm->tm_year-100; break;
10570 case 'M': i = tm->tm_mon+1; break;
10571 case 'd': i = tm->tm_mday; break;
10572 case 'h': i = tm->tm_hour; break;
10573 case 'm': i = tm->tm_min; break;
10574 case 's': i = tm->tm_sec; break;
10577 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10583 CountPlayers (char *p)
10586 while(p = strchr(p, '\n')) p++, n++; // count participants
10591 WriteTourneyFile (char *results, FILE *f)
10592 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10593 if(f == NULL) f = fopen(appData.tourneyFile, "w");
10594 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10595 // create a file with tournament description
10596 fprintf(f, "-participants {%s}\n", appData.participants);
10597 fprintf(f, "-seedBase %d\n", appData.seedBase);
10598 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10599 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10600 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10601 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10602 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10603 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10604 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10605 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10606 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10607 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10608 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10609 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10610 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10611 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10612 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10613 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10614 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10615 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10616 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10617 fprintf(f, "-smpCores %d\n", appData.smpCores);
10619 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10621 fprintf(f, "-mps %d\n", appData.movesPerSession);
10622 fprintf(f, "-tc %s\n", appData.timeControl);
10623 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10625 fprintf(f, "-results \"%s\"\n", results);
10630 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10633 Substitute (char *participants, int expunge)
10635 int i, changed, changes=0, nPlayers=0;
10636 char *p, *q, *r, buf[MSG_SIZ];
10637 if(participants == NULL) return;
10638 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10639 r = p = participants; q = appData.participants;
10640 while(*p && *p == *q) {
10641 if(*p == '\n') r = p+1, nPlayers++;
10644 if(*p) { // difference
10645 while(*p && *p++ != '\n');
10646 while(*q && *q++ != '\n');
10647 changed = nPlayers;
10648 changes = 1 + (strcmp(p, q) != 0);
10650 if(changes == 1) { // a single engine mnemonic was changed
10651 q = r; while(*q) nPlayers += (*q++ == '\n');
10652 p = buf; while(*r && (*p = *r++) != '\n') p++;
10654 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10655 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10656 if(mnemonic[i]) { // The substitute is valid
10658 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10659 flock(fileno(f), LOCK_EX);
10660 ParseArgsFromFile(f);
10661 fseek(f, 0, SEEK_SET);
10662 FREE(appData.participants); appData.participants = participants;
10663 if(expunge) { // erase results of replaced engine
10664 int len = strlen(appData.results), w, b, dummy;
10665 for(i=0; i<len; i++) {
10666 Pairing(i, nPlayers, &w, &b, &dummy);
10667 if((w == changed || b == changed) && appData.results[i] == '*') {
10668 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10673 for(i=0; i<len; i++) {
10674 Pairing(i, nPlayers, &w, &b, &dummy);
10675 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10678 WriteTourneyFile(appData.results, f);
10679 fclose(f); // release lock
10682 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10684 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10685 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10686 free(participants);
10691 CheckPlayers (char *participants)
10694 char buf[MSG_SIZ], *p;
10695 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10696 while(p = strchr(participants, '\n')) {
10698 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10700 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10702 DisplayError(buf, 0);
10706 participants = p + 1;
10712 CreateTourney (char *name)
10715 if(matchMode && strcmp(name, appData.tourneyFile)) {
10716 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10718 if(name[0] == NULLCHAR) {
10719 if(appData.participants[0])
10720 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10723 f = fopen(name, "r");
10724 if(f) { // file exists
10725 ASSIGN(appData.tourneyFile, name);
10726 ParseArgsFromFile(f); // parse it
10728 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10729 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10730 DisplayError(_("Not enough participants"), 0);
10733 if(CheckPlayers(appData.participants)) return 0;
10734 ASSIGN(appData.tourneyFile, name);
10735 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10736 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10739 appData.noChessProgram = FALSE;
10740 appData.clockMode = TRUE;
10746 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10748 char buf[MSG_SIZ], *p, *q;
10749 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10750 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10751 skip = !all && group[0]; // if group requested, we start in skip mode
10752 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10753 p = names; q = buf; header = 0;
10754 while(*p && *p != '\n') *q++ = *p++;
10756 if(*p == '\n') p++;
10757 if(buf[0] == '#') {
10758 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10759 depth++; // we must be entering a new group
10760 if(all) continue; // suppress printing group headers when complete list requested
10762 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10764 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10765 if(engineList[i]) free(engineList[i]);
10766 engineList[i] = strdup(buf);
10767 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10768 if(engineMnemonic[i]) free(engineMnemonic[i]);
10769 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10771 sscanf(q + 8, "%s", buf + strlen(buf));
10774 engineMnemonic[i] = strdup(buf);
10777 engineList[i] = engineMnemonic[i] = NULL;
10781 // following implemented as macro to avoid type limitations
10782 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10785 SwapEngines (int n)
10786 { // swap settings for first engine and other engine (so far only some selected options)
10791 SWAP(chessProgram, p)
10793 SWAP(hasOwnBookUCI, h)
10794 SWAP(protocolVersion, h)
10796 SWAP(scoreIsAbsolute, h)
10801 SWAP(engOptions, p)
10802 SWAP(engInitString, p)
10803 SWAP(computerString, p)
10805 SWAP(fenOverride, p)
10807 SWAP(accumulateTC, h)
10813 GetEngineLine (char *s, int n)
10817 extern char *icsNames;
10818 if(!s || !*s) return 0;
10819 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10820 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10821 if(!mnemonic[i]) return 0;
10822 if(n == 11) return 1; // just testing if there was a match
10823 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10824 if(n == 1) SwapEngines(n);
10825 ParseArgsFromString(buf);
10826 if(n == 1) SwapEngines(n);
10827 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10828 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10829 ParseArgsFromString(buf);
10835 SetPlayer (int player, char *p)
10836 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10838 char buf[MSG_SIZ], *engineName;
10839 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10840 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10841 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10843 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10844 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10845 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10846 ParseArgsFromString(buf);
10847 } else { // no engine with this nickname is installed!
10848 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10849 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10850 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10852 DisplayError(buf, 0);
10859 char *recentEngines;
10862 RecentEngineEvent (int nr)
10865 // SwapEngines(1); // bump first to second
10866 // ReplaceEngine(&second, 1); // and load it there
10867 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10868 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10869 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10870 ReplaceEngine(&first, 0);
10871 FloatToFront(&appData.recentEngineList, command[n]);
10876 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10877 { // determine players from game number
10878 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10880 if(appData.tourneyType == 0) {
10881 roundsPerCycle = (nPlayers - 1) | 1;
10882 pairingsPerRound = nPlayers / 2;
10883 } else if(appData.tourneyType > 0) {
10884 roundsPerCycle = nPlayers - appData.tourneyType;
10885 pairingsPerRound = appData.tourneyType;
10887 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10888 gamesPerCycle = gamesPerRound * roundsPerCycle;
10889 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10890 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10891 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10892 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10893 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10894 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10896 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10897 if(appData.roundSync) *syncInterval = gamesPerRound;
10899 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10901 if(appData.tourneyType == 0) {
10902 if(curPairing == (nPlayers-1)/2 ) {
10903 *whitePlayer = curRound;
10904 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10906 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10907 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10908 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10909 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10911 } else if(appData.tourneyType > 1) {
10912 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10913 *whitePlayer = curRound + appData.tourneyType;
10914 } else if(appData.tourneyType > 0) {
10915 *whitePlayer = curPairing;
10916 *blackPlayer = curRound + appData.tourneyType;
10919 // take care of white/black alternation per round.
10920 // For cycles and games this is already taken care of by default, derived from matchGame!
10921 return curRound & 1;
10925 NextTourneyGame (int nr, int *swapColors)
10926 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10928 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10930 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10931 tf = fopen(appData.tourneyFile, "r");
10932 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10933 ParseArgsFromFile(tf); fclose(tf);
10934 InitTimeControls(); // TC might be altered from tourney file
10936 nPlayers = CountPlayers(appData.participants); // count participants
10937 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10938 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10941 p = q = appData.results;
10942 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10943 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10944 DisplayMessage(_("Waiting for other game(s)"),"");
10945 waitingForGame = TRUE;
10946 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10949 waitingForGame = FALSE;
10952 if(appData.tourneyType < 0) {
10953 if(nr>=0 && !pairingReceived) {
10955 if(pairing.pr == NoProc) {
10956 if(!appData.pairingEngine[0]) {
10957 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10960 StartChessProgram(&pairing); // starts the pairing engine
10962 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10963 SendToProgram(buf, &pairing);
10964 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10965 SendToProgram(buf, &pairing);
10966 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10968 pairingReceived = 0; // ... so we continue here
10970 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10971 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10972 matchGame = 1; roundNr = nr / syncInterval + 1;
10975 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10977 // redefine engines, engine dir, etc.
10978 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10979 if(first.pr == NoProc) {
10980 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10981 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10983 if(second.pr == NoProc) {
10985 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10986 SwapEngines(1); // and make that valid for second engine by swapping
10987 InitEngine(&second, 1);
10989 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10990 UpdateLogos(FALSE); // leave display to ModeHiglight()
10996 { // performs game initialization that does not invoke engines, and then tries to start the game
10997 int res, firstWhite, swapColors = 0;
10998 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10999 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
11001 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11002 if(strcmp(buf, currentDebugFile)) { // name has changed
11003 FILE *f = fopen(buf, "w");
11004 if(f) { // if opening the new file failed, just keep using the old one
11005 ASSIGN(currentDebugFile, buf);
11009 if(appData.serverFileName) {
11010 if(serverFP) fclose(serverFP);
11011 serverFP = fopen(appData.serverFileName, "w");
11012 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11013 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11017 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11018 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11019 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
11020 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11021 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11022 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11023 Reset(FALSE, first.pr != NoProc);
11024 res = LoadGameOrPosition(matchGame); // setup game
11025 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11026 if(!res) return; // abort when bad game/pos file
11027 TwoMachinesEvent();
11031 UserAdjudicationEvent (int result)
11033 ChessMove gameResult = GameIsDrawn;
11036 gameResult = WhiteWins;
11038 else if( result < 0 ) {
11039 gameResult = BlackWins;
11042 if( gameMode == TwoMachinesPlay ) {
11043 GameEnds( gameResult, "User adjudication", GE_XBOARD );
11048 // [HGM] save: calculate checksum of game to make games easily identifiable
11050 StringCheckSum (char *s)
11053 if(s==NULL) return 0;
11054 while(*s) i = i*259 + *s++;
11062 for(i=backwardMostMove; i<forwardMostMove; i++) {
11063 sum += pvInfoList[i].depth;
11064 sum += StringCheckSum(parseList[i]);
11065 sum += StringCheckSum(commentList[i]);
11068 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11069 return sum + StringCheckSum(commentList[i]);
11070 } // end of save patch
11073 GameEnds (ChessMove result, char *resultDetails, int whosays)
11075 GameMode nextGameMode;
11077 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11079 if(endingGame) return; /* [HGM] crash: forbid recursion */
11081 if(twoBoards) { // [HGM] dual: switch back to one board
11082 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11083 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11085 if (appData.debugMode) {
11086 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11087 result, resultDetails ? resultDetails : "(null)", whosays);
11090 fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11092 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11094 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11095 /* If we are playing on ICS, the server decides when the
11096 game is over, but the engine can offer to draw, claim
11100 if (appData.zippyPlay && first.initDone) {
11101 if (result == GameIsDrawn) {
11102 /* In case draw still needs to be claimed */
11103 SendToICS(ics_prefix);
11104 SendToICS("draw\n");
11105 } else if (StrCaseStr(resultDetails, "resign")) {
11106 SendToICS(ics_prefix);
11107 SendToICS("resign\n");
11111 endingGame = 0; /* [HGM] crash */
11115 /* If we're loading the game from a file, stop */
11116 if (whosays == GE_FILE) {
11117 (void) StopLoadGameTimer();
11121 /* Cancel draw offers */
11122 first.offeredDraw = second.offeredDraw = 0;
11124 /* If this is an ICS game, only ICS can really say it's done;
11125 if not, anyone can. */
11126 isIcsGame = (gameMode == IcsPlayingWhite ||
11127 gameMode == IcsPlayingBlack ||
11128 gameMode == IcsObserving ||
11129 gameMode == IcsExamining);
11131 if (!isIcsGame || whosays == GE_ICS) {
11132 /* OK -- not an ICS game, or ICS said it was done */
11134 if (!isIcsGame && !appData.noChessProgram)
11135 SetUserThinkingEnables();
11137 /* [HGM] if a machine claims the game end we verify this claim */
11138 if(gameMode == TwoMachinesPlay && appData.testClaims) {
11139 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11141 ChessMove trueResult = (ChessMove) -1;
11143 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
11144 first.twoMachinesColor[0] :
11145 second.twoMachinesColor[0] ;
11147 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11148 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11149 /* [HGM] verify: engine mate claims accepted if they were flagged */
11150 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11152 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11153 /* [HGM] verify: engine mate claims accepted if they were flagged */
11154 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11156 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11157 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11160 // now verify win claims, but not in drop games, as we don't understand those yet
11161 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11162 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11163 (result == WhiteWins && claimer == 'w' ||
11164 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
11165 if (appData.debugMode) {
11166 fprintf(debugFP, "result=%d sp=%d move=%d\n",
11167 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11169 if(result != trueResult) {
11170 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11171 result = claimer == 'w' ? BlackWins : WhiteWins;
11172 resultDetails = buf;
11175 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11176 && (forwardMostMove <= backwardMostMove ||
11177 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11178 (claimer=='b')==(forwardMostMove&1))
11180 /* [HGM] verify: draws that were not flagged are false claims */
11181 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11182 result = claimer == 'w' ? BlackWins : WhiteWins;
11183 resultDetails = buf;
11185 /* (Claiming a loss is accepted no questions asked!) */
11186 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11187 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11188 result = GameUnfinished;
11189 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11191 /* [HGM] bare: don't allow bare King to win */
11192 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11193 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11194 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11195 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11196 && result != GameIsDrawn)
11197 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11198 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11199 int p = (signed char)boards[forwardMostMove][i][j] - color;
11200 if(p >= 0 && p <= (int)WhiteKing) k++;
11202 if (appData.debugMode) {
11203 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11204 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11207 result = GameIsDrawn;
11208 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11209 resultDetails = buf;
11215 if(serverMoves != NULL && !loadFlag) { char c = '=';
11216 if(result==WhiteWins) c = '+';
11217 if(result==BlackWins) c = '-';
11218 if(resultDetails != NULL)
11219 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11221 if (resultDetails != NULL) {
11222 gameInfo.result = result;
11223 gameInfo.resultDetails = StrSave(resultDetails);
11225 /* display last move only if game was not loaded from file */
11226 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11227 DisplayMove(currentMove - 1);
11229 if (forwardMostMove != 0) {
11230 if (gameMode != PlayFromGameFile && gameMode != EditGame
11231 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11233 if (*appData.saveGameFile != NULLCHAR) {
11234 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11235 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11237 SaveGameToFile(appData.saveGameFile, TRUE);
11238 } else if (appData.autoSaveGames) {
11239 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11241 if (*appData.savePositionFile != NULLCHAR) {
11242 SavePositionToFile(appData.savePositionFile);
11244 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11248 /* Tell program how game ended in case it is learning */
11249 /* [HGM] Moved this to after saving the PGN, just in case */
11250 /* engine died and we got here through time loss. In that */
11251 /* case we will get a fatal error writing the pipe, which */
11252 /* would otherwise lose us the PGN. */
11253 /* [HGM] crash: not needed anymore, but doesn't hurt; */
11254 /* output during GameEnds should never be fatal anymore */
11255 if (gameMode == MachinePlaysWhite ||
11256 gameMode == MachinePlaysBlack ||
11257 gameMode == TwoMachinesPlay ||
11258 gameMode == IcsPlayingWhite ||
11259 gameMode == IcsPlayingBlack ||
11260 gameMode == BeginningOfGame) {
11262 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11264 if (first.pr != NoProc) {
11265 SendToProgram(buf, &first);
11267 if (second.pr != NoProc &&
11268 gameMode == TwoMachinesPlay) {
11269 SendToProgram(buf, &second);
11274 if (appData.icsActive) {
11275 if (appData.quietPlay &&
11276 (gameMode == IcsPlayingWhite ||
11277 gameMode == IcsPlayingBlack)) {
11278 SendToICS(ics_prefix);
11279 SendToICS("set shout 1\n");
11281 nextGameMode = IcsIdle;
11282 ics_user_moved = FALSE;
11283 /* clean up premove. It's ugly when the game has ended and the
11284 * premove highlights are still on the board.
11287 gotPremove = FALSE;
11288 ClearPremoveHighlights();
11289 DrawPosition(FALSE, boards[currentMove]);
11291 if (whosays == GE_ICS) {
11294 if (gameMode == IcsPlayingWhite)
11296 else if(gameMode == IcsPlayingBlack)
11297 PlayIcsLossSound();
11300 if (gameMode == IcsPlayingBlack)
11302 else if(gameMode == IcsPlayingWhite)
11303 PlayIcsLossSound();
11306 PlayIcsDrawSound();
11309 PlayIcsUnfinishedSound();
11312 if(appData.quitNext) { ExitEvent(0); return; }
11313 } else if (gameMode == EditGame ||
11314 gameMode == PlayFromGameFile ||
11315 gameMode == AnalyzeMode ||
11316 gameMode == AnalyzeFile) {
11317 nextGameMode = gameMode;
11319 nextGameMode = EndOfGame;
11324 nextGameMode = gameMode;
11327 if (appData.noChessProgram) {
11328 gameMode = nextGameMode;
11330 endingGame = 0; /* [HGM] crash */
11335 /* Put first chess program into idle state */
11336 if (first.pr != NoProc &&
11337 (gameMode == MachinePlaysWhite ||
11338 gameMode == MachinePlaysBlack ||
11339 gameMode == TwoMachinesPlay ||
11340 gameMode == IcsPlayingWhite ||
11341 gameMode == IcsPlayingBlack ||
11342 gameMode == BeginningOfGame)) {
11343 SendToProgram("force\n", &first);
11344 if (first.usePing) {
11346 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11347 SendToProgram(buf, &first);
11350 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11351 /* Kill off first chess program */
11352 if (first.isr != NULL)
11353 RemoveInputSource(first.isr);
11356 if (first.pr != NoProc) {
11358 DoSleep( appData.delayBeforeQuit );
11359 SendToProgram("quit\n", &first);
11360 DestroyChildProcess(first.pr, 4 + first.useSigterm);
11361 first.reload = TRUE;
11365 if (second.reuse) {
11366 /* Put second chess program into idle state */
11367 if (second.pr != NoProc &&
11368 gameMode == TwoMachinesPlay) {
11369 SendToProgram("force\n", &second);
11370 if (second.usePing) {
11372 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11373 SendToProgram(buf, &second);
11376 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11377 /* Kill off second chess program */
11378 if (second.isr != NULL)
11379 RemoveInputSource(second.isr);
11382 if (second.pr != NoProc) {
11383 DoSleep( appData.delayBeforeQuit );
11384 SendToProgram("quit\n", &second);
11385 DestroyChildProcess(second.pr, 4 + second.useSigterm);
11386 second.reload = TRUE;
11388 second.pr = NoProc;
11391 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11392 char resChar = '=';
11396 if (first.twoMachinesColor[0] == 'w') {
11399 second.matchWins++;
11404 if (first.twoMachinesColor[0] == 'b') {
11407 second.matchWins++;
11410 case GameUnfinished:
11416 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11417 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11418 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11419 ReserveGame(nextGame, resChar); // sets nextGame
11420 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11421 else ranking = strdup("busy"); //suppress popup when aborted but not finished
11422 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11424 if (nextGame <= appData.matchGames && !abortMatch) {
11425 gameMode = nextGameMode;
11426 matchGame = nextGame; // this will be overruled in tourney mode!
11427 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11428 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11429 endingGame = 0; /* [HGM] crash */
11432 gameMode = nextGameMode;
11433 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11434 first.tidy, second.tidy,
11435 first.matchWins, second.matchWins,
11436 appData.matchGames - (first.matchWins + second.matchWins));
11437 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11438 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11439 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11440 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11441 first.twoMachinesColor = "black\n";
11442 second.twoMachinesColor = "white\n";
11444 first.twoMachinesColor = "white\n";
11445 second.twoMachinesColor = "black\n";
11449 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11450 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11452 gameMode = nextGameMode;
11454 endingGame = 0; /* [HGM] crash */
11455 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11456 if(matchMode == TRUE) { // match through command line: exit with or without popup
11458 ToNrEvent(forwardMostMove);
11459 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11461 } else DisplayFatalError(buf, 0, 0);
11462 } else { // match through menu; just stop, with or without popup
11463 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11466 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11467 } else DisplayNote(buf);
11469 if(ranking) free(ranking);
11473 /* Assumes program was just initialized (initString sent).
11474 Leaves program in force mode. */
11476 FeedMovesToProgram (ChessProgramState *cps, int upto)
11480 if (appData.debugMode)
11481 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11482 startedFromSetupPosition ? "position and " : "",
11483 backwardMostMove, upto, cps->which);
11484 if(currentlyInitializedVariant != gameInfo.variant) {
11486 // [HGM] variantswitch: make engine aware of new variant
11487 if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11488 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11489 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11490 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11491 SendToProgram(buf, cps);
11492 currentlyInitializedVariant = gameInfo.variant;
11494 SendToProgram("force\n", cps);
11495 if (startedFromSetupPosition) {
11496 SendBoard(cps, backwardMostMove);
11497 if (appData.debugMode) {
11498 fprintf(debugFP, "feedMoves\n");
11501 for (i = backwardMostMove; i < upto; i++) {
11502 SendMoveToProgram(i, cps);
11508 ResurrectChessProgram ()
11510 /* The chess program may have exited.
11511 If so, restart it and feed it all the moves made so far. */
11512 static int doInit = 0;
11514 if (appData.noChessProgram) return 1;
11516 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11517 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11518 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11519 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11521 if (first.pr != NoProc) return 1;
11522 StartChessProgram(&first);
11524 InitChessProgram(&first, FALSE);
11525 FeedMovesToProgram(&first, currentMove);
11527 if (!first.sendTime) {
11528 /* can't tell gnuchess what its clock should read,
11529 so we bow to its notion. */
11531 timeRemaining[0][currentMove] = whiteTimeRemaining;
11532 timeRemaining[1][currentMove] = blackTimeRemaining;
11535 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11536 appData.icsEngineAnalyze) && first.analysisSupport) {
11537 SendToProgram("analyze\n", &first);
11538 first.analyzing = TRUE;
11544 * Button procedures
11547 Reset (int redraw, int init)
11551 if (appData.debugMode) {
11552 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11553 redraw, init, gameMode);
11555 CleanupTail(); // [HGM] vari: delete any stored variations
11556 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11557 pausing = pauseExamInvalid = FALSE;
11558 startedFromSetupPosition = blackPlaysFirst = FALSE;
11560 whiteFlag = blackFlag = FALSE;
11561 userOfferedDraw = FALSE;
11562 hintRequested = bookRequested = FALSE;
11563 first.maybeThinking = FALSE;
11564 second.maybeThinking = FALSE;
11565 first.bookSuspend = FALSE; // [HGM] book
11566 second.bookSuspend = FALSE;
11567 thinkOutput[0] = NULLCHAR;
11568 lastHint[0] = NULLCHAR;
11569 ClearGameInfo(&gameInfo);
11570 gameInfo.variant = StringToVariant(appData.variant);
11571 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11572 ics_user_moved = ics_clock_paused = FALSE;
11573 ics_getting_history = H_FALSE;
11575 white_holding[0] = black_holding[0] = NULLCHAR;
11576 ClearProgramStats();
11577 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11581 flipView = appData.flipView;
11582 ClearPremoveHighlights();
11583 gotPremove = FALSE;
11584 alarmSounded = FALSE;
11585 killX = killY = -1; // [HGM] lion
11587 GameEnds(EndOfFile, NULL, GE_PLAYER);
11588 if(appData.serverMovesName != NULL) {
11589 /* [HGM] prepare to make moves file for broadcasting */
11590 clock_t t = clock();
11591 if(serverMoves != NULL) fclose(serverMoves);
11592 serverMoves = fopen(appData.serverMovesName, "r");
11593 if(serverMoves != NULL) {
11594 fclose(serverMoves);
11595 /* delay 15 sec before overwriting, so all clients can see end */
11596 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11598 serverMoves = fopen(appData.serverMovesName, "w");
11602 gameMode = BeginningOfGame;
11604 if(appData.icsActive) gameInfo.variant = VariantNormal;
11605 currentMove = forwardMostMove = backwardMostMove = 0;
11606 MarkTargetSquares(1);
11607 InitPosition(redraw);
11608 for (i = 0; i < MAX_MOVES; i++) {
11609 if (commentList[i] != NULL) {
11610 free(commentList[i]);
11611 commentList[i] = NULL;
11615 timeRemaining[0][0] = whiteTimeRemaining;
11616 timeRemaining[1][0] = blackTimeRemaining;
11618 if (first.pr == NoProc) {
11619 StartChessProgram(&first);
11622 InitChessProgram(&first, startedFromSetupPosition);
11625 DisplayMessage("", "");
11626 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11627 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11628 ClearMap(); // [HGM] exclude: invalidate map
11632 AutoPlayGameLoop ()
11635 if (!AutoPlayOneMove())
11637 if (matchMode || appData.timeDelay == 0)
11639 if (appData.timeDelay < 0)
11641 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11649 ReloadGame(1); // next game
11655 int fromX, fromY, toX, toY;
11657 if (appData.debugMode) {
11658 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11661 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11664 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11665 pvInfoList[currentMove].depth = programStats.depth;
11666 pvInfoList[currentMove].score = programStats.score;
11667 pvInfoList[currentMove].time = 0;
11668 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11669 else { // append analysis of final position as comment
11671 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11672 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11674 programStats.depth = 0;
11677 if (currentMove >= forwardMostMove) {
11678 if(gameMode == AnalyzeFile) {
11679 if(appData.loadGameIndex == -1) {
11680 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11681 ScheduleDelayedEvent(AnalyzeNextGame, 10);
11683 ExitAnalyzeMode(); SendToProgram("force\n", &first);
11686 // gameMode = EndOfGame;
11687 // ModeHighlight();
11689 /* [AS] Clear current move marker at the end of a game */
11690 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11695 toX = moveList[currentMove][2] - AAA;
11696 toY = moveList[currentMove][3] - ONE;
11698 if (moveList[currentMove][1] == '@') {
11699 if (appData.highlightLastMove) {
11700 SetHighlights(-1, -1, toX, toY);
11703 fromX = moveList[currentMove][0] - AAA;
11704 fromY = moveList[currentMove][1] - ONE;
11706 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11708 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11710 if (appData.highlightLastMove) {
11711 SetHighlights(fromX, fromY, toX, toY);
11714 DisplayMove(currentMove);
11715 SendMoveToProgram(currentMove++, &first);
11716 DisplayBothClocks();
11717 DrawPosition(FALSE, boards[currentMove]);
11718 // [HGM] PV info: always display, routine tests if empty
11719 DisplayComment(currentMove - 1, commentList[currentMove]);
11725 LoadGameOneMove (ChessMove readAhead)
11727 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11728 char promoChar = NULLCHAR;
11729 ChessMove moveType;
11730 char move[MSG_SIZ];
11733 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11734 gameMode != AnalyzeMode && gameMode != Training) {
11739 yyboardindex = forwardMostMove;
11740 if (readAhead != EndOfFile) {
11741 moveType = readAhead;
11743 if (gameFileFP == NULL)
11745 moveType = (ChessMove) Myylex();
11749 switch (moveType) {
11751 if (appData.debugMode)
11752 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11755 /* append the comment but don't display it */
11756 AppendComment(currentMove, p, FALSE);
11759 case WhiteCapturesEnPassant:
11760 case BlackCapturesEnPassant:
11761 case WhitePromotion:
11762 case BlackPromotion:
11763 case WhiteNonPromotion:
11764 case BlackNonPromotion:
11767 case WhiteKingSideCastle:
11768 case WhiteQueenSideCastle:
11769 case BlackKingSideCastle:
11770 case BlackQueenSideCastle:
11771 case WhiteKingSideCastleWild:
11772 case WhiteQueenSideCastleWild:
11773 case BlackKingSideCastleWild:
11774 case BlackQueenSideCastleWild:
11776 case WhiteHSideCastleFR:
11777 case WhiteASideCastleFR:
11778 case BlackHSideCastleFR:
11779 case BlackASideCastleFR:
11781 if (appData.debugMode)
11782 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11783 fromX = currentMoveString[0] - AAA;
11784 fromY = currentMoveString[1] - ONE;
11785 toX = currentMoveString[2] - AAA;
11786 toY = currentMoveString[3] - ONE;
11787 promoChar = currentMoveString[4];
11788 if(promoChar == ';') promoChar = NULLCHAR;
11793 if (appData.debugMode)
11794 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11795 fromX = moveType == WhiteDrop ?
11796 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11797 (int) CharToPiece(ToLower(currentMoveString[0]));
11799 toX = currentMoveString[2] - AAA;
11800 toY = currentMoveString[3] - ONE;
11806 case GameUnfinished:
11807 if (appData.debugMode)
11808 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11809 p = strchr(yy_text, '{');
11810 if (p == NULL) p = strchr(yy_text, '(');
11813 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11815 q = strchr(p, *p == '{' ? '}' : ')');
11816 if (q != NULL) *q = NULLCHAR;
11819 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11820 GameEnds(moveType, p, GE_FILE);
11822 if (cmailMsgLoaded) {
11824 flipView = WhiteOnMove(currentMove);
11825 if (moveType == GameUnfinished) flipView = !flipView;
11826 if (appData.debugMode)
11827 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11832 if (appData.debugMode)
11833 fprintf(debugFP, "Parser hit end of file\n");
11834 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11840 if (WhiteOnMove(currentMove)) {
11841 GameEnds(BlackWins, "Black mates", GE_FILE);
11843 GameEnds(WhiteWins, "White mates", GE_FILE);
11847 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11853 case MoveNumberOne:
11854 if (lastLoadGameStart == GNUChessGame) {
11855 /* GNUChessGames have numbers, but they aren't move numbers */
11856 if (appData.debugMode)
11857 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11858 yy_text, (int) moveType);
11859 return LoadGameOneMove(EndOfFile); /* tail recursion */
11861 /* else fall thru */
11866 /* Reached start of next game in file */
11867 if (appData.debugMode)
11868 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11869 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11875 if (WhiteOnMove(currentMove)) {
11876 GameEnds(BlackWins, "Black mates", GE_FILE);
11878 GameEnds(WhiteWins, "White mates", GE_FILE);
11882 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11888 case PositionDiagram: /* should not happen; ignore */
11889 case ElapsedTime: /* ignore */
11890 case NAG: /* ignore */
11891 if (appData.debugMode)
11892 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11893 yy_text, (int) moveType);
11894 return LoadGameOneMove(EndOfFile); /* tail recursion */
11897 if (appData.testLegality) {
11898 if (appData.debugMode)
11899 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11900 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11901 (forwardMostMove / 2) + 1,
11902 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11903 DisplayError(move, 0);
11906 if (appData.debugMode)
11907 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11908 yy_text, currentMoveString);
11909 fromX = currentMoveString[0] - AAA;
11910 fromY = currentMoveString[1] - ONE;
11911 toX = currentMoveString[2] - AAA;
11912 toY = currentMoveString[3] - ONE;
11913 promoChar = currentMoveString[4];
11917 case AmbiguousMove:
11918 if (appData.debugMode)
11919 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11920 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11921 (forwardMostMove / 2) + 1,
11922 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11923 DisplayError(move, 0);
11928 case ImpossibleMove:
11929 if (appData.debugMode)
11930 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11931 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11932 (forwardMostMove / 2) + 1,
11933 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11934 DisplayError(move, 0);
11940 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11941 DrawPosition(FALSE, boards[currentMove]);
11942 DisplayBothClocks();
11943 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11944 DisplayComment(currentMove - 1, commentList[currentMove]);
11946 (void) StopLoadGameTimer();
11948 cmailOldMove = forwardMostMove;
11951 /* currentMoveString is set as a side-effect of yylex */
11953 thinkOutput[0] = NULLCHAR;
11954 MakeMove(fromX, fromY, toX, toY, promoChar);
11955 killX = killY = -1; // [HGM] lion: used up
11956 currentMove = forwardMostMove;
11961 /* Load the nth game from the given file */
11963 LoadGameFromFile (char *filename, int n, char *title, int useList)
11968 if (strcmp(filename, "-") == 0) {
11972 f = fopen(filename, "rb");
11974 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11975 DisplayError(buf, errno);
11979 if (fseek(f, 0, 0) == -1) {
11980 /* f is not seekable; probably a pipe */
11983 if (useList && n == 0) {
11984 int error = GameListBuild(f);
11986 DisplayError(_("Cannot build game list"), error);
11987 } else if (!ListEmpty(&gameList) &&
11988 ((ListGame *) gameList.tailPred)->number > 1) {
11989 GameListPopUp(f, title);
11996 return LoadGame(f, n, title, FALSE);
12001 MakeRegisteredMove ()
12003 int fromX, fromY, toX, toY;
12005 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12006 switch (cmailMoveType[lastLoadGameNumber - 1]) {
12009 if (appData.debugMode)
12010 fprintf(debugFP, "Restoring %s for game %d\n",
12011 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12013 thinkOutput[0] = NULLCHAR;
12014 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12015 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12016 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12017 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12018 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12019 promoChar = cmailMove[lastLoadGameNumber - 1][4];
12020 MakeMove(fromX, fromY, toX, toY, promoChar);
12021 ShowMove(fromX, fromY, toX, toY);
12023 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12030 if (WhiteOnMove(currentMove)) {
12031 GameEnds(BlackWins, "Black mates", GE_PLAYER);
12033 GameEnds(WhiteWins, "White mates", GE_PLAYER);
12038 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12045 if (WhiteOnMove(currentMove)) {
12046 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12048 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12053 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12064 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12066 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12070 if (gameNumber > nCmailGames) {
12071 DisplayError(_("No more games in this message"), 0);
12074 if (f == lastLoadGameFP) {
12075 int offset = gameNumber - lastLoadGameNumber;
12077 cmailMsg[0] = NULLCHAR;
12078 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12079 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12080 nCmailMovesRegistered--;
12082 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12083 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12084 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12087 if (! RegisterMove()) return FALSE;
12091 retVal = LoadGame(f, gameNumber, title, useList);
12093 /* Make move registered during previous look at this game, if any */
12094 MakeRegisteredMove();
12096 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12097 commentList[currentMove]
12098 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12099 DisplayComment(currentMove - 1, commentList[currentMove]);
12105 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12107 ReloadGame (int offset)
12109 int gameNumber = lastLoadGameNumber + offset;
12110 if (lastLoadGameFP == NULL) {
12111 DisplayError(_("No game has been loaded yet"), 0);
12114 if (gameNumber <= 0) {
12115 DisplayError(_("Can't back up any further"), 0);
12118 if (cmailMsgLoaded) {
12119 return CmailLoadGame(lastLoadGameFP, gameNumber,
12120 lastLoadGameTitle, lastLoadGameUseList);
12122 return LoadGame(lastLoadGameFP, gameNumber,
12123 lastLoadGameTitle, lastLoadGameUseList);
12127 int keys[EmptySquare+1];
12130 PositionMatches (Board b1, Board b2)
12133 switch(appData.searchMode) {
12134 case 1: return CompareWithRights(b1, b2);
12136 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12137 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12141 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12142 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12143 sum += keys[b1[r][f]] - keys[b2[r][f]];
12147 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12148 sum += keys[b1[r][f]] - keys[b2[r][f]];
12160 int pieceList[256], quickBoard[256];
12161 ChessSquare pieceType[256] = { EmptySquare };
12162 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12163 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12164 int soughtTotal, turn;
12165 Boolean epOK, flipSearch;
12168 unsigned char piece, to;
12171 #define DSIZE (250000)
12173 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12174 Move *moveDatabase = initialSpace;
12175 unsigned int movePtr, dataSize = DSIZE;
12178 MakePieceList (Board board, int *counts)
12180 int r, f, n=Q_PROMO, total=0;
12181 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12182 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12183 int sq = f + (r<<4);
12184 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12185 quickBoard[sq] = ++n;
12187 pieceType[n] = board[r][f];
12188 counts[board[r][f]]++;
12189 if(board[r][f] == WhiteKing) pieceList[1] = n; else
12190 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12194 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12199 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12201 int sq = fromX + (fromY<<4);
12202 int piece = quickBoard[sq], rook;
12203 quickBoard[sq] = 0;
12204 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12205 if(piece == pieceList[1] && fromY == toY) {
12206 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12207 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12208 moveDatabase[movePtr++].piece = Q_WCASTL;
12209 quickBoard[sq] = piece;
12210 piece = quickBoard[from]; quickBoard[from] = 0;
12211 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12212 } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12213 quickBoard[sq] = 0; // remove Rook
12214 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12215 moveDatabase[movePtr++].piece = Q_WCASTL;
12216 quickBoard[sq] = pieceList[1]; // put King
12218 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12221 if(piece == pieceList[2] && fromY == toY) {
12222 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12223 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12224 moveDatabase[movePtr++].piece = Q_BCASTL;
12225 quickBoard[sq] = piece;
12226 piece = quickBoard[from]; quickBoard[from] = 0;
12227 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12228 } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12229 quickBoard[sq] = 0; // remove Rook
12230 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12231 moveDatabase[movePtr++].piece = Q_BCASTL;
12232 quickBoard[sq] = pieceList[2]; // put King
12234 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12237 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12238 quickBoard[(fromY<<4)+toX] = 0;
12239 moveDatabase[movePtr].piece = Q_EP;
12240 moveDatabase[movePtr++].to = (fromY<<4)+toX;
12241 moveDatabase[movePtr].to = sq;
12243 if(promoPiece != pieceType[piece]) {
12244 moveDatabase[movePtr++].piece = Q_PROMO;
12245 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12247 moveDatabase[movePtr].piece = piece;
12248 quickBoard[sq] = piece;
12253 PackGame (Board board)
12255 Move *newSpace = NULL;
12256 moveDatabase[movePtr].piece = 0; // terminate previous game
12257 if(movePtr > dataSize) {
12258 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12259 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12260 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12263 Move *p = moveDatabase, *q = newSpace;
12264 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
12265 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12266 moveDatabase = newSpace;
12267 } else { // calloc failed, we must be out of memory. Too bad...
12268 dataSize = 0; // prevent calloc events for all subsequent games
12269 return 0; // and signal this one isn't cached
12273 MakePieceList(board, counts);
12278 QuickCompare (Board board, int *minCounts, int *maxCounts)
12279 { // compare according to search mode
12281 switch(appData.searchMode)
12283 case 1: // exact position match
12284 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12285 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12286 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12289 case 2: // can have extra material on empty squares
12290 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12291 if(board[r][f] == EmptySquare) continue;
12292 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12295 case 3: // material with exact Pawn structure
12296 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12297 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12298 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12299 } // fall through to material comparison
12300 case 4: // exact material
12301 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12303 case 6: // material range with given imbalance
12304 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12305 // fall through to range comparison
12306 case 5: // material range
12307 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12313 QuickScan (Board board, Move *move)
12314 { // reconstruct game,and compare all positions in it
12315 int cnt=0, stretch=0, total = MakePieceList(board, counts);
12317 int piece = move->piece;
12318 int to = move->to, from = pieceList[piece];
12319 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12320 if(!piece) return -1;
12321 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12322 piece = (++move)->piece;
12323 from = pieceList[piece];
12324 counts[pieceType[piece]]--;
12325 pieceType[piece] = (ChessSquare) move->to;
12326 counts[move->to]++;
12327 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12328 counts[pieceType[quickBoard[to]]]--;
12329 quickBoard[to] = 0; total--;
12332 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12333 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12334 from = pieceList[piece]; // so this must be King
12335 quickBoard[from] = 0;
12336 pieceList[piece] = to;
12337 from = pieceList[(++move)->piece]; // for FRC this has to be done here
12338 quickBoard[from] = 0; // rook
12339 quickBoard[to] = piece;
12340 to = move->to; piece = move->piece;
12344 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12345 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12346 quickBoard[from] = 0;
12348 quickBoard[to] = piece;
12349 pieceList[piece] = to;
12351 if(QuickCompare(soughtBoard, minSought, maxSought) ||
12352 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12353 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12354 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12356 static int lastCounts[EmptySquare+1];
12358 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12359 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12360 } else stretch = 0;
12361 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12370 flipSearch = FALSE;
12371 CopyBoard(soughtBoard, boards[currentMove]);
12372 soughtTotal = MakePieceList(soughtBoard, maxSought);
12373 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12374 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12375 CopyBoard(reverseBoard, boards[currentMove]);
12376 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12377 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12378 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12379 reverseBoard[r][f] = piece;
12381 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12382 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12383 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12384 || (boards[currentMove][CASTLING][2] == NoRights ||
12385 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12386 && (boards[currentMove][CASTLING][5] == NoRights ||
12387 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12390 CopyBoard(flipBoard, soughtBoard);
12391 CopyBoard(rotateBoard, reverseBoard);
12392 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12393 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
12394 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12397 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12398 if(appData.searchMode >= 5) {
12399 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12400 MakePieceList(soughtBoard, minSought);
12401 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12403 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12404 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12407 GameInfo dummyInfo;
12408 static int creatingBook;
12411 GameContainsPosition (FILE *f, ListGame *lg)
12413 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12414 int fromX, fromY, toX, toY;
12416 static int initDone=FALSE;
12418 // weed out games based on numerical tag comparison
12419 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12420 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12421 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12422 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12424 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12427 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12428 else CopyBoard(boards[scratch], initialPosition); // default start position
12431 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12432 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12435 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12436 fseek(f, lg->offset, 0);
12439 yyboardindex = scratch;
12440 quickFlag = plyNr+1;
12445 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12451 if(plyNr) return -1; // after we have seen moves, this is for new game
12454 case AmbiguousMove: // we cannot reconstruct the game beyond these two
12455 case ImpossibleMove:
12456 case WhiteWins: // game ends here with these four
12459 case GameUnfinished:
12463 if(appData.testLegality) return -1;
12464 case WhiteCapturesEnPassant:
12465 case BlackCapturesEnPassant:
12466 case WhitePromotion:
12467 case BlackPromotion:
12468 case WhiteNonPromotion:
12469 case BlackNonPromotion:
12472 case WhiteKingSideCastle:
12473 case WhiteQueenSideCastle:
12474 case BlackKingSideCastle:
12475 case BlackQueenSideCastle:
12476 case WhiteKingSideCastleWild:
12477 case WhiteQueenSideCastleWild:
12478 case BlackKingSideCastleWild:
12479 case BlackQueenSideCastleWild:
12480 case WhiteHSideCastleFR:
12481 case WhiteASideCastleFR:
12482 case BlackHSideCastleFR:
12483 case BlackASideCastleFR:
12484 fromX = currentMoveString[0] - AAA;
12485 fromY = currentMoveString[1] - ONE;
12486 toX = currentMoveString[2] - AAA;
12487 toY = currentMoveString[3] - ONE;
12488 promoChar = currentMoveString[4];
12492 fromX = next == WhiteDrop ?
12493 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12494 (int) CharToPiece(ToLower(currentMoveString[0]));
12496 toX = currentMoveString[2] - AAA;
12497 toY = currentMoveString[3] - ONE;
12501 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12503 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12504 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12505 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12506 if(appData.findMirror) {
12507 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12508 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12513 /* Load the nth game from open file f */
12515 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12519 int gn = gameNumber;
12520 ListGame *lg = NULL;
12521 int numPGNTags = 0;
12523 GameMode oldGameMode;
12524 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12526 if (appData.debugMode)
12527 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12529 if (gameMode == Training )
12530 SetTrainingModeOff();
12532 oldGameMode = gameMode;
12533 if (gameMode != BeginningOfGame) {
12534 Reset(FALSE, TRUE);
12536 killX = killY = -1; // [HGM] lion: in case we did not Reset
12539 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12540 fclose(lastLoadGameFP);
12544 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12547 fseek(f, lg->offset, 0);
12548 GameListHighlight(gameNumber);
12549 pos = lg->position;
12553 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12554 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12556 DisplayError(_("Game number out of range"), 0);
12561 if (fseek(f, 0, 0) == -1) {
12562 if (f == lastLoadGameFP ?
12563 gameNumber == lastLoadGameNumber + 1 :
12567 DisplayError(_("Can't seek on game file"), 0);
12572 lastLoadGameFP = f;
12573 lastLoadGameNumber = gameNumber;
12574 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12575 lastLoadGameUseList = useList;
12579 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12580 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12581 lg->gameInfo.black);
12583 } else if (*title != NULLCHAR) {
12584 if (gameNumber > 1) {
12585 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12588 DisplayTitle(title);
12592 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12593 gameMode = PlayFromGameFile;
12597 currentMove = forwardMostMove = backwardMostMove = 0;
12598 CopyBoard(boards[0], initialPosition);
12602 * Skip the first gn-1 games in the file.
12603 * Also skip over anything that precedes an identifiable
12604 * start of game marker, to avoid being confused by
12605 * garbage at the start of the file. Currently
12606 * recognized start of game markers are the move number "1",
12607 * the pattern "gnuchess .* game", the pattern
12608 * "^[#;%] [^ ]* game file", and a PGN tag block.
12609 * A game that starts with one of the latter two patterns
12610 * will also have a move number 1, possibly
12611 * following a position diagram.
12612 * 5-4-02: Let's try being more lenient and allowing a game to
12613 * start with an unnumbered move. Does that break anything?
12615 cm = lastLoadGameStart = EndOfFile;
12617 yyboardindex = forwardMostMove;
12618 cm = (ChessMove) Myylex();
12621 if (cmailMsgLoaded) {
12622 nCmailGames = CMAIL_MAX_GAMES - gn;
12625 DisplayError(_("Game not found in file"), 0);
12632 lastLoadGameStart = cm;
12635 case MoveNumberOne:
12636 switch (lastLoadGameStart) {
12641 case MoveNumberOne:
12643 gn--; /* count this game */
12644 lastLoadGameStart = cm;
12653 switch (lastLoadGameStart) {
12656 case MoveNumberOne:
12658 gn--; /* count this game */
12659 lastLoadGameStart = cm;
12662 lastLoadGameStart = cm; /* game counted already */
12670 yyboardindex = forwardMostMove;
12671 cm = (ChessMove) Myylex();
12672 } while (cm == PGNTag || cm == Comment);
12679 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12680 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
12681 != CMAIL_OLD_RESULT) {
12683 cmailResult[ CMAIL_MAX_GAMES
12684 - gn - 1] = CMAIL_OLD_RESULT;
12691 /* Only a NormalMove can be at the start of a game
12692 * without a position diagram. */
12693 if (lastLoadGameStart == EndOfFile ) {
12695 lastLoadGameStart = MoveNumberOne;
12704 if (appData.debugMode)
12705 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12707 if (cm == XBoardGame) {
12708 /* Skip any header junk before position diagram and/or move 1 */
12710 yyboardindex = forwardMostMove;
12711 cm = (ChessMove) Myylex();
12713 if (cm == EndOfFile ||
12714 cm == GNUChessGame || cm == XBoardGame) {
12715 /* Empty game; pretend end-of-file and handle later */
12720 if (cm == MoveNumberOne || cm == PositionDiagram ||
12721 cm == PGNTag || cm == Comment)
12724 } else if (cm == GNUChessGame) {
12725 if (gameInfo.event != NULL) {
12726 free(gameInfo.event);
12728 gameInfo.event = StrSave(yy_text);
12731 startedFromSetupPosition = FALSE;
12732 while (cm == PGNTag) {
12733 if (appData.debugMode)
12734 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12735 err = ParsePGNTag(yy_text, &gameInfo);
12736 if (!err) numPGNTags++;
12738 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12739 if(gameInfo.variant != oldVariant) {
12740 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12741 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12742 InitPosition(TRUE);
12743 oldVariant = gameInfo.variant;
12744 if (appData.debugMode)
12745 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12749 if (gameInfo.fen != NULL) {
12750 Board initial_position;
12751 startedFromSetupPosition = TRUE;
12752 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12754 DisplayError(_("Bad FEN position in file"), 0);
12757 CopyBoard(boards[0], initial_position);
12758 if (blackPlaysFirst) {
12759 currentMove = forwardMostMove = backwardMostMove = 1;
12760 CopyBoard(boards[1], initial_position);
12761 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12762 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12763 timeRemaining[0][1] = whiteTimeRemaining;
12764 timeRemaining[1][1] = blackTimeRemaining;
12765 if (commentList[0] != NULL) {
12766 commentList[1] = commentList[0];
12767 commentList[0] = NULL;
12770 currentMove = forwardMostMove = backwardMostMove = 0;
12772 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12774 initialRulePlies = FENrulePlies;
12775 for( i=0; i< nrCastlingRights; i++ )
12776 initialRights[i] = initial_position[CASTLING][i];
12778 yyboardindex = forwardMostMove;
12779 free(gameInfo.fen);
12780 gameInfo.fen = NULL;
12783 yyboardindex = forwardMostMove;
12784 cm = (ChessMove) Myylex();
12786 /* Handle comments interspersed among the tags */
12787 while (cm == Comment) {
12789 if (appData.debugMode)
12790 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12792 AppendComment(currentMove, p, FALSE);
12793 yyboardindex = forwardMostMove;
12794 cm = (ChessMove) Myylex();
12798 /* don't rely on existence of Event tag since if game was
12799 * pasted from clipboard the Event tag may not exist
12801 if (numPGNTags > 0){
12803 if (gameInfo.variant == VariantNormal) {
12804 VariantClass v = StringToVariant(gameInfo.event);
12805 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12806 if(v < VariantShogi) gameInfo.variant = v;
12809 if( appData.autoDisplayTags ) {
12810 tags = PGNTags(&gameInfo);
12811 TagsPopUp(tags, CmailMsg());
12816 /* Make something up, but don't display it now */
12821 if (cm == PositionDiagram) {
12824 Board initial_position;
12826 if (appData.debugMode)
12827 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12829 if (!startedFromSetupPosition) {
12831 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12832 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12843 initial_position[i][j++] = CharToPiece(*p);
12846 while (*p == ' ' || *p == '\t' ||
12847 *p == '\n' || *p == '\r') p++;
12849 if (strncmp(p, "black", strlen("black"))==0)
12850 blackPlaysFirst = TRUE;
12852 blackPlaysFirst = FALSE;
12853 startedFromSetupPosition = TRUE;
12855 CopyBoard(boards[0], initial_position);
12856 if (blackPlaysFirst) {
12857 currentMove = forwardMostMove = backwardMostMove = 1;
12858 CopyBoard(boards[1], initial_position);
12859 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12860 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12861 timeRemaining[0][1] = whiteTimeRemaining;
12862 timeRemaining[1][1] = blackTimeRemaining;
12863 if (commentList[0] != NULL) {
12864 commentList[1] = commentList[0];
12865 commentList[0] = NULL;
12868 currentMove = forwardMostMove = backwardMostMove = 0;
12871 yyboardindex = forwardMostMove;
12872 cm = (ChessMove) Myylex();
12875 if(!creatingBook) {
12876 if (first.pr == NoProc) {
12877 StartChessProgram(&first);
12879 InitChessProgram(&first, FALSE);
12880 SendToProgram("force\n", &first);
12881 if (startedFromSetupPosition) {
12882 SendBoard(&first, forwardMostMove);
12883 if (appData.debugMode) {
12884 fprintf(debugFP, "Load Game\n");
12886 DisplayBothClocks();
12890 /* [HGM] server: flag to write setup moves in broadcast file as one */
12891 loadFlag = appData.suppressLoadMoves;
12893 while (cm == Comment) {
12895 if (appData.debugMode)
12896 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12898 AppendComment(currentMove, p, FALSE);
12899 yyboardindex = forwardMostMove;
12900 cm = (ChessMove) Myylex();
12903 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12904 cm == WhiteWins || cm == BlackWins ||
12905 cm == GameIsDrawn || cm == GameUnfinished) {
12906 DisplayMessage("", _("No moves in game"));
12907 if (cmailMsgLoaded) {
12908 if (appData.debugMode)
12909 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12913 DrawPosition(FALSE, boards[currentMove]);
12914 DisplayBothClocks();
12915 gameMode = EditGame;
12922 // [HGM] PV info: routine tests if comment empty
12923 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12924 DisplayComment(currentMove - 1, commentList[currentMove]);
12926 if (!matchMode && appData.timeDelay != 0)
12927 DrawPosition(FALSE, boards[currentMove]);
12929 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12930 programStats.ok_to_send = 1;
12933 /* if the first token after the PGN tags is a move
12934 * and not move number 1, retrieve it from the parser
12936 if (cm != MoveNumberOne)
12937 LoadGameOneMove(cm);
12939 /* load the remaining moves from the file */
12940 while (LoadGameOneMove(EndOfFile)) {
12941 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12942 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12945 /* rewind to the start of the game */
12946 currentMove = backwardMostMove;
12948 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12950 if (oldGameMode == AnalyzeFile) {
12951 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12952 AnalyzeFileEvent();
12954 if (oldGameMode == AnalyzeMode) {
12955 AnalyzeFileEvent();
12958 if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12959 long int w, b; // [HGM] adjourn: restore saved clock times
12960 char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12961 if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12962 timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12963 timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12967 if(creatingBook) return TRUE;
12968 if (!matchMode && pos > 0) {
12969 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12971 if (matchMode || appData.timeDelay == 0) {
12973 } else if (appData.timeDelay > 0) {
12974 AutoPlayGameLoop();
12977 if (appData.debugMode)
12978 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12980 loadFlag = 0; /* [HGM] true game starts */
12984 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12986 ReloadPosition (int offset)
12988 int positionNumber = lastLoadPositionNumber + offset;
12989 if (lastLoadPositionFP == NULL) {
12990 DisplayError(_("No position has been loaded yet"), 0);
12993 if (positionNumber <= 0) {
12994 DisplayError(_("Can't back up any further"), 0);
12997 return LoadPosition(lastLoadPositionFP, positionNumber,
12998 lastLoadPositionTitle);
13001 /* Load the nth position from the given file */
13003 LoadPositionFromFile (char *filename, int n, char *title)
13008 if (strcmp(filename, "-") == 0) {
13009 return LoadPosition(stdin, n, "stdin");
13011 f = fopen(filename, "rb");
13013 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13014 DisplayError(buf, errno);
13017 return LoadPosition(f, n, title);
13022 /* Load the nth position from the given open file, and close it */
13024 LoadPosition (FILE *f, int positionNumber, char *title)
13026 char *p, line[MSG_SIZ];
13027 Board initial_position;
13028 int i, j, fenMode, pn;
13030 if (gameMode == Training )
13031 SetTrainingModeOff();
13033 if (gameMode != BeginningOfGame) {
13034 Reset(FALSE, TRUE);
13036 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13037 fclose(lastLoadPositionFP);
13039 if (positionNumber == 0) positionNumber = 1;
13040 lastLoadPositionFP = f;
13041 lastLoadPositionNumber = positionNumber;
13042 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13043 if (first.pr == NoProc && !appData.noChessProgram) {
13044 StartChessProgram(&first);
13045 InitChessProgram(&first, FALSE);
13047 pn = positionNumber;
13048 if (positionNumber < 0) {
13049 /* Negative position number means to seek to that byte offset */
13050 if (fseek(f, -positionNumber, 0) == -1) {
13051 DisplayError(_("Can't seek on position file"), 0);
13056 if (fseek(f, 0, 0) == -1) {
13057 if (f == lastLoadPositionFP ?
13058 positionNumber == lastLoadPositionNumber + 1 :
13059 positionNumber == 1) {
13062 DisplayError(_("Can't seek on position file"), 0);
13067 /* See if this file is FEN or old-style xboard */
13068 if (fgets(line, MSG_SIZ, f) == NULL) {
13069 DisplayError(_("Position not found in file"), 0);
13072 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13073 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13076 if (fenMode || line[0] == '#') pn--;
13078 /* skip positions before number pn */
13079 if (fgets(line, MSG_SIZ, f) == NULL) {
13081 DisplayError(_("Position not found in file"), 0);
13084 if (fenMode || line[0] == '#') pn--;
13089 if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13090 DisplayError(_("Bad FEN position in file"), 0);
13094 (void) fgets(line, MSG_SIZ, f);
13095 (void) fgets(line, MSG_SIZ, f);
13097 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13098 (void) fgets(line, MSG_SIZ, f);
13099 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13102 initial_position[i][j++] = CharToPiece(*p);
13106 blackPlaysFirst = FALSE;
13108 (void) fgets(line, MSG_SIZ, f);
13109 if (strncmp(line, "black", strlen("black"))==0)
13110 blackPlaysFirst = TRUE;
13113 startedFromSetupPosition = TRUE;
13115 CopyBoard(boards[0], initial_position);
13116 if (blackPlaysFirst) {
13117 currentMove = forwardMostMove = backwardMostMove = 1;
13118 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13119 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13120 CopyBoard(boards[1], initial_position);
13121 DisplayMessage("", _("Black to play"));
13123 currentMove = forwardMostMove = backwardMostMove = 0;
13124 DisplayMessage("", _("White to play"));
13126 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13127 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13128 SendToProgram("force\n", &first);
13129 SendBoard(&first, forwardMostMove);
13131 if (appData.debugMode) {
13133 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13134 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13135 fprintf(debugFP, "Load Position\n");
13138 if (positionNumber > 1) {
13139 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13140 DisplayTitle(line);
13142 DisplayTitle(title);
13144 gameMode = EditGame;
13147 timeRemaining[0][1] = whiteTimeRemaining;
13148 timeRemaining[1][1] = blackTimeRemaining;
13149 DrawPosition(FALSE, boards[currentMove]);
13156 CopyPlayerNameIntoFileName (char **dest, char *src)
13158 while (*src != NULLCHAR && *src != ',') {
13163 *(*dest)++ = *src++;
13169 DefaultFileName (char *ext)
13171 static char def[MSG_SIZ];
13174 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13176 CopyPlayerNameIntoFileName(&p, gameInfo.white);
13178 CopyPlayerNameIntoFileName(&p, gameInfo.black);
13180 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13187 /* Save the current game to the given file */
13189 SaveGameToFile (char *filename, int append)
13193 int result, i, t,tot=0;
13195 if (strcmp(filename, "-") == 0) {
13196 return SaveGame(stdout, 0, NULL);
13198 for(i=0; i<10; i++) { // upto 10 tries
13199 f = fopen(filename, append ? "a" : "w");
13200 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13201 if(f || errno != 13) break;
13202 DoSleep(t = 5 + random()%11); // wait 5-15 msec
13206 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13207 DisplayError(buf, errno);
13210 safeStrCpy(buf, lastMsg, MSG_SIZ);
13211 DisplayMessage(_("Waiting for access to save file"), "");
13212 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13213 DisplayMessage(_("Saving game"), "");
13214 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
13215 result = SaveGame(f, 0, NULL);
13216 DisplayMessage(buf, "");
13223 SavePart (char *str)
13225 static char buf[MSG_SIZ];
13228 p = strchr(str, ' ');
13229 if (p == NULL) return str;
13230 strncpy(buf, str, p - str);
13231 buf[p - str] = NULLCHAR;
13235 #define PGN_MAX_LINE 75
13237 #define PGN_SIDE_WHITE 0
13238 #define PGN_SIDE_BLACK 1
13241 FindFirstMoveOutOfBook (int side)
13245 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13246 int index = backwardMostMove;
13247 int has_book_hit = 0;
13249 if( (index % 2) != side ) {
13253 while( index < forwardMostMove ) {
13254 /* Check to see if engine is in book */
13255 int depth = pvInfoList[index].depth;
13256 int score = pvInfoList[index].score;
13262 else if( score == 0 && depth == 63 ) {
13263 in_book = 1; /* Zappa */
13265 else if( score == 2 && depth == 99 ) {
13266 in_book = 1; /* Abrok */
13269 has_book_hit += in_book;
13285 GetOutOfBookInfo (char * buf)
13289 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13291 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13292 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13296 if( oob[0] >= 0 || oob[1] >= 0 ) {
13297 for( i=0; i<2; i++ ) {
13301 if( i > 0 && oob[0] >= 0 ) {
13302 strcat( buf, " " );
13305 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13306 sprintf( buf+strlen(buf), "%s%.2f",
13307 pvInfoList[idx].score >= 0 ? "+" : "",
13308 pvInfoList[idx].score / 100.0 );
13314 /* Save game in PGN style and close the file */
13316 SaveGamePGN (FILE *f)
13318 int i, offset, linelen, newblock;
13321 int movelen, numlen, blank;
13322 char move_buffer[100]; /* [AS] Buffer for move+PV info */
13324 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13326 PrintPGNTags(f, &gameInfo);
13328 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13330 if (backwardMostMove > 0 || startedFromSetupPosition) {
13331 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13332 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13333 fprintf(f, "\n{--------------\n");
13334 PrintPosition(f, backwardMostMove);
13335 fprintf(f, "--------------}\n");
13339 /* [AS] Out of book annotation */
13340 if( appData.saveOutOfBookInfo ) {
13343 GetOutOfBookInfo( buf );
13345 if( buf[0] != '\0' ) {
13346 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13353 i = backwardMostMove;
13357 while (i < forwardMostMove) {
13358 /* Print comments preceding this move */
13359 if (commentList[i] != NULL) {
13360 if (linelen > 0) fprintf(f, "\n");
13361 fprintf(f, "%s", commentList[i]);
13366 /* Format move number */
13368 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13371 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13373 numtext[0] = NULLCHAR;
13375 numlen = strlen(numtext);
13378 /* Print move number */
13379 blank = linelen > 0 && numlen > 0;
13380 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13389 fprintf(f, "%s", numtext);
13393 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13394 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13397 blank = linelen > 0 && movelen > 0;
13398 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13407 fprintf(f, "%s", move_buffer);
13408 linelen += movelen;
13410 /* [AS] Add PV info if present */
13411 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13412 /* [HGM] add time */
13413 char buf[MSG_SIZ]; int seconds;
13415 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13421 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13424 seconds = (seconds + 4)/10; // round to full seconds
13426 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13428 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13431 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13432 pvInfoList[i].score >= 0 ? "+" : "",
13433 pvInfoList[i].score / 100.0,
13434 pvInfoList[i].depth,
13437 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13439 /* Print score/depth */
13440 blank = linelen > 0 && movelen > 0;
13441 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13450 fprintf(f, "%s", move_buffer);
13451 linelen += movelen;
13457 /* Start a new line */
13458 if (linelen > 0) fprintf(f, "\n");
13460 /* Print comments after last move */
13461 if (commentList[i] != NULL) {
13462 fprintf(f, "%s\n", commentList[i]);
13466 if (gameInfo.resultDetails != NULL &&
13467 gameInfo.resultDetails[0] != NULLCHAR) {
13468 char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13469 if(gameInfo.result == GameUnfinished && appData.clockMode &&
13470 (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13471 snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13472 fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13474 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13478 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13482 /* Save game in old style and close the file */
13484 SaveGameOldStyle (FILE *f)
13489 tm = time((time_t *) NULL);
13491 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13494 if (backwardMostMove > 0 || startedFromSetupPosition) {
13495 fprintf(f, "\n[--------------\n");
13496 PrintPosition(f, backwardMostMove);
13497 fprintf(f, "--------------]\n");
13502 i = backwardMostMove;
13503 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13505 while (i < forwardMostMove) {
13506 if (commentList[i] != NULL) {
13507 fprintf(f, "[%s]\n", commentList[i]);
13510 if ((i % 2) == 1) {
13511 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
13514 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
13516 if (commentList[i] != NULL) {
13520 if (i >= forwardMostMove) {
13524 fprintf(f, "%s\n", parseList[i]);
13529 if (commentList[i] != NULL) {
13530 fprintf(f, "[%s]\n", commentList[i]);
13533 /* This isn't really the old style, but it's close enough */
13534 if (gameInfo.resultDetails != NULL &&
13535 gameInfo.resultDetails[0] != NULLCHAR) {
13536 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13537 gameInfo.resultDetails);
13539 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13546 /* Save the current game to open file f and close the file */
13548 SaveGame (FILE *f, int dummy, char *dummy2)
13550 if (gameMode == EditPosition) EditPositionDone(TRUE);
13551 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13552 if (appData.oldSaveStyle)
13553 return SaveGameOldStyle(f);
13555 return SaveGamePGN(f);
13558 /* Save the current position to the given file */
13560 SavePositionToFile (char *filename)
13565 if (strcmp(filename, "-") == 0) {
13566 return SavePosition(stdout, 0, NULL);
13568 f = fopen(filename, "a");
13570 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13571 DisplayError(buf, errno);
13574 safeStrCpy(buf, lastMsg, MSG_SIZ);
13575 DisplayMessage(_("Waiting for access to save file"), "");
13576 flock(fileno(f), LOCK_EX); // [HGM] lock
13577 DisplayMessage(_("Saving position"), "");
13578 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
13579 SavePosition(f, 0, NULL);
13580 DisplayMessage(buf, "");
13586 /* Save the current position to the given open file and close the file */
13588 SavePosition (FILE *f, int dummy, char *dummy2)
13593 if (gameMode == EditPosition) EditPositionDone(TRUE);
13594 if (appData.oldSaveStyle) {
13595 tm = time((time_t *) NULL);
13597 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13599 fprintf(f, "[--------------\n");
13600 PrintPosition(f, currentMove);
13601 fprintf(f, "--------------]\n");
13603 fen = PositionToFEN(currentMove, NULL, 1);
13604 fprintf(f, "%s\n", fen);
13612 ReloadCmailMsgEvent (int unregister)
13615 static char *inFilename = NULL;
13616 static char *outFilename;
13618 struct stat inbuf, outbuf;
13621 /* Any registered moves are unregistered if unregister is set, */
13622 /* i.e. invoked by the signal handler */
13624 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13625 cmailMoveRegistered[i] = FALSE;
13626 if (cmailCommentList[i] != NULL) {
13627 free(cmailCommentList[i]);
13628 cmailCommentList[i] = NULL;
13631 nCmailMovesRegistered = 0;
13634 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13635 cmailResult[i] = CMAIL_NOT_RESULT;
13639 if (inFilename == NULL) {
13640 /* Because the filenames are static they only get malloced once */
13641 /* and they never get freed */
13642 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13643 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13645 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13646 sprintf(outFilename, "%s.out", appData.cmailGameName);
13649 status = stat(outFilename, &outbuf);
13651 cmailMailedMove = FALSE;
13653 status = stat(inFilename, &inbuf);
13654 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13657 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13658 counts the games, notes how each one terminated, etc.
13660 It would be nice to remove this kludge and instead gather all
13661 the information while building the game list. (And to keep it
13662 in the game list nodes instead of having a bunch of fixed-size
13663 parallel arrays.) Note this will require getting each game's
13664 termination from the PGN tags, as the game list builder does
13665 not process the game moves. --mann
13667 cmailMsgLoaded = TRUE;
13668 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13670 /* Load first game in the file or popup game menu */
13671 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13673 #endif /* !WIN32 */
13681 char string[MSG_SIZ];
13683 if ( cmailMailedMove
13684 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13685 return TRUE; /* Allow free viewing */
13688 /* Unregister move to ensure that we don't leave RegisterMove */
13689 /* with the move registered when the conditions for registering no */
13691 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13692 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13693 nCmailMovesRegistered --;
13695 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13697 free(cmailCommentList[lastLoadGameNumber - 1]);
13698 cmailCommentList[lastLoadGameNumber - 1] = NULL;
13702 if (cmailOldMove == -1) {
13703 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13707 if (currentMove > cmailOldMove + 1) {
13708 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13712 if (currentMove < cmailOldMove) {
13713 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13717 if (forwardMostMove > currentMove) {
13718 /* Silently truncate extra moves */
13722 if ( (currentMove == cmailOldMove + 1)
13723 || ( (currentMove == cmailOldMove)
13724 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13725 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13726 if (gameInfo.result != GameUnfinished) {
13727 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13730 if (commentList[currentMove] != NULL) {
13731 cmailCommentList[lastLoadGameNumber - 1]
13732 = StrSave(commentList[currentMove]);
13734 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13736 if (appData.debugMode)
13737 fprintf(debugFP, "Saving %s for game %d\n",
13738 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13740 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13742 f = fopen(string, "w");
13743 if (appData.oldSaveStyle) {
13744 SaveGameOldStyle(f); /* also closes the file */
13746 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13747 f = fopen(string, "w");
13748 SavePosition(f, 0, NULL); /* also closes the file */
13750 fprintf(f, "{--------------\n");
13751 PrintPosition(f, currentMove);
13752 fprintf(f, "--------------}\n\n");
13754 SaveGame(f, 0, NULL); /* also closes the file*/
13757 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13758 nCmailMovesRegistered ++;
13759 } else if (nCmailGames == 1) {
13760 DisplayError(_("You have not made a move yet"), 0);
13771 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13772 FILE *commandOutput;
13773 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13774 int nBytes = 0; /* Suppress warnings on uninitialized variables */
13780 if (! cmailMsgLoaded) {
13781 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13785 if (nCmailGames == nCmailResults) {
13786 DisplayError(_("No unfinished games"), 0);
13790 #if CMAIL_PROHIBIT_REMAIL
13791 if (cmailMailedMove) {
13792 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);
13793 DisplayError(msg, 0);
13798 if (! (cmailMailedMove || RegisterMove())) return;
13800 if ( cmailMailedMove
13801 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13802 snprintf(string, MSG_SIZ, partCommandString,
13803 appData.debugMode ? " -v" : "", appData.cmailGameName);
13804 commandOutput = popen(string, "r");
13806 if (commandOutput == NULL) {
13807 DisplayError(_("Failed to invoke cmail"), 0);
13809 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13810 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13812 if (nBuffers > 1) {
13813 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13814 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13815 nBytes = MSG_SIZ - 1;
13817 (void) memcpy(msg, buffer, nBytes);
13819 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13821 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13822 cmailMailedMove = TRUE; /* Prevent >1 moves */
13825 for (i = 0; i < nCmailGames; i ++) {
13826 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13831 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13833 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13835 appData.cmailGameName,
13837 LoadGameFromFile(buffer, 1, buffer, FALSE);
13838 cmailMsgLoaded = FALSE;
13842 DisplayInformation(msg);
13843 pclose(commandOutput);
13846 if ((*cmailMsg) != '\0') {
13847 DisplayInformation(cmailMsg);
13852 #endif /* !WIN32 */
13861 int prependComma = 0;
13863 char string[MSG_SIZ]; /* Space for game-list */
13866 if (!cmailMsgLoaded) return "";
13868 if (cmailMailedMove) {
13869 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13871 /* Create a list of games left */
13872 snprintf(string, MSG_SIZ, "[");
13873 for (i = 0; i < nCmailGames; i ++) {
13874 if (! ( cmailMoveRegistered[i]
13875 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13876 if (prependComma) {
13877 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13879 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13883 strcat(string, number);
13886 strcat(string, "]");
13888 if (nCmailMovesRegistered + nCmailResults == 0) {
13889 switch (nCmailGames) {
13891 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13895 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13899 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13904 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13906 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13911 if (nCmailResults == nCmailGames) {
13912 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13914 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13919 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13931 if (gameMode == Training)
13932 SetTrainingModeOff();
13935 cmailMsgLoaded = FALSE;
13936 if (appData.icsActive) {
13937 SendToICS(ics_prefix);
13938 SendToICS("refresh\n");
13943 ExitEvent (int status)
13947 /* Give up on clean exit */
13951 /* Keep trying for clean exit */
13955 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13957 if (telnetISR != NULL) {
13958 RemoveInputSource(telnetISR);
13960 if (icsPR != NoProc) {
13961 DestroyChildProcess(icsPR, TRUE);
13964 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13965 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13967 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13968 /* make sure this other one finishes before killing it! */
13969 if(endingGame) { int count = 0;
13970 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13971 while(endingGame && count++ < 10) DoSleep(1);
13972 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13975 /* Kill off chess programs */
13976 if (first.pr != NoProc) {
13979 DoSleep( appData.delayBeforeQuit );
13980 SendToProgram("quit\n", &first);
13981 DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
13983 if (second.pr != NoProc) {
13984 DoSleep( appData.delayBeforeQuit );
13985 SendToProgram("quit\n", &second);
13986 DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
13988 if (first.isr != NULL) {
13989 RemoveInputSource(first.isr);
13991 if (second.isr != NULL) {
13992 RemoveInputSource(second.isr);
13995 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13996 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13998 ShutDownFrontEnd();
14003 PauseEngine (ChessProgramState *cps)
14005 SendToProgram("pause\n", cps);
14010 UnPauseEngine (ChessProgramState *cps)
14012 SendToProgram("resume\n", cps);
14019 if (appData.debugMode)
14020 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14024 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14026 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14027 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14028 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14030 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14031 HandleMachineMove(stashedInputMove, stalledEngine);
14032 stalledEngine = NULL;
14035 if (gameMode == MachinePlaysWhite ||
14036 gameMode == TwoMachinesPlay ||
14037 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14038 if(first.pause) UnPauseEngine(&first);
14039 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14040 if(second.pause) UnPauseEngine(&second);
14041 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14044 DisplayBothClocks();
14046 if (gameMode == PlayFromGameFile) {
14047 if (appData.timeDelay >= 0)
14048 AutoPlayGameLoop();
14049 } else if (gameMode == IcsExamining && pauseExamInvalid) {
14050 Reset(FALSE, TRUE);
14051 SendToICS(ics_prefix);
14052 SendToICS("refresh\n");
14053 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14054 ForwardInner(forwardMostMove);
14056 pauseExamInvalid = FALSE;
14058 switch (gameMode) {
14062 pauseExamForwardMostMove = forwardMostMove;
14063 pauseExamInvalid = FALSE;
14066 case IcsPlayingWhite:
14067 case IcsPlayingBlack:
14071 case PlayFromGameFile:
14072 (void) StopLoadGameTimer();
14076 case BeginningOfGame:
14077 if (appData.icsActive) return;
14078 /* else fall through */
14079 case MachinePlaysWhite:
14080 case MachinePlaysBlack:
14081 case TwoMachinesPlay:
14082 if (forwardMostMove == 0)
14083 return; /* don't pause if no one has moved */
14084 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14085 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14086 if(onMove->pause) { // thinking engine can be paused
14087 PauseEngine(onMove); // do it
14088 if(onMove->other->pause) // pondering opponent can always be paused immediately
14089 PauseEngine(onMove->other);
14091 SendToProgram("easy\n", onMove->other);
14093 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14094 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14096 PauseEngine(&first);
14098 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14099 } else { // human on move, pause pondering by either method
14101 PauseEngine(&first);
14102 else if(appData.ponderNextMove)
14103 SendToProgram("easy\n", &first);
14106 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14116 EditCommentEvent ()
14118 char title[MSG_SIZ];
14120 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14121 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14123 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14124 WhiteOnMove(currentMove - 1) ? " " : ".. ",
14125 parseList[currentMove - 1]);
14128 EditCommentPopUp(currentMove, title, commentList[currentMove]);
14135 char *tags = PGNTags(&gameInfo);
14137 EditTagsPopUp(tags, NULL);
14144 if(second.analyzing) {
14145 SendToProgram("exit\n", &second);
14146 second.analyzing = FALSE;
14148 if (second.pr == NoProc) StartChessProgram(&second);
14149 InitChessProgram(&second, FALSE);
14150 FeedMovesToProgram(&second, currentMove);
14152 SendToProgram("analyze\n", &second);
14153 second.analyzing = TRUE;
14157 /* Toggle ShowThinking */
14159 ToggleShowThinking()
14161 appData.showThinking = !appData.showThinking;
14162 ShowThinkingEvent();
14166 AnalyzeModeEvent ()
14170 if (!first.analysisSupport) {
14171 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14172 DisplayError(buf, 0);
14175 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14176 if (appData.icsActive) {
14177 if (gameMode != IcsObserving) {
14178 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14179 DisplayError(buf, 0);
14181 if (appData.icsEngineAnalyze) {
14182 if (appData.debugMode)
14183 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14189 /* if enable, user wants to disable icsEngineAnalyze */
14190 if (appData.icsEngineAnalyze) {
14195 appData.icsEngineAnalyze = TRUE;
14196 if (appData.debugMode)
14197 fprintf(debugFP, "ICS engine analyze starting... \n");
14200 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14201 if (appData.noChessProgram || gameMode == AnalyzeMode)
14204 if (gameMode != AnalyzeFile) {
14205 if (!appData.icsEngineAnalyze) {
14207 if (gameMode != EditGame) return 0;
14209 if (!appData.showThinking) ToggleShowThinking();
14210 ResurrectChessProgram();
14211 SendToProgram("analyze\n", &first);
14212 first.analyzing = TRUE;
14213 /*first.maybeThinking = TRUE;*/
14214 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14215 EngineOutputPopUp();
14217 if (!appData.icsEngineAnalyze) {
14218 gameMode = AnalyzeMode;
14219 ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14225 StartAnalysisClock();
14226 GetTimeMark(&lastNodeCountTime);
14232 AnalyzeFileEvent ()
14234 if (appData.noChessProgram || gameMode == AnalyzeFile)
14237 if (!first.analysisSupport) {
14239 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14240 DisplayError(buf, 0);
14244 if (gameMode != AnalyzeMode) {
14245 keepInfo = 1; // mere annotating should not alter PGN tags
14248 if (gameMode != EditGame) return;
14249 if (!appData.showThinking) ToggleShowThinking();
14250 ResurrectChessProgram();
14251 SendToProgram("analyze\n", &first);
14252 first.analyzing = TRUE;
14253 /*first.maybeThinking = TRUE;*/
14254 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14255 EngineOutputPopUp();
14257 gameMode = AnalyzeFile;
14261 StartAnalysisClock();
14262 GetTimeMark(&lastNodeCountTime);
14264 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14265 AnalysisPeriodicEvent(1);
14269 MachineWhiteEvent ()
14272 char *bookHit = NULL;
14274 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14278 if (gameMode == PlayFromGameFile ||
14279 gameMode == TwoMachinesPlay ||
14280 gameMode == Training ||
14281 gameMode == AnalyzeMode ||
14282 gameMode == EndOfGame)
14285 if (gameMode == EditPosition)
14286 EditPositionDone(TRUE);
14288 if (!WhiteOnMove(currentMove)) {
14289 DisplayError(_("It is not White's turn"), 0);
14293 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14296 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14297 gameMode == AnalyzeFile)
14300 ResurrectChessProgram(); /* in case it isn't running */
14301 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14302 gameMode = MachinePlaysWhite;
14305 gameMode = MachinePlaysWhite;
14309 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14311 if (first.sendName) {
14312 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14313 SendToProgram(buf, &first);
14315 if (first.sendTime) {
14316 if (first.useColors) {
14317 SendToProgram("black\n", &first); /*gnu kludge*/
14319 SendTimeRemaining(&first, TRUE);
14321 if (first.useColors) {
14322 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14324 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14325 SetMachineThinkingEnables();
14326 first.maybeThinking = TRUE;
14330 if (appData.autoFlipView && !flipView) {
14331 flipView = !flipView;
14332 DrawPosition(FALSE, NULL);
14333 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14336 if(bookHit) { // [HGM] book: simulate book reply
14337 static char bookMove[MSG_SIZ]; // a bit generous?
14339 programStats.nodes = programStats.depth = programStats.time =
14340 programStats.score = programStats.got_only_move = 0;
14341 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14343 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14344 strcat(bookMove, bookHit);
14345 HandleMachineMove(bookMove, &first);
14350 MachineBlackEvent ()
14353 char *bookHit = NULL;
14355 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14359 if (gameMode == PlayFromGameFile ||
14360 gameMode == TwoMachinesPlay ||
14361 gameMode == Training ||
14362 gameMode == AnalyzeMode ||
14363 gameMode == EndOfGame)
14366 if (gameMode == EditPosition)
14367 EditPositionDone(TRUE);
14369 if (WhiteOnMove(currentMove)) {
14370 DisplayError(_("It is not Black's turn"), 0);
14374 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14377 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14378 gameMode == AnalyzeFile)
14381 ResurrectChessProgram(); /* in case it isn't running */
14382 gameMode = MachinePlaysBlack;
14386 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14388 if (first.sendName) {
14389 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14390 SendToProgram(buf, &first);
14392 if (first.sendTime) {
14393 if (first.useColors) {
14394 SendToProgram("white\n", &first); /*gnu kludge*/
14396 SendTimeRemaining(&first, FALSE);
14398 if (first.useColors) {
14399 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14401 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14402 SetMachineThinkingEnables();
14403 first.maybeThinking = TRUE;
14406 if (appData.autoFlipView && flipView) {
14407 flipView = !flipView;
14408 DrawPosition(FALSE, NULL);
14409 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14411 if(bookHit) { // [HGM] book: simulate book reply
14412 static char bookMove[MSG_SIZ]; // a bit generous?
14414 programStats.nodes = programStats.depth = programStats.time =
14415 programStats.score = programStats.got_only_move = 0;
14416 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14418 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14419 strcat(bookMove, bookHit);
14420 HandleMachineMove(bookMove, &first);
14426 DisplayTwoMachinesTitle ()
14429 if (appData.matchGames > 0) {
14430 if(appData.tourneyFile[0]) {
14431 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14432 gameInfo.white, _("vs."), gameInfo.black,
14433 nextGame+1, appData.matchGames+1,
14434 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14436 if (first.twoMachinesColor[0] == 'w') {
14437 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14438 gameInfo.white, _("vs."), gameInfo.black,
14439 first.matchWins, second.matchWins,
14440 matchGame - 1 - (first.matchWins + second.matchWins));
14442 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14443 gameInfo.white, _("vs."), gameInfo.black,
14444 second.matchWins, first.matchWins,
14445 matchGame - 1 - (first.matchWins + second.matchWins));
14448 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14454 SettingsMenuIfReady ()
14456 if (second.lastPing != second.lastPong) {
14457 DisplayMessage("", _("Waiting for second chess program"));
14458 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14462 DisplayMessage("", "");
14463 SettingsPopUp(&second);
14467 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14470 if (cps->pr == NoProc) {
14471 StartChessProgram(cps);
14472 if (cps->protocolVersion == 1) {
14474 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14476 /* kludge: allow timeout for initial "feature" command */
14477 if(retry != TwoMachinesEventIfReady) FreezeUI();
14478 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14479 DisplayMessage("", buf);
14480 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14488 TwoMachinesEvent P((void))
14492 ChessProgramState *onmove;
14493 char *bookHit = NULL;
14494 static int stalling = 0;
14498 if (appData.noChessProgram) return;
14500 switch (gameMode) {
14501 case TwoMachinesPlay:
14503 case MachinePlaysWhite:
14504 case MachinePlaysBlack:
14505 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14506 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14510 case BeginningOfGame:
14511 case PlayFromGameFile:
14514 if (gameMode != EditGame) return;
14517 EditPositionDone(TRUE);
14528 // forwardMostMove = currentMove;
14529 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14530 startingEngine = TRUE;
14532 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14534 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14535 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14536 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14539 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14541 if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14542 gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14543 startingEngine = FALSE;
14544 DisplayError("second engine does not play this", 0);
14549 InitChessProgram(&second, FALSE); // unbalances ping of second engine
14550 SendToProgram("force\n", &second);
14552 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14555 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14556 if(appData.matchPause>10000 || appData.matchPause<10)
14557 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14558 wait = SubtractTimeMarks(&now, &pauseStart);
14559 if(wait < appData.matchPause) {
14560 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14563 // we are now committed to starting the game
14565 DisplayMessage("", "");
14566 if (startedFromSetupPosition) {
14567 SendBoard(&second, backwardMostMove);
14568 if (appData.debugMode) {
14569 fprintf(debugFP, "Two Machines\n");
14572 for (i = backwardMostMove; i < forwardMostMove; i++) {
14573 SendMoveToProgram(i, &second);
14576 gameMode = TwoMachinesPlay;
14577 pausing = startingEngine = FALSE;
14578 ModeHighlight(); // [HGM] logo: this triggers display update of logos
14580 DisplayTwoMachinesTitle();
14582 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14587 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14588 SendToProgram(first.computerString, &first);
14589 if (first.sendName) {
14590 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14591 SendToProgram(buf, &first);
14593 SendToProgram(second.computerString, &second);
14594 if (second.sendName) {
14595 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14596 SendToProgram(buf, &second);
14600 if (!first.sendTime || !second.sendTime) {
14601 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14602 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14604 if (onmove->sendTime) {
14605 if (onmove->useColors) {
14606 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14608 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14610 if (onmove->useColors) {
14611 SendToProgram(onmove->twoMachinesColor, onmove);
14613 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14614 // SendToProgram("go\n", onmove);
14615 onmove->maybeThinking = TRUE;
14616 SetMachineThinkingEnables();
14620 if(bookHit) { // [HGM] book: simulate book reply
14621 static char bookMove[MSG_SIZ]; // a bit generous?
14623 programStats.nodes = programStats.depth = programStats.time =
14624 programStats.score = programStats.got_only_move = 0;
14625 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14627 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14628 strcat(bookMove, bookHit);
14629 savedMessage = bookMove; // args for deferred call
14630 savedState = onmove;
14631 ScheduleDelayedEvent(DeferredBookMove, 1);
14638 if (gameMode == Training) {
14639 SetTrainingModeOff();
14640 gameMode = PlayFromGameFile;
14641 DisplayMessage("", _("Training mode off"));
14643 gameMode = Training;
14644 animateTraining = appData.animate;
14646 /* make sure we are not already at the end of the game */
14647 if (currentMove < forwardMostMove) {
14648 SetTrainingModeOn();
14649 DisplayMessage("", _("Training mode on"));
14651 gameMode = PlayFromGameFile;
14652 DisplayError(_("Already at end of game"), 0);
14661 if (!appData.icsActive) return;
14662 switch (gameMode) {
14663 case IcsPlayingWhite:
14664 case IcsPlayingBlack:
14667 case BeginningOfGame:
14675 EditPositionDone(TRUE);
14688 gameMode = IcsIdle;
14698 switch (gameMode) {
14700 SetTrainingModeOff();
14702 case MachinePlaysWhite:
14703 case MachinePlaysBlack:
14704 case BeginningOfGame:
14705 SendToProgram("force\n", &first);
14706 SetUserThinkingEnables();
14708 case PlayFromGameFile:
14709 (void) StopLoadGameTimer();
14710 if (gameFileFP != NULL) {
14715 EditPositionDone(TRUE);
14720 SendToProgram("force\n", &first);
14722 case TwoMachinesPlay:
14723 GameEnds(EndOfFile, NULL, GE_PLAYER);
14724 ResurrectChessProgram();
14725 SetUserThinkingEnables();
14728 ResurrectChessProgram();
14730 case IcsPlayingBlack:
14731 case IcsPlayingWhite:
14732 DisplayError(_("Warning: You are still playing a game"), 0);
14735 DisplayError(_("Warning: You are still observing a game"), 0);
14738 DisplayError(_("Warning: You are still examining a game"), 0);
14749 first.offeredDraw = second.offeredDraw = 0;
14751 if (gameMode == PlayFromGameFile) {
14752 whiteTimeRemaining = timeRemaining[0][currentMove];
14753 blackTimeRemaining = timeRemaining[1][currentMove];
14757 if (gameMode == MachinePlaysWhite ||
14758 gameMode == MachinePlaysBlack ||
14759 gameMode == TwoMachinesPlay ||
14760 gameMode == EndOfGame) {
14761 i = forwardMostMove;
14762 while (i > currentMove) {
14763 SendToProgram("undo\n", &first);
14766 if(!adjustedClock) {
14767 whiteTimeRemaining = timeRemaining[0][currentMove];
14768 blackTimeRemaining = timeRemaining[1][currentMove];
14769 DisplayBothClocks();
14771 if (whiteFlag || blackFlag) {
14772 whiteFlag = blackFlag = 0;
14777 gameMode = EditGame;
14784 EditPositionEvent ()
14786 if (gameMode == EditPosition) {
14792 if (gameMode != EditGame) return;
14794 gameMode = EditPosition;
14797 if (currentMove > 0)
14798 CopyBoard(boards[0], boards[currentMove]);
14800 blackPlaysFirst = !WhiteOnMove(currentMove);
14802 currentMove = forwardMostMove = backwardMostMove = 0;
14803 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14805 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14811 /* [DM] icsEngineAnalyze - possible call from other functions */
14812 if (appData.icsEngineAnalyze) {
14813 appData.icsEngineAnalyze = FALSE;
14815 DisplayMessage("",_("Close ICS engine analyze..."));
14817 if (first.analysisSupport && first.analyzing) {
14818 SendToBoth("exit\n");
14819 first.analyzing = second.analyzing = FALSE;
14821 thinkOutput[0] = NULLCHAR;
14825 EditPositionDone (Boolean fakeRights)
14827 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14829 startedFromSetupPosition = TRUE;
14830 InitChessProgram(&first, FALSE);
14831 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14832 boards[0][EP_STATUS] = EP_NONE;
14833 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14834 if(boards[0][0][BOARD_WIDTH>>1] == king) {
14835 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14836 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14837 } else boards[0][CASTLING][2] = NoRights;
14838 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14839 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14840 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14841 } else boards[0][CASTLING][5] = NoRights;
14842 if(gameInfo.variant == VariantSChess) {
14844 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14845 boards[0][VIRGIN][i] = 0;
14846 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14847 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14851 SendToProgram("force\n", &first);
14852 if (blackPlaysFirst) {
14853 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14854 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14855 currentMove = forwardMostMove = backwardMostMove = 1;
14856 CopyBoard(boards[1], boards[0]);
14858 currentMove = forwardMostMove = backwardMostMove = 0;
14860 SendBoard(&first, forwardMostMove);
14861 if (appData.debugMode) {
14862 fprintf(debugFP, "EditPosDone\n");
14865 DisplayMessage("", "");
14866 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14867 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14868 gameMode = EditGame;
14870 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14871 ClearHighlights(); /* [AS] */
14874 /* Pause for `ms' milliseconds */
14875 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14877 TimeDelay (long ms)
14884 } while (SubtractTimeMarks(&m2, &m1) < ms);
14887 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14889 SendMultiLineToICS (char *buf)
14891 char temp[MSG_SIZ+1], *p;
14898 strncpy(temp, buf, len);
14903 if (*p == '\n' || *p == '\r')
14908 strcat(temp, "\n");
14910 SendToPlayer(temp, strlen(temp));
14914 SetWhiteToPlayEvent ()
14916 if (gameMode == EditPosition) {
14917 blackPlaysFirst = FALSE;
14918 DisplayBothClocks(); /* works because currentMove is 0 */
14919 } else if (gameMode == IcsExamining) {
14920 SendToICS(ics_prefix);
14921 SendToICS("tomove white\n");
14926 SetBlackToPlayEvent ()
14928 if (gameMode == EditPosition) {
14929 blackPlaysFirst = TRUE;
14930 currentMove = 1; /* kludge */
14931 DisplayBothClocks();
14933 } else if (gameMode == IcsExamining) {
14934 SendToICS(ics_prefix);
14935 SendToICS("tomove black\n");
14940 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14943 ChessSquare piece = boards[0][y][x];
14944 static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14945 static int lastVariant;
14947 if (gameMode != EditPosition && gameMode != IcsExamining) return;
14949 switch (selection) {
14951 CopyBoard(currentBoard, boards[0]);
14952 CopyBoard(menuBoard, initialPosition);
14953 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14954 SendToICS(ics_prefix);
14955 SendToICS("bsetup clear\n");
14956 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14957 SendToICS(ics_prefix);
14958 SendToICS("clearboard\n");
14961 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14962 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14963 for (y = 0; y < BOARD_HEIGHT; y++) {
14964 if (gameMode == IcsExamining) {
14965 if (boards[currentMove][y][x] != EmptySquare) {
14966 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14971 if(boards[0][y][x] != p) nonEmpty++;
14972 boards[0][y][x] = p;
14975 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14977 if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14978 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
14979 ChessSquare p = menuBoard[0][x];
14980 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14981 p = menuBoard[BOARD_HEIGHT-1][x];
14982 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14984 DisplayMessage("Clicking clock again restores position", "");
14985 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14986 if(!nonEmpty) { // asked to clear an empty board
14987 CopyBoard(boards[0], menuBoard);
14989 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14990 CopyBoard(boards[0], initialPosition);
14992 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14993 && !CompareBoards(nullBoard, erasedBoard)) {
14994 CopyBoard(boards[0], erasedBoard);
14996 CopyBoard(erasedBoard, currentBoard);
15000 if (gameMode == EditPosition) {
15001 DrawPosition(FALSE, boards[0]);
15006 SetWhiteToPlayEvent();
15010 SetBlackToPlayEvent();
15014 if (gameMode == IcsExamining) {
15015 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15016 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15019 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15020 if(x == BOARD_LEFT-2) {
15021 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15022 boards[0][y][1] = 0;
15024 if(x == BOARD_RGHT+1) {
15025 if(y >= gameInfo.holdingsSize) break;
15026 boards[0][y][BOARD_WIDTH-2] = 0;
15029 boards[0][y][x] = EmptySquare;
15030 DrawPosition(FALSE, boards[0]);
15035 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15036 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
15037 selection = (ChessSquare) (PROMOTED piece);
15038 } else if(piece == EmptySquare) selection = WhiteSilver;
15039 else selection = (ChessSquare)((int)piece - 1);
15043 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15044 piece > (int)BlackMan && piece <= (int)BlackKing ) {
15045 selection = (ChessSquare) (DEMOTED piece);
15046 } else if(piece == EmptySquare) selection = BlackSilver;
15047 else selection = (ChessSquare)((int)piece + 1);
15052 if(gameInfo.variant == VariantShatranj ||
15053 gameInfo.variant == VariantXiangqi ||
15054 gameInfo.variant == VariantCourier ||
15055 gameInfo.variant == VariantASEAN ||
15056 gameInfo.variant == VariantMakruk )
15057 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15062 if(gameInfo.variant == VariantXiangqi)
15063 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15064 if(gameInfo.variant == VariantKnightmate)
15065 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15068 if (gameMode == IcsExamining) {
15069 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15070 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15071 PieceToChar(selection), AAA + x, ONE + y);
15074 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15076 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15077 n = PieceToNumber(selection - BlackPawn);
15078 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15079 boards[0][BOARD_HEIGHT-1-n][0] = selection;
15080 boards[0][BOARD_HEIGHT-1-n][1]++;
15082 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15083 n = PieceToNumber(selection);
15084 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15085 boards[0][n][BOARD_WIDTH-1] = selection;
15086 boards[0][n][BOARD_WIDTH-2]++;
15089 boards[0][y][x] = selection;
15090 DrawPosition(TRUE, boards[0]);
15092 fromX = fromY = -1;
15100 DropMenuEvent (ChessSquare selection, int x, int y)
15102 ChessMove moveType;
15104 switch (gameMode) {
15105 case IcsPlayingWhite:
15106 case MachinePlaysBlack:
15107 if (!WhiteOnMove(currentMove)) {
15108 DisplayMoveError(_("It is Black's turn"));
15111 moveType = WhiteDrop;
15113 case IcsPlayingBlack:
15114 case MachinePlaysWhite:
15115 if (WhiteOnMove(currentMove)) {
15116 DisplayMoveError(_("It is White's turn"));
15119 moveType = BlackDrop;
15122 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15128 if (moveType == BlackDrop && selection < BlackPawn) {
15129 selection = (ChessSquare) ((int) selection
15130 + (int) BlackPawn - (int) WhitePawn);
15132 if (boards[currentMove][y][x] != EmptySquare) {
15133 DisplayMoveError(_("That square is occupied"));
15137 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15143 /* Accept a pending offer of any kind from opponent */
15145 if (appData.icsActive) {
15146 SendToICS(ics_prefix);
15147 SendToICS("accept\n");
15148 } else if (cmailMsgLoaded) {
15149 if (currentMove == cmailOldMove &&
15150 commentList[cmailOldMove] != NULL &&
15151 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15152 "Black offers a draw" : "White offers a draw")) {
15154 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15155 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15157 DisplayError(_("There is no pending offer on this move"), 0);
15158 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15161 /* Not used for offers from chess program */
15168 /* Decline a pending offer of any kind from opponent */
15170 if (appData.icsActive) {
15171 SendToICS(ics_prefix);
15172 SendToICS("decline\n");
15173 } else if (cmailMsgLoaded) {
15174 if (currentMove == cmailOldMove &&
15175 commentList[cmailOldMove] != NULL &&
15176 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15177 "Black offers a draw" : "White offers a draw")) {
15179 AppendComment(cmailOldMove, "Draw declined", TRUE);
15180 DisplayComment(cmailOldMove - 1, "Draw declined");
15183 DisplayError(_("There is no pending offer on this move"), 0);
15186 /* Not used for offers from chess program */
15193 /* Issue ICS rematch command */
15194 if (appData.icsActive) {
15195 SendToICS(ics_prefix);
15196 SendToICS("rematch\n");
15203 /* Call your opponent's flag (claim a win on time) */
15204 if (appData.icsActive) {
15205 SendToICS(ics_prefix);
15206 SendToICS("flag\n");
15208 switch (gameMode) {
15211 case MachinePlaysWhite:
15214 GameEnds(GameIsDrawn, "Both players ran out of time",
15217 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15219 DisplayError(_("Your opponent is not out of time"), 0);
15222 case MachinePlaysBlack:
15225 GameEnds(GameIsDrawn, "Both players ran out of time",
15228 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15230 DisplayError(_("Your opponent is not out of time"), 0);
15238 ClockClick (int which)
15239 { // [HGM] code moved to back-end from winboard.c
15240 if(which) { // black clock
15241 if (gameMode == EditPosition || gameMode == IcsExamining) {
15242 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15243 SetBlackToPlayEvent();
15244 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15245 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15246 } else if (shiftKey) {
15247 AdjustClock(which, -1);
15248 } else if (gameMode == IcsPlayingWhite ||
15249 gameMode == MachinePlaysBlack) {
15252 } else { // white clock
15253 if (gameMode == EditPosition || gameMode == IcsExamining) {
15254 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15255 SetWhiteToPlayEvent();
15256 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15257 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15258 } else if (shiftKey) {
15259 AdjustClock(which, -1);
15260 } else if (gameMode == IcsPlayingBlack ||
15261 gameMode == MachinePlaysWhite) {
15270 /* Offer draw or accept pending draw offer from opponent */
15272 if (appData.icsActive) {
15273 /* Note: tournament rules require draw offers to be
15274 made after you make your move but before you punch
15275 your clock. Currently ICS doesn't let you do that;
15276 instead, you immediately punch your clock after making
15277 a move, but you can offer a draw at any time. */
15279 SendToICS(ics_prefix);
15280 SendToICS("draw\n");
15281 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15282 } else if (cmailMsgLoaded) {
15283 if (currentMove == cmailOldMove &&
15284 commentList[cmailOldMove] != NULL &&
15285 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15286 "Black offers a draw" : "White offers a draw")) {
15287 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15288 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15289 } else if (currentMove == cmailOldMove + 1) {
15290 char *offer = WhiteOnMove(cmailOldMove) ?
15291 "White offers a draw" : "Black offers a draw";
15292 AppendComment(currentMove, offer, TRUE);
15293 DisplayComment(currentMove - 1, offer);
15294 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15296 DisplayError(_("You must make your move before offering a draw"), 0);
15297 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15299 } else if (first.offeredDraw) {
15300 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15302 if (first.sendDrawOffers) {
15303 SendToProgram("draw\n", &first);
15304 userOfferedDraw = TRUE;
15312 /* Offer Adjourn or accept pending Adjourn offer from opponent */
15314 if (appData.icsActive) {
15315 SendToICS(ics_prefix);
15316 SendToICS("adjourn\n");
15318 /* Currently GNU Chess doesn't offer or accept Adjourns */
15326 /* Offer Abort or accept pending Abort offer from opponent */
15328 if (appData.icsActive) {
15329 SendToICS(ics_prefix);
15330 SendToICS("abort\n");
15332 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15339 /* Resign. You can do this even if it's not your turn. */
15341 if (appData.icsActive) {
15342 SendToICS(ics_prefix);
15343 SendToICS("resign\n");
15345 switch (gameMode) {
15346 case MachinePlaysWhite:
15347 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15349 case MachinePlaysBlack:
15350 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15353 if (cmailMsgLoaded) {
15355 if (WhiteOnMove(cmailOldMove)) {
15356 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15358 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15360 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15371 StopObservingEvent ()
15373 /* Stop observing current games */
15374 SendToICS(ics_prefix);
15375 SendToICS("unobserve\n");
15379 StopExaminingEvent ()
15381 /* Stop observing current game */
15382 SendToICS(ics_prefix);
15383 SendToICS("unexamine\n");
15387 ForwardInner (int target)
15389 int limit; int oldSeekGraphUp = seekGraphUp;
15391 if (appData.debugMode)
15392 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15393 target, currentMove, forwardMostMove);
15395 if (gameMode == EditPosition)
15398 seekGraphUp = FALSE;
15399 MarkTargetSquares(1);
15401 if (gameMode == PlayFromGameFile && !pausing)
15404 if (gameMode == IcsExamining && pausing)
15405 limit = pauseExamForwardMostMove;
15407 limit = forwardMostMove;
15409 if (target > limit) target = limit;
15411 if (target > 0 && moveList[target - 1][0]) {
15412 int fromX, fromY, toX, toY;
15413 toX = moveList[target - 1][2] - AAA;
15414 toY = moveList[target - 1][3] - ONE;
15415 if (moveList[target - 1][1] == '@') {
15416 if (appData.highlightLastMove) {
15417 SetHighlights(-1, -1, toX, toY);
15420 fromX = moveList[target - 1][0] - AAA;
15421 fromY = moveList[target - 1][1] - ONE;
15422 if (target == currentMove + 1) {
15423 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15425 if (appData.highlightLastMove) {
15426 SetHighlights(fromX, fromY, toX, toY);
15430 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15431 gameMode == Training || gameMode == PlayFromGameFile ||
15432 gameMode == AnalyzeFile) {
15433 while (currentMove < target) {
15434 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15435 SendMoveToProgram(currentMove++, &first);
15438 currentMove = target;
15441 if (gameMode == EditGame || gameMode == EndOfGame) {
15442 whiteTimeRemaining = timeRemaining[0][currentMove];
15443 blackTimeRemaining = timeRemaining[1][currentMove];
15445 DisplayBothClocks();
15446 DisplayMove(currentMove - 1);
15447 DrawPosition(oldSeekGraphUp, boards[currentMove]);
15448 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15449 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15450 DisplayComment(currentMove - 1, commentList[currentMove]);
15452 ClearMap(); // [HGM] exclude: invalidate map
15459 if (gameMode == IcsExamining && !pausing) {
15460 SendToICS(ics_prefix);
15461 SendToICS("forward\n");
15463 ForwardInner(currentMove + 1);
15470 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15471 /* to optimze, we temporarily turn off analysis mode while we feed
15472 * the remaining moves to the engine. Otherwise we get analysis output
15475 if (first.analysisSupport) {
15476 SendToProgram("exit\nforce\n", &first);
15477 first.analyzing = FALSE;
15481 if (gameMode == IcsExamining && !pausing) {
15482 SendToICS(ics_prefix);
15483 SendToICS("forward 999999\n");
15485 ForwardInner(forwardMostMove);
15488 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15489 /* we have fed all the moves, so reactivate analysis mode */
15490 SendToProgram("analyze\n", &first);
15491 first.analyzing = TRUE;
15492 /*first.maybeThinking = TRUE;*/
15493 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15498 BackwardInner (int target)
15500 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15502 if (appData.debugMode)
15503 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15504 target, currentMove, forwardMostMove);
15506 if (gameMode == EditPosition) return;
15507 seekGraphUp = FALSE;
15508 MarkTargetSquares(1);
15509 if (currentMove <= backwardMostMove) {
15511 DrawPosition(full_redraw, boards[currentMove]);
15514 if (gameMode == PlayFromGameFile && !pausing)
15517 if (moveList[target][0]) {
15518 int fromX, fromY, toX, toY;
15519 toX = moveList[target][2] - AAA;
15520 toY = moveList[target][3] - ONE;
15521 if (moveList[target][1] == '@') {
15522 if (appData.highlightLastMove) {
15523 SetHighlights(-1, -1, toX, toY);
15526 fromX = moveList[target][0] - AAA;
15527 fromY = moveList[target][1] - ONE;
15528 if (target == currentMove - 1) {
15529 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15531 if (appData.highlightLastMove) {
15532 SetHighlights(fromX, fromY, toX, toY);
15536 if (gameMode == EditGame || gameMode==AnalyzeMode ||
15537 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15538 while (currentMove > target) {
15539 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15540 // null move cannot be undone. Reload program with move history before it.
15542 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15543 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15545 SendBoard(&first, i);
15546 if(second.analyzing) SendBoard(&second, i);
15547 for(currentMove=i; currentMove<target; currentMove++) {
15548 SendMoveToProgram(currentMove, &first);
15549 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15553 SendToBoth("undo\n");
15557 currentMove = target;
15560 if (gameMode == EditGame || gameMode == EndOfGame) {
15561 whiteTimeRemaining = timeRemaining[0][currentMove];
15562 blackTimeRemaining = timeRemaining[1][currentMove];
15564 DisplayBothClocks();
15565 DisplayMove(currentMove - 1);
15566 DrawPosition(full_redraw, boards[currentMove]);
15567 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15568 // [HGM] PV info: routine tests if comment empty
15569 DisplayComment(currentMove - 1, commentList[currentMove]);
15570 ClearMap(); // [HGM] exclude: invalidate map
15576 if (gameMode == IcsExamining && !pausing) {
15577 SendToICS(ics_prefix);
15578 SendToICS("backward\n");
15580 BackwardInner(currentMove - 1);
15587 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15588 /* to optimize, we temporarily turn off analysis mode while we undo
15589 * all the moves. Otherwise we get analysis output after each undo.
15591 if (first.analysisSupport) {
15592 SendToProgram("exit\nforce\n", &first);
15593 first.analyzing = FALSE;
15597 if (gameMode == IcsExamining && !pausing) {
15598 SendToICS(ics_prefix);
15599 SendToICS("backward 999999\n");
15601 BackwardInner(backwardMostMove);
15604 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15605 /* we have fed all the moves, so reactivate analysis mode */
15606 SendToProgram("analyze\n", &first);
15607 first.analyzing = TRUE;
15608 /*first.maybeThinking = TRUE;*/
15609 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15616 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15617 if (to >= forwardMostMove) to = forwardMostMove;
15618 if (to <= backwardMostMove) to = backwardMostMove;
15619 if (to < currentMove) {
15627 RevertEvent (Boolean annotate)
15629 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15632 if (gameMode != IcsExamining) {
15633 DisplayError(_("You are not examining a game"), 0);
15637 DisplayError(_("You can't revert while pausing"), 0);
15640 SendToICS(ics_prefix);
15641 SendToICS("revert\n");
15645 RetractMoveEvent ()
15647 switch (gameMode) {
15648 case MachinePlaysWhite:
15649 case MachinePlaysBlack:
15650 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15651 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15654 if (forwardMostMove < 2) return;
15655 currentMove = forwardMostMove = forwardMostMove - 2;
15656 whiteTimeRemaining = timeRemaining[0][currentMove];
15657 blackTimeRemaining = timeRemaining[1][currentMove];
15658 DisplayBothClocks();
15659 DisplayMove(currentMove - 1);
15660 ClearHighlights();/*!! could figure this out*/
15661 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15662 SendToProgram("remove\n", &first);
15663 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15666 case BeginningOfGame:
15670 case IcsPlayingWhite:
15671 case IcsPlayingBlack:
15672 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15673 SendToICS(ics_prefix);
15674 SendToICS("takeback 2\n");
15676 SendToICS(ics_prefix);
15677 SendToICS("takeback 1\n");
15686 ChessProgramState *cps;
15688 switch (gameMode) {
15689 case MachinePlaysWhite:
15690 if (!WhiteOnMove(forwardMostMove)) {
15691 DisplayError(_("It is your turn"), 0);
15696 case MachinePlaysBlack:
15697 if (WhiteOnMove(forwardMostMove)) {
15698 DisplayError(_("It is your turn"), 0);
15703 case TwoMachinesPlay:
15704 if (WhiteOnMove(forwardMostMove) ==
15705 (first.twoMachinesColor[0] == 'w')) {
15711 case BeginningOfGame:
15715 SendToProgram("?\n", cps);
15719 TruncateGameEvent ()
15722 if (gameMode != EditGame) return;
15729 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15730 if (forwardMostMove > currentMove) {
15731 if (gameInfo.resultDetails != NULL) {
15732 free(gameInfo.resultDetails);
15733 gameInfo.resultDetails = NULL;
15734 gameInfo.result = GameUnfinished;
15736 forwardMostMove = currentMove;
15737 HistorySet(parseList, backwardMostMove, forwardMostMove,
15745 if (appData.noChessProgram) return;
15746 switch (gameMode) {
15747 case MachinePlaysWhite:
15748 if (WhiteOnMove(forwardMostMove)) {
15749 DisplayError(_("Wait until your turn."), 0);
15753 case BeginningOfGame:
15754 case MachinePlaysBlack:
15755 if (!WhiteOnMove(forwardMostMove)) {
15756 DisplayError(_("Wait until your turn."), 0);
15761 DisplayError(_("No hint available"), 0);
15764 SendToProgram("hint\n", &first);
15765 hintRequested = TRUE;
15771 ListGame * lg = (ListGame *) gameList.head;
15774 static int secondTime = FALSE;
15776 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15777 DisplayError(_("Game list not loaded or empty"), 0);
15781 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15784 DisplayNote(_("Book file exists! Try again for overwrite."));
15788 creatingBook = TRUE;
15789 secondTime = FALSE;
15791 /* Get list size */
15792 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15793 LoadGame(f, nItem, "", TRUE);
15794 AddGameToBook(TRUE);
15795 lg = (ListGame *) lg->node.succ;
15798 creatingBook = FALSE;
15805 if (appData.noChessProgram) return;
15806 switch (gameMode) {
15807 case MachinePlaysWhite:
15808 if (WhiteOnMove(forwardMostMove)) {
15809 DisplayError(_("Wait until your turn."), 0);
15813 case BeginningOfGame:
15814 case MachinePlaysBlack:
15815 if (!WhiteOnMove(forwardMostMove)) {
15816 DisplayError(_("Wait until your turn."), 0);
15821 EditPositionDone(TRUE);
15823 case TwoMachinesPlay:
15828 SendToProgram("bk\n", &first);
15829 bookOutput[0] = NULLCHAR;
15830 bookRequested = TRUE;
15836 char *tags = PGNTags(&gameInfo);
15837 TagsPopUp(tags, CmailMsg());
15841 /* end button procedures */
15844 PrintPosition (FILE *fp, int move)
15848 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15849 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15850 char c = PieceToChar(boards[move][i][j]);
15851 fputc(c == 'x' ? '.' : c, fp);
15852 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15855 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15856 fprintf(fp, "white to play\n");
15858 fprintf(fp, "black to play\n");
15862 PrintOpponents (FILE *fp)
15864 if (gameInfo.white != NULL) {
15865 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15871 /* Find last component of program's own name, using some heuristics */
15873 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15876 int local = (strcmp(host, "localhost") == 0);
15877 while (!local && (p = strchr(prog, ';')) != NULL) {
15879 while (*p == ' ') p++;
15882 if (*prog == '"' || *prog == '\'') {
15883 q = strchr(prog + 1, *prog);
15885 q = strchr(prog, ' ');
15887 if (q == NULL) q = prog + strlen(prog);
15889 while (p >= prog && *p != '/' && *p != '\\') p--;
15891 if(p == prog && *p == '"') p++;
15893 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15894 memcpy(buf, p, q - p);
15895 buf[q - p] = NULLCHAR;
15903 TimeControlTagValue ()
15906 if (!appData.clockMode) {
15907 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15908 } else if (movesPerSession > 0) {
15909 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15910 } else if (timeIncrement == 0) {
15911 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15913 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15915 return StrSave(buf);
15921 /* This routine is used only for certain modes */
15922 VariantClass v = gameInfo.variant;
15923 ChessMove r = GameUnfinished;
15926 if(keepInfo) return;
15928 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15929 r = gameInfo.result;
15930 p = gameInfo.resultDetails;
15931 gameInfo.resultDetails = NULL;
15933 ClearGameInfo(&gameInfo);
15934 gameInfo.variant = v;
15936 switch (gameMode) {
15937 case MachinePlaysWhite:
15938 gameInfo.event = StrSave( appData.pgnEventHeader );
15939 gameInfo.site = StrSave(HostName());
15940 gameInfo.date = PGNDate();
15941 gameInfo.round = StrSave("-");
15942 gameInfo.white = StrSave(first.tidy);
15943 gameInfo.black = StrSave(UserName());
15944 gameInfo.timeControl = TimeControlTagValue();
15947 case MachinePlaysBlack:
15948 gameInfo.event = StrSave( appData.pgnEventHeader );
15949 gameInfo.site = StrSave(HostName());
15950 gameInfo.date = PGNDate();
15951 gameInfo.round = StrSave("-");
15952 gameInfo.white = StrSave(UserName());
15953 gameInfo.black = StrSave(first.tidy);
15954 gameInfo.timeControl = TimeControlTagValue();
15957 case TwoMachinesPlay:
15958 gameInfo.event = StrSave( appData.pgnEventHeader );
15959 gameInfo.site = StrSave(HostName());
15960 gameInfo.date = PGNDate();
15963 snprintf(buf, MSG_SIZ, "%d", roundNr);
15964 gameInfo.round = StrSave(buf);
15966 gameInfo.round = StrSave("-");
15968 if (first.twoMachinesColor[0] == 'w') {
15969 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15970 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15972 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15973 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15975 gameInfo.timeControl = TimeControlTagValue();
15979 gameInfo.event = StrSave("Edited game");
15980 gameInfo.site = StrSave(HostName());
15981 gameInfo.date = PGNDate();
15982 gameInfo.round = StrSave("-");
15983 gameInfo.white = StrSave("-");
15984 gameInfo.black = StrSave("-");
15985 gameInfo.result = r;
15986 gameInfo.resultDetails = p;
15990 gameInfo.event = StrSave("Edited position");
15991 gameInfo.site = StrSave(HostName());
15992 gameInfo.date = PGNDate();
15993 gameInfo.round = StrSave("-");
15994 gameInfo.white = StrSave("-");
15995 gameInfo.black = StrSave("-");
15998 case IcsPlayingWhite:
15999 case IcsPlayingBlack:
16004 case PlayFromGameFile:
16005 gameInfo.event = StrSave("Game from non-PGN file");
16006 gameInfo.site = StrSave(HostName());
16007 gameInfo.date = PGNDate();
16008 gameInfo.round = StrSave("-");
16009 gameInfo.white = StrSave("?");
16010 gameInfo.black = StrSave("?");
16019 ReplaceComment (int index, char *text)
16025 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16026 pvInfoList[index-1].depth == len &&
16027 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16028 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16029 while (*text == '\n') text++;
16030 len = strlen(text);
16031 while (len > 0 && text[len - 1] == '\n') len--;
16033 if (commentList[index] != NULL)
16034 free(commentList[index]);
16037 commentList[index] = NULL;
16040 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16041 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16042 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16043 commentList[index] = (char *) malloc(len + 2);
16044 strncpy(commentList[index], text, len);
16045 commentList[index][len] = '\n';
16046 commentList[index][len + 1] = NULLCHAR;
16048 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16050 commentList[index] = (char *) malloc(len + 7);
16051 safeStrCpy(commentList[index], "{\n", 3);
16052 safeStrCpy(commentList[index]+2, text, len+1);
16053 commentList[index][len+2] = NULLCHAR;
16054 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16055 strcat(commentList[index], "\n}\n");
16060 CrushCRs (char *text)
16068 if (ch == '\r') continue;
16070 } while (ch != '\0');
16074 AppendComment (int index, char *text, Boolean addBraces)
16075 /* addBraces tells if we should add {} */
16080 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16081 if(addBraces == 3) addBraces = 0; else // force appending literally
16082 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16085 while (*text == '\n') text++;
16086 len = strlen(text);
16087 while (len > 0 && text[len - 1] == '\n') len--;
16088 text[len] = NULLCHAR;
16090 if (len == 0) return;
16092 if (commentList[index] != NULL) {
16093 Boolean addClosingBrace = addBraces;
16094 old = commentList[index];
16095 oldlen = strlen(old);
16096 while(commentList[index][oldlen-1] == '\n')
16097 commentList[index][--oldlen] = NULLCHAR;
16098 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16099 safeStrCpy(commentList[index], old, oldlen + len + 6);
16101 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16102 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16103 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16104 while (*text == '\n') { text++; len--; }
16105 commentList[index][--oldlen] = NULLCHAR;
16107 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16108 else strcat(commentList[index], "\n");
16109 strcat(commentList[index], text);
16110 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16111 else strcat(commentList[index], "\n");
16113 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16115 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16116 else commentList[index][0] = NULLCHAR;
16117 strcat(commentList[index], text);
16118 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16119 if(addBraces == TRUE) strcat(commentList[index], "}\n");
16124 FindStr (char * text, char * sub_text)
16126 char * result = strstr( text, sub_text );
16128 if( result != NULL ) {
16129 result += strlen( sub_text );
16135 /* [AS] Try to extract PV info from PGN comment */
16136 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16138 GetInfoFromComment (int index, char * text)
16140 char * sep = text, *p;
16142 if( text != NULL && index > 0 ) {
16145 int time = -1, sec = 0, deci;
16146 char * s_eval = FindStr( text, "[%eval " );
16147 char * s_emt = FindStr( text, "[%emt " );
16149 if( s_eval != NULL || s_emt != NULL ) {
16151 if(0) { // [HGM] this code is not finished, and could actually be detrimental
16156 if( s_eval != NULL ) {
16157 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16161 if( delim != ']' ) {
16166 if( s_emt != NULL ) {
16171 /* We expect something like: [+|-]nnn.nn/dd */
16174 if(*text != '{') return text; // [HGM] braces: must be normal comment
16176 sep = strchr( text, '/' );
16177 if( sep == NULL || sep < (text+4) ) {
16182 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16183 if(p[1] == '(') { // comment starts with PV
16184 p = strchr(p, ')'); // locate end of PV
16185 if(p == NULL || sep < p+5) return text;
16186 // at this point we have something like "{(.*) +0.23/6 ..."
16187 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16188 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16189 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16191 time = -1; sec = -1; deci = -1;
16192 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16193 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16194 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16195 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
16199 if( score_lo < 0 || score_lo >= 100 ) {
16203 if(sec >= 0) time = 600*time + 10*sec; else
16204 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16206 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16208 /* [HGM] PV time: now locate end of PV info */
16209 while( *++sep >= '0' && *sep <= '9'); // strip depth
16211 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16213 while( *++sep >= '0' && *sep <= '9'); // strip seconds
16215 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16216 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16227 pvInfoList[index-1].depth = depth;
16228 pvInfoList[index-1].score = score;
16229 pvInfoList[index-1].time = 10*time; // centi-sec
16230 if(*sep == '}') *sep = 0; else *--sep = '{';
16231 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16237 SendToProgram (char *message, ChessProgramState *cps)
16239 int count, outCount, error;
16242 if (cps->pr == NoProc) return;
16245 if (appData.debugMode) {
16248 fprintf(debugFP, "%ld >%-6s: %s",
16249 SubtractTimeMarks(&now, &programStartTime),
16250 cps->which, message);
16252 fprintf(serverFP, "%ld >%-6s: %s",
16253 SubtractTimeMarks(&now, &programStartTime),
16254 cps->which, message), fflush(serverFP);
16257 count = strlen(message);
16258 outCount = OutputToProcess(cps->pr, message, count, &error);
16259 if (outCount < count && !exiting
16260 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16261 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16262 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16263 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16264 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16265 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16266 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16267 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16269 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16270 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16271 gameInfo.result = res;
16273 gameInfo.resultDetails = StrSave(buf);
16275 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16276 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16281 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16285 ChessProgramState *cps = (ChessProgramState *)closure;
16287 if (isr != cps->isr) return; /* Killed intentionally */
16290 RemoveInputSource(cps->isr);
16291 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16292 _(cps->which), cps->program);
16293 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16294 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16295 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16296 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16297 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16298 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16300 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16301 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16302 gameInfo.result = res;
16304 gameInfo.resultDetails = StrSave(buf);
16306 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16307 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16309 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16310 _(cps->which), cps->program);
16311 RemoveInputSource(cps->isr);
16313 /* [AS] Program is misbehaving badly... kill it */
16314 if( count == -2 ) {
16315 DestroyChildProcess( cps->pr, 9 );
16319 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16324 if ((end_str = strchr(message, '\r')) != NULL)
16325 *end_str = NULLCHAR;
16326 if ((end_str = strchr(message, '\n')) != NULL)
16327 *end_str = NULLCHAR;
16329 if (appData.debugMode) {
16330 TimeMark now; int print = 1;
16331 char *quote = ""; char c; int i;
16333 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16334 char start = message[0];
16335 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16336 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16337 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
16338 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16339 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16340 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
16341 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16342 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
16343 sscanf(message, "hint: %c", &c)!=1 &&
16344 sscanf(message, "pong %c", &c)!=1 && start != '#') {
16345 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16346 print = (appData.engineComments >= 2);
16348 message[0] = start; // restore original message
16352 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16353 SubtractTimeMarks(&now, &programStartTime), cps->which,
16357 fprintf(serverFP, "%ld <%-6s: %s%s\n",
16358 SubtractTimeMarks(&now, &programStartTime), cps->which,
16360 message), fflush(serverFP);
16364 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16365 if (appData.icsEngineAnalyze) {
16366 if (strstr(message, "whisper") != NULL ||
16367 strstr(message, "kibitz") != NULL ||
16368 strstr(message, "tellics") != NULL) return;
16371 HandleMachineMove(message, cps);
16376 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16381 if( timeControl_2 > 0 ) {
16382 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16383 tc = timeControl_2;
16386 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16387 inc /= cps->timeOdds;
16388 st /= cps->timeOdds;
16390 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16393 /* Set exact time per move, normally using st command */
16394 if (cps->stKludge) {
16395 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16397 if (seconds == 0) {
16398 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16400 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16403 snprintf(buf, MSG_SIZ, "st %d\n", st);
16406 /* Set conventional or incremental time control, using level command */
16407 if (seconds == 0) {
16408 /* Note old gnuchess bug -- minutes:seconds used to not work.
16409 Fixed in later versions, but still avoid :seconds
16410 when seconds is 0. */
16411 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16413 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16414 seconds, inc/1000.);
16417 SendToProgram(buf, cps);
16419 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16420 /* Orthogonally, limit search to given depth */
16422 if (cps->sdKludge) {
16423 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16425 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16427 SendToProgram(buf, cps);
16430 if(cps->nps >= 0) { /* [HGM] nps */
16431 if(cps->supportsNPS == FALSE)
16432 cps->nps = -1; // don't use if engine explicitly says not supported!
16434 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16435 SendToProgram(buf, cps);
16440 ChessProgramState *
16442 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16444 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16445 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16451 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16453 char message[MSG_SIZ];
16456 /* Note: this routine must be called when the clocks are stopped
16457 or when they have *just* been set or switched; otherwise
16458 it will be off by the time since the current tick started.
16460 if (machineWhite) {
16461 time = whiteTimeRemaining / 10;
16462 otime = blackTimeRemaining / 10;
16464 time = blackTimeRemaining / 10;
16465 otime = whiteTimeRemaining / 10;
16467 /* [HGM] translate opponent's time by time-odds factor */
16468 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16470 if (time <= 0) time = 1;
16471 if (otime <= 0) otime = 1;
16473 snprintf(message, MSG_SIZ, "time %ld\n", time);
16474 SendToProgram(message, cps);
16476 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16477 SendToProgram(message, cps);
16481 EngineDefinedVariant (ChessProgramState *cps, int n)
16482 { // return name of n-th unknown variant that engine supports
16483 static char buf[MSG_SIZ];
16484 char *p, *s = cps->variants;
16485 if(!s) return NULL;
16486 do { // parse string from variants feature
16488 p = strchr(s, ',');
16489 if(p) *p = NULLCHAR;
16490 v = StringToVariant(s);
16491 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16492 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16493 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16496 if(n < 0) return buf;
16502 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16505 int len = strlen(name);
16508 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16510 sscanf(*p, "%d", &val);
16512 while (**p && **p != ' ')
16514 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16515 SendToProgram(buf, cps);
16522 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16525 int len = strlen(name);
16526 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16528 sscanf(*p, "%d", loc);
16529 while (**p && **p != ' ') (*p)++;
16530 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16531 SendToProgram(buf, cps);
16538 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16541 int len = strlen(name);
16542 if (strncmp((*p), name, len) == 0
16543 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16545 ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16546 sscanf(*p, "%[^\"]", *loc);
16547 while (**p && **p != '\"') (*p)++;
16548 if (**p == '\"') (*p)++;
16549 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16550 SendToProgram(buf, cps);
16557 ParseOption (Option *opt, ChessProgramState *cps)
16558 // [HGM] options: process the string that defines an engine option, and determine
16559 // name, type, default value, and allowed value range
16561 char *p, *q, buf[MSG_SIZ];
16562 int n, min = (-1)<<31, max = 1<<31, def;
16564 if(p = strstr(opt->name, " -spin ")) {
16565 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16566 if(max < min) max = min; // enforce consistency
16567 if(def < min) def = min;
16568 if(def > max) def = max;
16573 } else if((p = strstr(opt->name, " -slider "))) {
16574 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16575 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16576 if(max < min) max = min; // enforce consistency
16577 if(def < min) def = min;
16578 if(def > max) def = max;
16582 opt->type = Spin; // Slider;
16583 } else if((p = strstr(opt->name, " -string "))) {
16584 opt->textValue = p+9;
16585 opt->type = TextBox;
16586 } else if((p = strstr(opt->name, " -file "))) {
16587 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16588 opt->textValue = p+7;
16589 opt->type = FileName; // FileName;
16590 } else if((p = strstr(opt->name, " -path "))) {
16591 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16592 opt->textValue = p+7;
16593 opt->type = PathName; // PathName;
16594 } else if(p = strstr(opt->name, " -check ")) {
16595 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16596 opt->value = (def != 0);
16597 opt->type = CheckBox;
16598 } else if(p = strstr(opt->name, " -combo ")) {
16599 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16600 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16601 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16602 opt->value = n = 0;
16603 while(q = StrStr(q, " /// ")) {
16604 n++; *q = 0; // count choices, and null-terminate each of them
16606 if(*q == '*') { // remember default, which is marked with * prefix
16610 cps->comboList[cps->comboCnt++] = q;
16612 cps->comboList[cps->comboCnt++] = NULL;
16614 opt->type = ComboBox;
16615 } else if(p = strstr(opt->name, " -button")) {
16616 opt->type = Button;
16617 } else if(p = strstr(opt->name, " -save")) {
16618 opt->type = SaveButton;
16619 } else return FALSE;
16620 *p = 0; // terminate option name
16621 // now look if the command-line options define a setting for this engine option.
16622 if(cps->optionSettings && cps->optionSettings[0])
16623 p = strstr(cps->optionSettings, opt->name); else p = NULL;
16624 if(p && (p == cps->optionSettings || p[-1] == ',')) {
16625 snprintf(buf, MSG_SIZ, "option %s", p);
16626 if(p = strstr(buf, ",")) *p = 0;
16627 if(q = strchr(buf, '=')) switch(opt->type) {
16629 for(n=0; n<opt->max; n++)
16630 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16633 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16637 opt->value = atoi(q+1);
16642 SendToProgram(buf, cps);
16648 FeatureDone (ChessProgramState *cps, int val)
16650 DelayedEventCallback cb = GetDelayedEvent();
16651 if ((cb == InitBackEnd3 && cps == &first) ||
16652 (cb == SettingsMenuIfReady && cps == &second) ||
16653 (cb == LoadEngine) ||
16654 (cb == TwoMachinesEventIfReady)) {
16655 CancelDelayedEvent();
16656 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16658 cps->initDone = val;
16659 if(val) cps->reload = FALSE;
16662 /* Parse feature command from engine */
16664 ParseFeatures (char *args, ChessProgramState *cps)
16672 while (*p == ' ') p++;
16673 if (*p == NULLCHAR) return;
16675 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16676 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16677 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16678 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16679 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16680 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16681 if (BoolFeature(&p, "reuse", &val, cps)) {
16682 /* Engine can disable reuse, but can't enable it if user said no */
16683 if (!val) cps->reuse = FALSE;
16686 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16687 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16688 if (gameMode == TwoMachinesPlay) {
16689 DisplayTwoMachinesTitle();
16695 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16696 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16697 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16698 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16699 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16700 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16701 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16702 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16703 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16704 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16705 if (IntFeature(&p, "done", &val, cps)) {
16706 FeatureDone(cps, val);
16709 /* Added by Tord: */
16710 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16711 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16712 /* End of additions by Tord */
16714 /* [HGM] added features: */
16715 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16716 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16717 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16718 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16719 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16720 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16721 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16722 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16723 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16724 FREE(cps->option[cps->nrOptions].name);
16725 cps->option[cps->nrOptions].name = q; q = NULL;
16726 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16727 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16728 SendToProgram(buf, cps);
16731 if(cps->nrOptions >= MAX_OPTIONS) {
16733 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16734 DisplayError(buf, 0);
16738 /* End of additions by HGM */
16740 /* unknown feature: complain and skip */
16742 while (*q && *q != '=') q++;
16743 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16744 SendToProgram(buf, cps);
16750 while (*p && *p != '\"') p++;
16751 if (*p == '\"') p++;
16753 while (*p && *p != ' ') p++;
16761 PeriodicUpdatesEvent (int newState)
16763 if (newState == appData.periodicUpdates)
16766 appData.periodicUpdates=newState;
16768 /* Display type changes, so update it now */
16769 // DisplayAnalysis();
16771 /* Get the ball rolling again... */
16773 AnalysisPeriodicEvent(1);
16774 StartAnalysisClock();
16779 PonderNextMoveEvent (int newState)
16781 if (newState == appData.ponderNextMove) return;
16782 if (gameMode == EditPosition) EditPositionDone(TRUE);
16784 SendToProgram("hard\n", &first);
16785 if (gameMode == TwoMachinesPlay) {
16786 SendToProgram("hard\n", &second);
16789 SendToProgram("easy\n", &first);
16790 thinkOutput[0] = NULLCHAR;
16791 if (gameMode == TwoMachinesPlay) {
16792 SendToProgram("easy\n", &second);
16795 appData.ponderNextMove = newState;
16799 NewSettingEvent (int option, int *feature, char *command, int value)
16803 if (gameMode == EditPosition) EditPositionDone(TRUE);
16804 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16805 if(feature == NULL || *feature) SendToProgram(buf, &first);
16806 if (gameMode == TwoMachinesPlay) {
16807 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16812 ShowThinkingEvent ()
16813 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16815 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16816 int newState = appData.showThinking
16817 // [HGM] thinking: other features now need thinking output as well
16818 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16820 if (oldState == newState) return;
16821 oldState = newState;
16822 if (gameMode == EditPosition) EditPositionDone(TRUE);
16824 SendToProgram("post\n", &first);
16825 if (gameMode == TwoMachinesPlay) {
16826 SendToProgram("post\n", &second);
16829 SendToProgram("nopost\n", &first);
16830 thinkOutput[0] = NULLCHAR;
16831 if (gameMode == TwoMachinesPlay) {
16832 SendToProgram("nopost\n", &second);
16835 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16839 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16841 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16842 if (pr == NoProc) return;
16843 AskQuestion(title, question, replyPrefix, pr);
16847 TypeInEvent (char firstChar)
16849 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16850 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16851 gameMode == AnalyzeMode || gameMode == EditGame ||
16852 gameMode == EditPosition || gameMode == IcsExamining ||
16853 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16854 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16855 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16856 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
16857 gameMode == Training) PopUpMoveDialog(firstChar);
16861 TypeInDoneEvent (char *move)
16864 int n, fromX, fromY, toX, toY;
16866 ChessMove moveType;
16869 if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16870 EditPositionPasteFEN(move);
16873 // [HGM] movenum: allow move number to be typed in any mode
16874 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16878 // undocumented kludge: allow command-line option to be typed in!
16879 // (potentially fatal, and does not implement the effect of the option.)
16880 // should only be used for options that are values on which future decisions will be made,
16881 // and definitely not on options that would be used during initialization.
16882 if(strstr(move, "!!! -") == move) {
16883 ParseArgsFromString(move+4);
16887 if (gameMode != EditGame && currentMove != forwardMostMove &&
16888 gameMode != Training) {
16889 DisplayMoveError(_("Displayed move is not current"));
16891 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16892 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16893 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16894 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16895 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16896 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16898 DisplayMoveError(_("Could not parse move"));
16904 DisplayMove (int moveNumber)
16906 char message[MSG_SIZ];
16908 char cpThinkOutput[MSG_SIZ];
16910 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16912 if (moveNumber == forwardMostMove - 1 ||
16913 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16915 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16917 if (strchr(cpThinkOutput, '\n')) {
16918 *strchr(cpThinkOutput, '\n') = NULLCHAR;
16921 *cpThinkOutput = NULLCHAR;
16924 /* [AS] Hide thinking from human user */
16925 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16926 *cpThinkOutput = NULLCHAR;
16927 if( thinkOutput[0] != NULLCHAR ) {
16930 for( i=0; i<=hiddenThinkOutputState; i++ ) {
16931 cpThinkOutput[i] = '.';
16933 cpThinkOutput[i] = NULLCHAR;
16934 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16938 if (moveNumber == forwardMostMove - 1 &&
16939 gameInfo.resultDetails != NULL) {
16940 if (gameInfo.resultDetails[0] == NULLCHAR) {
16941 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16943 snprintf(res, MSG_SIZ, " {%s} %s",
16944 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16950 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16951 DisplayMessage(res, cpThinkOutput);
16953 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16954 WhiteOnMove(moveNumber) ? " " : ".. ",
16955 parseList[moveNumber], res);
16956 DisplayMessage(message, cpThinkOutput);
16961 DisplayComment (int moveNumber, char *text)
16963 char title[MSG_SIZ];
16965 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16966 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16968 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16969 WhiteOnMove(moveNumber) ? " " : ".. ",
16970 parseList[moveNumber]);
16972 if (text != NULL && (appData.autoDisplayComment || commentUp))
16973 CommentPopUp(title, text);
16976 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16977 * might be busy thinking or pondering. It can be omitted if your
16978 * gnuchess is configured to stop thinking immediately on any user
16979 * input. However, that gnuchess feature depends on the FIONREAD
16980 * ioctl, which does not work properly on some flavors of Unix.
16983 Attention (ChessProgramState *cps)
16986 if (!cps->useSigint) return;
16987 if (appData.noChessProgram || (cps->pr == NoProc)) return;
16988 switch (gameMode) {
16989 case MachinePlaysWhite:
16990 case MachinePlaysBlack:
16991 case TwoMachinesPlay:
16992 case IcsPlayingWhite:
16993 case IcsPlayingBlack:
16996 /* Skip if we know it isn't thinking */
16997 if (!cps->maybeThinking) return;
16998 if (appData.debugMode)
16999 fprintf(debugFP, "Interrupting %s\n", cps->which);
17000 InterruptChildProcess(cps->pr);
17001 cps->maybeThinking = FALSE;
17006 #endif /*ATTENTION*/
17012 if (whiteTimeRemaining <= 0) {
17015 if (appData.icsActive) {
17016 if (appData.autoCallFlag &&
17017 gameMode == IcsPlayingBlack && !blackFlag) {
17018 SendToICS(ics_prefix);
17019 SendToICS("flag\n");
17023 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17025 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17026 if (appData.autoCallFlag) {
17027 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17034 if (blackTimeRemaining <= 0) {
17037 if (appData.icsActive) {
17038 if (appData.autoCallFlag &&
17039 gameMode == IcsPlayingWhite && !whiteFlag) {
17040 SendToICS(ics_prefix);
17041 SendToICS("flag\n");
17045 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17047 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17048 if (appData.autoCallFlag) {
17049 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17060 CheckTimeControl ()
17062 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17063 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17066 * add time to clocks when time control is achieved ([HGM] now also used for increment)
17068 if ( !WhiteOnMove(forwardMostMove) ) {
17069 /* White made time control */
17070 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17071 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17072 /* [HGM] time odds: correct new time quota for time odds! */
17073 / WhitePlayer()->timeOdds;
17074 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17076 lastBlack -= blackTimeRemaining;
17077 /* Black made time control */
17078 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17079 / WhitePlayer()->other->timeOdds;
17080 lastWhite = whiteTimeRemaining;
17085 DisplayBothClocks ()
17087 int wom = gameMode == EditPosition ?
17088 !blackPlaysFirst : WhiteOnMove(currentMove);
17089 DisplayWhiteClock(whiteTimeRemaining, wom);
17090 DisplayBlackClock(blackTimeRemaining, !wom);
17094 /* Timekeeping seems to be a portability nightmare. I think everyone
17095 has ftime(), but I'm really not sure, so I'm including some ifdefs
17096 to use other calls if you don't. Clocks will be less accurate if
17097 you have neither ftime nor gettimeofday.
17100 /* VS 2008 requires the #include outside of the function */
17101 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17102 #include <sys/timeb.h>
17105 /* Get the current time as a TimeMark */
17107 GetTimeMark (TimeMark *tm)
17109 #if HAVE_GETTIMEOFDAY
17111 struct timeval timeVal;
17112 struct timezone timeZone;
17114 gettimeofday(&timeVal, &timeZone);
17115 tm->sec = (long) timeVal.tv_sec;
17116 tm->ms = (int) (timeVal.tv_usec / 1000L);
17118 #else /*!HAVE_GETTIMEOFDAY*/
17121 // include <sys/timeb.h> / moved to just above start of function
17122 struct timeb timeB;
17125 tm->sec = (long) timeB.time;
17126 tm->ms = (int) timeB.millitm;
17128 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17129 tm->sec = (long) time(NULL);
17135 /* Return the difference in milliseconds between two
17136 time marks. We assume the difference will fit in a long!
17139 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17141 return 1000L*(tm2->sec - tm1->sec) +
17142 (long) (tm2->ms - tm1->ms);
17147 * Code to manage the game clocks.
17149 * In tournament play, black starts the clock and then white makes a move.
17150 * We give the human user a slight advantage if he is playing white---the
17151 * clocks don't run until he makes his first move, so it takes zero time.
17152 * Also, we don't account for network lag, so we could get out of sync
17153 * with GNU Chess's clock -- but then, referees are always right.
17156 static TimeMark tickStartTM;
17157 static long intendedTickLength;
17160 NextTickLength (long timeRemaining)
17162 long nominalTickLength, nextTickLength;
17164 if (timeRemaining > 0L && timeRemaining <= 10000L)
17165 nominalTickLength = 100L;
17167 nominalTickLength = 1000L;
17168 nextTickLength = timeRemaining % nominalTickLength;
17169 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17171 return nextTickLength;
17174 /* Adjust clock one minute up or down */
17176 AdjustClock (Boolean which, int dir)
17178 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17179 if(which) blackTimeRemaining += 60000*dir;
17180 else whiteTimeRemaining += 60000*dir;
17181 DisplayBothClocks();
17182 adjustedClock = TRUE;
17185 /* Stop clocks and reset to a fresh time control */
17189 (void) StopClockTimer();
17190 if (appData.icsActive) {
17191 whiteTimeRemaining = blackTimeRemaining = 0;
17192 } else if (searchTime) {
17193 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17194 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17195 } else { /* [HGM] correct new time quote for time odds */
17196 whiteTC = blackTC = fullTimeControlString;
17197 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17198 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17200 if (whiteFlag || blackFlag) {
17202 whiteFlag = blackFlag = FALSE;
17204 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17205 DisplayBothClocks();
17206 adjustedClock = FALSE;
17209 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17211 /* Decrement running clock by amount of time that has passed */
17215 long timeRemaining;
17216 long lastTickLength, fudge;
17219 if (!appData.clockMode) return;
17220 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17224 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17226 /* Fudge if we woke up a little too soon */
17227 fudge = intendedTickLength - lastTickLength;
17228 if (fudge < 0 || fudge > FUDGE) fudge = 0;
17230 if (WhiteOnMove(forwardMostMove)) {
17231 if(whiteNPS >= 0) lastTickLength = 0;
17232 timeRemaining = whiteTimeRemaining -= lastTickLength;
17233 if(timeRemaining < 0 && !appData.icsActive) {
17234 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17235 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17236 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17237 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17240 DisplayWhiteClock(whiteTimeRemaining - fudge,
17241 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17243 if(blackNPS >= 0) lastTickLength = 0;
17244 timeRemaining = blackTimeRemaining -= lastTickLength;
17245 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17246 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17248 blackStartMove = forwardMostMove;
17249 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17252 DisplayBlackClock(blackTimeRemaining - fudge,
17253 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17255 if (CheckFlags()) return;
17257 if(twoBoards) { // count down secondary board's clocks as well
17258 activePartnerTime -= lastTickLength;
17260 if(activePartner == 'W')
17261 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17263 DisplayBlackClock(activePartnerTime, TRUE);
17268 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17269 StartClockTimer(intendedTickLength);
17271 /* if the time remaining has fallen below the alarm threshold, sound the
17272 * alarm. if the alarm has sounded and (due to a takeback or time control
17273 * with increment) the time remaining has increased to a level above the
17274 * threshold, reset the alarm so it can sound again.
17277 if (appData.icsActive && appData.icsAlarm) {
17279 /* make sure we are dealing with the user's clock */
17280 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17281 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17284 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17285 alarmSounded = FALSE;
17286 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17288 alarmSounded = TRUE;
17294 /* A player has just moved, so stop the previously running
17295 clock and (if in clock mode) start the other one.
17296 We redisplay both clocks in case we're in ICS mode, because
17297 ICS gives us an update to both clocks after every move.
17298 Note that this routine is called *after* forwardMostMove
17299 is updated, so the last fractional tick must be subtracted
17300 from the color that is *not* on move now.
17303 SwitchClocks (int newMoveNr)
17305 long lastTickLength;
17307 int flagged = FALSE;
17311 if (StopClockTimer() && appData.clockMode) {
17312 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17313 if (!WhiteOnMove(forwardMostMove)) {
17314 if(blackNPS >= 0) lastTickLength = 0;
17315 blackTimeRemaining -= lastTickLength;
17316 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17317 // if(pvInfoList[forwardMostMove].time == -1)
17318 pvInfoList[forwardMostMove].time = // use GUI time
17319 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17321 if(whiteNPS >= 0) lastTickLength = 0;
17322 whiteTimeRemaining -= lastTickLength;
17323 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17324 // if(pvInfoList[forwardMostMove].time == -1)
17325 pvInfoList[forwardMostMove].time =
17326 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17328 flagged = CheckFlags();
17330 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17331 CheckTimeControl();
17333 if (flagged || !appData.clockMode) return;
17335 switch (gameMode) {
17336 case MachinePlaysBlack:
17337 case MachinePlaysWhite:
17338 case BeginningOfGame:
17339 if (pausing) return;
17343 case PlayFromGameFile:
17351 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17352 if(WhiteOnMove(forwardMostMove))
17353 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17354 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17358 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17359 whiteTimeRemaining : blackTimeRemaining);
17360 StartClockTimer(intendedTickLength);
17364 /* Stop both clocks */
17368 long lastTickLength;
17371 if (!StopClockTimer()) return;
17372 if (!appData.clockMode) return;
17376 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17377 if (WhiteOnMove(forwardMostMove)) {
17378 if(whiteNPS >= 0) lastTickLength = 0;
17379 whiteTimeRemaining -= lastTickLength;
17380 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17382 if(blackNPS >= 0) lastTickLength = 0;
17383 blackTimeRemaining -= lastTickLength;
17384 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17389 /* Start clock of player on move. Time may have been reset, so
17390 if clock is already running, stop and restart it. */
17394 (void) StopClockTimer(); /* in case it was running already */
17395 DisplayBothClocks();
17396 if (CheckFlags()) return;
17398 if (!appData.clockMode) return;
17399 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17401 GetTimeMark(&tickStartTM);
17402 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17403 whiteTimeRemaining : blackTimeRemaining);
17405 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17406 whiteNPS = blackNPS = -1;
17407 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17408 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17409 whiteNPS = first.nps;
17410 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17411 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17412 blackNPS = first.nps;
17413 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17414 whiteNPS = second.nps;
17415 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17416 blackNPS = second.nps;
17417 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17419 StartClockTimer(intendedTickLength);
17423 TimeString (long ms)
17425 long second, minute, hour, day;
17427 static char buf[32];
17429 if (ms > 0 && ms <= 9900) {
17430 /* convert milliseconds to tenths, rounding up */
17431 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17433 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17437 /* convert milliseconds to seconds, rounding up */
17438 /* use floating point to avoid strangeness of integer division
17439 with negative dividends on many machines */
17440 second = (long) floor(((double) (ms + 999L)) / 1000.0);
17447 day = second / (60 * 60 * 24);
17448 second = second % (60 * 60 * 24);
17449 hour = second / (60 * 60);
17450 second = second % (60 * 60);
17451 minute = second / 60;
17452 second = second % 60;
17455 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17456 sign, day, hour, minute, second);
17458 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17460 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17467 * This is necessary because some C libraries aren't ANSI C compliant yet.
17470 StrStr (char *string, char *match)
17474 length = strlen(match);
17476 for (i = strlen(string) - length; i >= 0; i--, string++)
17477 if (!strncmp(match, string, length))
17484 StrCaseStr (char *string, char *match)
17488 length = strlen(match);
17490 for (i = strlen(string) - length; i >= 0; i--, string++) {
17491 for (j = 0; j < length; j++) {
17492 if (ToLower(match[j]) != ToLower(string[j]))
17495 if (j == length) return string;
17503 StrCaseCmp (char *s1, char *s2)
17508 c1 = ToLower(*s1++);
17509 c2 = ToLower(*s2++);
17510 if (c1 > c2) return 1;
17511 if (c1 < c2) return -1;
17512 if (c1 == NULLCHAR) return 0;
17520 return isupper(c) ? tolower(c) : c;
17527 return islower(c) ? toupper(c) : c;
17529 #endif /* !_amigados */
17536 if ((ret = (char *) malloc(strlen(s) + 1)))
17538 safeStrCpy(ret, s, strlen(s)+1);
17544 StrSavePtr (char *s, char **savePtr)
17549 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17550 safeStrCpy(*savePtr, s, strlen(s)+1);
17562 clock = time((time_t *)NULL);
17563 tm = localtime(&clock);
17564 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17565 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17566 return StrSave(buf);
17571 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17573 int i, j, fromX, fromY, toX, toY;
17580 whiteToPlay = (gameMode == EditPosition) ?
17581 !blackPlaysFirst : (move % 2 == 0);
17584 /* Piece placement data */
17585 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17586 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17588 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17589 if (boards[move][i][j] == EmptySquare) {
17591 } else { ChessSquare piece = boards[move][i][j];
17592 if (emptycount > 0) {
17593 if(emptycount<10) /* [HGM] can be >= 10 */
17594 *p++ = '0' + emptycount;
17595 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17598 if(PieceToChar(piece) == '+') {
17599 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17601 piece = (ChessSquare)(DEMOTED piece);
17603 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17605 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17606 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17611 if (emptycount > 0) {
17612 if(emptycount<10) /* [HGM] can be >= 10 */
17613 *p++ = '0' + emptycount;
17614 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17621 /* [HGM] print Crazyhouse or Shogi holdings */
17622 if( gameInfo.holdingsWidth ) {
17623 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17625 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17626 piece = boards[move][i][BOARD_WIDTH-1];
17627 if( piece != EmptySquare )
17628 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17629 *p++ = PieceToChar(piece);
17631 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17632 piece = boards[move][BOARD_HEIGHT-i-1][0];
17633 if( piece != EmptySquare )
17634 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17635 *p++ = PieceToChar(piece);
17638 if( q == p ) *p++ = '-';
17644 *p++ = whiteToPlay ? 'w' : 'b';
17647 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17648 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17650 if(nrCastlingRights) {
17652 if(appData.fischerCastling) {
17653 /* [HGM] write directly from rights */
17654 if(boards[move][CASTLING][2] != NoRights &&
17655 boards[move][CASTLING][0] != NoRights )
17656 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17657 if(boards[move][CASTLING][2] != NoRights &&
17658 boards[move][CASTLING][1] != NoRights )
17659 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17660 if(boards[move][CASTLING][5] != NoRights &&
17661 boards[move][CASTLING][3] != NoRights )
17662 *p++ = boards[move][CASTLING][3] + AAA;
17663 if(boards[move][CASTLING][5] != NoRights &&
17664 boards[move][CASTLING][4] != NoRights )
17665 *p++ = boards[move][CASTLING][4] + AAA;
17668 /* [HGM] write true castling rights */
17669 if( nrCastlingRights == 6 ) {
17671 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17672 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
17673 q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17674 boards[move][CASTLING][2] != NoRights );
17675 if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17676 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17677 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17678 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17679 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17683 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17684 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
17685 q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17686 boards[move][CASTLING][5] != NoRights );
17687 if(gameInfo.variant == VariantSChess) {
17688 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17689 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17690 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17691 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17696 if (q == p) *p++ = '-'; /* No castling rights */
17700 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
17701 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17702 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17703 /* En passant target square */
17704 if (move > backwardMostMove) {
17705 fromX = moveList[move - 1][0] - AAA;
17706 fromY = moveList[move - 1][1] - ONE;
17707 toX = moveList[move - 1][2] - AAA;
17708 toY = moveList[move - 1][3] - ONE;
17709 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17710 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17711 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17713 /* 2-square pawn move just happened */
17715 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17719 } else if(move == backwardMostMove) {
17720 // [HGM] perhaps we should always do it like this, and forget the above?
17721 if((signed char)boards[move][EP_STATUS] >= 0) {
17722 *p++ = boards[move][EP_STATUS] + AAA;
17723 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17735 { int i = 0, j=move;
17737 /* [HGM] find reversible plies */
17738 if (appData.debugMode) { int k;
17739 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17740 for(k=backwardMostMove; k<=forwardMostMove; k++)
17741 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17745 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17746 if( j == backwardMostMove ) i += initialRulePlies;
17747 sprintf(p, "%d ", i);
17748 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17750 /* Fullmove number */
17751 sprintf(p, "%d", (move / 2) + 1);
17752 } else *--p = NULLCHAR;
17754 return StrSave(buf);
17758 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17760 int i, j, k, w=0, subst=0, shuffle=0;
17762 int emptycount, virgin[BOARD_FILES];
17767 /* Piece placement data */
17768 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17771 if (*p == '/' || *p == ' ' || *p == '[' ) {
17773 emptycount = gameInfo.boardWidth - j;
17774 while (emptycount--)
17775 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17776 if (*p == '/') p++;
17777 else if(autoSize) { // we stumbled unexpectedly into end of board
17778 for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17779 for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17781 appData.NrRanks = gameInfo.boardHeight - i; i=0;
17784 #if(BOARD_FILES >= 10)
17785 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17786 p++; emptycount=10;
17787 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17788 while (emptycount--)
17789 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17791 } else if (*p == '*') {
17792 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17793 } else if (isdigit(*p)) {
17794 emptycount = *p++ - '0';
17795 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17796 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17797 while (emptycount--)
17798 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17799 } else if (*p == '<') {
17800 if(i == BOARD_HEIGHT-1) shuffle = 1;
17801 else if (i != 0 || !shuffle) return FALSE;
17803 } else if (shuffle && *p == '>') {
17804 p++; // for now ignore closing shuffle range, and assume rank-end
17805 } else if (*p == '?') {
17806 if (j >= gameInfo.boardWidth) return FALSE;
17807 if (i != 0 && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
17808 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
17809 } else if (*p == '+' || isalpha(*p)) {
17810 if (j >= gameInfo.boardWidth) return FALSE;
17812 piece = CharToPiece(*++p);
17813 if(piece == EmptySquare) return FALSE; /* unknown piece */
17814 piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17815 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17816 } else piece = CharToPiece(*p++);
17818 if(piece==EmptySquare) return FALSE; /* unknown piece */
17819 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17820 piece = (ChessSquare) (PROMOTED piece);
17821 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17824 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17830 while (*p == '/' || *p == ' ') p++;
17832 if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17834 /* [HGM] by default clear Crazyhouse holdings, if present */
17835 if(gameInfo.holdingsWidth) {
17836 for(i=0; i<BOARD_HEIGHT; i++) {
17837 board[i][0] = EmptySquare; /* black holdings */
17838 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17839 board[i][1] = (ChessSquare) 0; /* black counts */
17840 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17844 /* [HGM] look for Crazyhouse holdings here */
17845 while(*p==' ') p++;
17846 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17847 int swap=0, wcnt=0, bcnt=0;
17849 if(*p == '<') swap++, p++;
17850 if(*p == '-' ) p++; /* empty holdings */ else {
17851 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17852 /* if we would allow FEN reading to set board size, we would */
17853 /* have to add holdings and shift the board read so far here */
17854 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17856 if((int) piece >= (int) BlackPawn ) {
17857 i = (int)piece - (int)BlackPawn;
17858 i = PieceToNumber((ChessSquare)i);
17859 if( i >= gameInfo.holdingsSize ) return FALSE;
17860 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17861 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
17864 i = (int)piece - (int)WhitePawn;
17865 i = PieceToNumber((ChessSquare)i);
17866 if( i >= gameInfo.holdingsSize ) return FALSE;
17867 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
17868 board[i][BOARD_WIDTH-2]++; /* black holdings */
17872 if(subst) { // substitute back-rank question marks by holdings pieces
17873 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
17874 int k, m, n = bcnt + 1;
17875 if(board[0][j] == ClearBoard) {
17876 if(!wcnt) return FALSE;
17878 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
17879 board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
17880 if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
17884 if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
17885 if(!bcnt) return FALSE;
17886 if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
17887 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
17888 board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
17889 if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
17900 if(subst) return FALSE; // substitution requested, but no holdings
17902 while(*p == ' ') p++;
17906 if(appData.colorNickNames) {
17907 if( c == appData.colorNickNames[0] ) c = 'w'; else
17908 if( c == appData.colorNickNames[1] ) c = 'b';
17912 *blackPlaysFirst = FALSE;
17915 *blackPlaysFirst = TRUE;
17921 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17922 /* return the extra info in global variiables */
17924 /* set defaults in case FEN is incomplete */
17925 board[EP_STATUS] = EP_UNKNOWN;
17926 for(i=0; i<nrCastlingRights; i++ ) {
17927 board[CASTLING][i] =
17928 appData.fischerCastling ? NoRights : initialRights[i];
17929 } /* assume possible unless obviously impossible */
17930 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17931 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17932 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17933 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17934 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17935 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17936 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17937 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17940 while(*p==' ') p++;
17941 if(nrCastlingRights) {
17943 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17944 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17945 /* castling indicator present, so default becomes no castlings */
17946 for(i=0; i<nrCastlingRights; i++ ) {
17947 board[CASTLING][i] = NoRights;
17950 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17951 (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
17952 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17953 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
17954 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17956 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17957 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17958 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
17960 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17961 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17962 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17963 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17964 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17965 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17968 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17969 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17970 board[CASTLING][2] = whiteKingFile;
17971 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17972 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17973 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
17976 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17977 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17978 board[CASTLING][2] = whiteKingFile;
17979 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17980 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17981 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
17984 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17985 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17986 board[CASTLING][5] = blackKingFile;
17987 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17988 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17989 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
17992 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17993 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17994 board[CASTLING][5] = blackKingFile;
17995 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17996 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17997 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18000 default: /* FRC castlings */
18001 if(c >= 'a') { /* black rights */
18002 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18003 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18004 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18005 if(i == BOARD_RGHT) break;
18006 board[CASTLING][5] = i;
18008 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
18009 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
18011 board[CASTLING][3] = c;
18013 board[CASTLING][4] = c;
18014 } else { /* white rights */
18015 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18016 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18017 if(board[0][i] == WhiteKing) break;
18018 if(i == BOARD_RGHT) break;
18019 board[CASTLING][2] = i;
18020 c -= AAA - 'a' + 'A';
18021 if(board[0][c] >= WhiteKing) break;
18023 board[CASTLING][0] = c;
18025 board[CASTLING][1] = c;
18029 for(i=0; i<nrCastlingRights; i++)
18030 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18031 if(gameInfo.variant == VariantSChess)
18032 for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18033 if(fischer && shuffle) appData.fischerCastling = TRUE;
18034 if (appData.debugMode) {
18035 fprintf(debugFP, "FEN castling rights:");
18036 for(i=0; i<nrCastlingRights; i++)
18037 fprintf(debugFP, " %d", board[CASTLING][i]);
18038 fprintf(debugFP, "\n");
18041 while(*p==' ') p++;
18044 if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18046 /* read e.p. field in games that know e.p. capture */
18047 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
18048 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18049 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18051 p++; board[EP_STATUS] = EP_NONE;
18053 char c = *p++ - AAA;
18055 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18056 if(*p >= '0' && *p <='9') p++;
18057 board[EP_STATUS] = c;
18062 if(sscanf(p, "%d", &i) == 1) {
18063 FENrulePlies = i; /* 50-move ply counter */
18064 /* (The move number is still ignored) */
18071 EditPositionPasteFEN (char *fen)
18074 Board initial_position;
18076 if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18077 DisplayError(_("Bad FEN position in clipboard"), 0);
18080 int savedBlackPlaysFirst = blackPlaysFirst;
18081 EditPositionEvent();
18082 blackPlaysFirst = savedBlackPlaysFirst;
18083 CopyBoard(boards[0], initial_position);
18084 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18085 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18086 DisplayBothClocks();
18087 DrawPosition(FALSE, boards[currentMove]);
18092 static char cseq[12] = "\\ ";
18095 set_cont_sequence (char *new_seq)
18100 // handle bad attempts to set the sequence
18102 return 0; // acceptable error - no debug
18104 len = strlen(new_seq);
18105 ret = (len > 0) && (len < sizeof(cseq));
18107 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18108 else if (appData.debugMode)
18109 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18114 reformat a source message so words don't cross the width boundary. internal
18115 newlines are not removed. returns the wrapped size (no null character unless
18116 included in source message). If dest is NULL, only calculate the size required
18117 for the dest buffer. lp argument indicats line position upon entry, and it's
18118 passed back upon exit.
18121 wrap (char *dest, char *src, int count, int width, int *lp)
18123 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18125 cseq_len = strlen(cseq);
18126 old_line = line = *lp;
18127 ansi = len = clen = 0;
18129 for (i=0; i < count; i++)
18131 if (src[i] == '\033')
18134 // if we hit the width, back up
18135 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18137 // store i & len in case the word is too long
18138 old_i = i, old_len = len;
18140 // find the end of the last word
18141 while (i && src[i] != ' ' && src[i] != '\n')
18147 // word too long? restore i & len before splitting it
18148 if ((old_i-i+clen) >= width)
18155 if (i && src[i-1] == ' ')
18158 if (src[i] != ' ' && src[i] != '\n')
18165 // now append the newline and continuation sequence
18170 strncpy(dest+len, cseq, cseq_len);
18178 dest[len] = src[i];
18182 if (src[i] == '\n')
18187 if (dest && appData.debugMode)
18189 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18190 count, width, line, len, *lp);
18191 show_bytes(debugFP, src, count);
18192 fprintf(debugFP, "\ndest: ");
18193 show_bytes(debugFP, dest, len);
18194 fprintf(debugFP, "\n");
18196 *lp = dest ? line : old_line;
18201 // [HGM] vari: routines for shelving variations
18202 Boolean modeRestore = FALSE;
18205 PushInner (int firstMove, int lastMove)
18207 int i, j, nrMoves = lastMove - firstMove;
18209 // push current tail of game on stack
18210 savedResult[storedGames] = gameInfo.result;
18211 savedDetails[storedGames] = gameInfo.resultDetails;
18212 gameInfo.resultDetails = NULL;
18213 savedFirst[storedGames] = firstMove;
18214 savedLast [storedGames] = lastMove;
18215 savedFramePtr[storedGames] = framePtr;
18216 framePtr -= nrMoves; // reserve space for the boards
18217 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18218 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18219 for(j=0; j<MOVE_LEN; j++)
18220 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18221 for(j=0; j<2*MOVE_LEN; j++)
18222 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18223 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18224 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18225 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18226 pvInfoList[firstMove+i-1].depth = 0;
18227 commentList[framePtr+i] = commentList[firstMove+i];
18228 commentList[firstMove+i] = NULL;
18232 forwardMostMove = firstMove; // truncate game so we can start variation
18236 PushTail (int firstMove, int lastMove)
18238 if(appData.icsActive) { // only in local mode
18239 forwardMostMove = currentMove; // mimic old ICS behavior
18242 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18244 PushInner(firstMove, lastMove);
18245 if(storedGames == 1) GreyRevert(FALSE);
18246 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18250 PopInner (Boolean annotate)
18253 char buf[8000], moveBuf[20];
18255 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18256 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18257 nrMoves = savedLast[storedGames] - currentMove;
18260 if(!WhiteOnMove(currentMove))
18261 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18262 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18263 for(i=currentMove; i<forwardMostMove; i++) {
18265 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18266 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18267 strcat(buf, moveBuf);
18268 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18269 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18273 for(i=1; i<=nrMoves; i++) { // copy last variation back
18274 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18275 for(j=0; j<MOVE_LEN; j++)
18276 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18277 for(j=0; j<2*MOVE_LEN; j++)
18278 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18279 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18280 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18281 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18282 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18283 commentList[currentMove+i] = commentList[framePtr+i];
18284 commentList[framePtr+i] = NULL;
18286 if(annotate) AppendComment(currentMove+1, buf, FALSE);
18287 framePtr = savedFramePtr[storedGames];
18288 gameInfo.result = savedResult[storedGames];
18289 if(gameInfo.resultDetails != NULL) {
18290 free(gameInfo.resultDetails);
18292 gameInfo.resultDetails = savedDetails[storedGames];
18293 forwardMostMove = currentMove + nrMoves;
18297 PopTail (Boolean annotate)
18299 if(appData.icsActive) return FALSE; // only in local mode
18300 if(!storedGames) return FALSE; // sanity
18301 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18303 PopInner(annotate);
18304 if(currentMove < forwardMostMove) ForwardEvent(); else
18305 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18307 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18313 { // remove all shelved variations
18315 for(i=0; i<storedGames; i++) {
18316 if(savedDetails[i])
18317 free(savedDetails[i]);
18318 savedDetails[i] = NULL;
18320 for(i=framePtr; i<MAX_MOVES; i++) {
18321 if(commentList[i]) free(commentList[i]);
18322 commentList[i] = NULL;
18324 framePtr = MAX_MOVES-1;
18329 LoadVariation (int index, char *text)
18330 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18331 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18332 int level = 0, move;
18334 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18335 // first find outermost bracketing variation
18336 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18337 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18338 if(*p == '{') wait = '}'; else
18339 if(*p == '[') wait = ']'; else
18340 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18341 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18343 if(*p == wait) wait = NULLCHAR; // closing ]} found
18346 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18347 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18348 end[1] = NULLCHAR; // clip off comment beyond variation
18349 ToNrEvent(currentMove-1);
18350 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18351 // kludge: use ParsePV() to append variation to game
18352 move = currentMove;
18353 ParsePV(start, TRUE, TRUE);
18354 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18355 ClearPremoveHighlights();
18357 ToNrEvent(currentMove+1);
18363 char *p, *q, buf[MSG_SIZ];
18364 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18365 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18366 ParseArgsFromString(buf);
18367 ActivateTheme(TRUE); // also redo colors
18371 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18374 q = appData.themeNames;
18375 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18376 if(appData.useBitmaps) {
18377 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18378 appData.liteBackTextureFile, appData.darkBackTextureFile,
18379 appData.liteBackTextureMode,
18380 appData.darkBackTextureMode );
18382 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18383 Col2Text(2), // lightSquareColor
18384 Col2Text(3) ); // darkSquareColor
18386 if(appData.useBorder) {
18387 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18390 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18392 if(appData.useFont) {
18393 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18394 appData.renderPiecesWithFont,
18395 appData.fontToPieceTable,
18396 Col2Text(9), // appData.fontBackColorWhite
18397 Col2Text(10) ); // appData.fontForeColorBlack
18399 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18400 appData.pieceDirectory);
18401 if(!appData.pieceDirectory[0])
18402 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18403 Col2Text(0), // whitePieceColor
18404 Col2Text(1) ); // blackPieceColor
18406 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18407 Col2Text(4), // highlightSquareColor
18408 Col2Text(5) ); // premoveHighlightColor
18409 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18410 if(insert != q) insert[-1] = NULLCHAR;
18411 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18414 ActivateTheme(FALSE);