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, 2015, 2016 Free
9 * Software Foundation, Inc.
11 * Enhancements Copyright 2005 Alessandro Scotti
13 * The following terms apply to Digital Equipment Corporation's copyright
15 * ------------------------------------------------------------------------
18 * Permission to use, copy, modify, and distribute this software and its
19 * documentation for any purpose and without fee is hereby granted,
20 * provided that the above copyright notice appear in all copies and that
21 * both that copyright notice and this permission notice appear in
22 * supporting documentation, and that the name of Digital not be
23 * used in advertising or publicity pertaining to distribution of the
24 * software without specific, written prior permission.
26 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
27 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
28 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
29 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
30 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
31 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
33 * ------------------------------------------------------------------------
35 * The following terms apply to the enhanced version of XBoard
36 * distributed by the Free Software Foundation:
37 * ------------------------------------------------------------------------
39 * GNU XBoard is free software: you can redistribute it and/or modify
40 * it under the terms of the GNU General Public License as published by
41 * the Free Software Foundation, either version 3 of the License, or (at
42 * your option) any later version.
44 * GNU XBoard is distributed in the hope that it will be useful, but
45 * WITHOUT ANY WARRANTY; without even the implied warranty of
46 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
47 * General Public License for more details.
49 * You should have received a copy of the GNU General Public License
50 * along with this program. If not, see http://www.gnu.org/licenses/. *
52 *------------------------------------------------------------------------
53 ** See the file ChangeLog for a revision history. */
55 /* [AS] Also useful here for debugging */
59 int flock(int f, int code);
64 # define EGBB_NAME "egbbdll64.dll"
66 # define EGBB_NAME "egbbdll.dll"
71 # include <sys/file.h>
76 # define EGBB_NAME "egbbso64.so"
78 # define EGBB_NAME "egbbso.so"
80 // kludge to allow Windows code in back-end by converting it to corresponding Linux code
82 # define HMODULE void *
83 # define LoadLibrary(x) dlopen(x, RTLD_LAZY)
84 # define GetProcAddress dlsym
94 #include <sys/types.h>
103 #else /* not STDC_HEADERS */
106 # else /* not HAVE_STRING_H */
107 # include <strings.h>
108 # endif /* not HAVE_STRING_H */
109 #endif /* not STDC_HEADERS */
112 # include <sys/fcntl.h>
113 #else /* not HAVE_SYS_FCNTL_H */
116 # endif /* HAVE_FCNTL_H */
117 #endif /* not HAVE_SYS_FCNTL_H */
119 #if TIME_WITH_SYS_TIME
120 # include <sys/time.h>
124 # include <sys/time.h>
130 #if defined(_amigados) && !defined(__GNUC__)
135 extern int gettimeofday(struct timeval *, struct timezone *);
143 #include "frontend.h"
150 #include "backendz.h"
151 #include "evalgraph.h"
152 #include "engineoutput.h"
156 # define _(s) gettext (s)
157 # define N_(s) gettext_noop (s)
158 # define T_(s) gettext(s)
171 int establish P((void));
172 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
173 char *buf, int count, int error));
174 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
175 char *buf, int count, int error));
176 void SendToICS P((char *s));
177 void SendToICSDelayed P((char *s, long msdelay));
178 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
179 void HandleMachineMove P((char *message, ChessProgramState *cps));
180 int AutoPlayOneMove P((void));
181 int LoadGameOneMove P((ChessMove readAhead));
182 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
183 int LoadPositionFromFile P((char *filename, int n, char *title));
184 int SavePositionToFile P((char *filename));
185 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
186 void ShowMove P((int fromX, int fromY, int toX, int toY));
187 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
188 /*char*/int promoChar));
189 void BackwardInner P((int target));
190 void ForwardInner P((int target));
191 int Adjudicate P((ChessProgramState *cps));
192 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
193 void EditPositionDone P((Boolean fakeRights));
194 void PrintOpponents P((FILE *fp));
195 void PrintPosition P((FILE *fp, int move));
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;
298 int border; /* [HGM] width of board rim, needed to size seek graph */
299 char bestMove[MSG_SIZ], avoidMove[MSG_SIZ];
300 int solvingTime, totalTime;
302 /* States for ics_getting_history */
304 #define H_REQUESTED 1
305 #define H_GOT_REQ_HEADER 2
306 #define H_GOT_UNREQ_HEADER 3
307 #define H_GETTING_MOVES 4
308 #define H_GOT_UNWANTED_HEADER 5
310 /* whosays values for GameEnds */
319 /* Maximum number of games in a cmail message */
320 #define CMAIL_MAX_GAMES 20
322 /* Different types of move when calling RegisterMove */
324 #define CMAIL_RESIGN 1
326 #define CMAIL_ACCEPT 3
328 /* Different types of result to remember for each game */
329 #define CMAIL_NOT_RESULT 0
330 #define CMAIL_OLD_RESULT 1
331 #define CMAIL_NEW_RESULT 2
333 /* Telnet protocol constants */
344 safeStrCpy (char *dst, const char *src, size_t count)
347 assert( dst != NULL );
348 assert( src != NULL );
351 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
352 if( i == count && dst[count-1] != NULLCHAR)
354 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
355 if(appData.debugMode)
356 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
362 /* Some compiler can't cast u64 to double
363 * This function do the job for us:
365 * We use the highest bit for cast, this only
366 * works if the highest bit is not
367 * in use (This should not happen)
369 * We used this for all compiler
372 u64ToDouble (u64 value)
375 u64 tmp = value & u64Const(0x7fffffffffffffff);
376 r = (double)(s64)tmp;
377 if (value & u64Const(0x8000000000000000))
378 r += 9.2233720368547758080e18; /* 2^63 */
382 /* Fake up flags for now, as we aren't keeping track of castling
383 availability yet. [HGM] Change of logic: the flag now only
384 indicates the type of castlings allowed by the rule of the game.
385 The actual rights themselves are maintained in the array
386 castlingRights, as part of the game history, and are not probed
392 int flags = F_ALL_CASTLE_OK;
393 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
394 switch (gameInfo.variant) {
396 flags &= ~F_ALL_CASTLE_OK;
397 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
398 flags |= F_IGNORE_CHECK;
400 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
403 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
405 case VariantKriegspiel:
406 flags |= F_KRIEGSPIEL_CAPTURE;
408 case VariantCapaRandom:
409 case VariantFischeRandom:
410 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
411 case VariantNoCastle:
412 case VariantShatranj:
417 flags &= ~F_ALL_CASTLE_OK;
420 case VariantChuChess:
422 flags |= F_NULL_MOVE;
427 if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
431 FILE *gameFileFP, *debugFP, *serverFP;
432 char *currentDebugFile; // [HGM] debug split: to remember name
435 [AS] Note: sometimes, the sscanf() function is used to parse the input
436 into a fixed-size buffer. Because of this, we must be prepared to
437 receive strings as long as the size of the input buffer, which is currently
438 set to 4K for Windows and 8K for the rest.
439 So, we must either allocate sufficiently large buffers here, or
440 reduce the size of the input buffer in the input reading part.
443 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
444 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
445 char thinkOutput1[MSG_SIZ*10];
446 char promoRestrict[MSG_SIZ];
448 ChessProgramState first, second, pairing;
450 /* premove variables */
453 int premoveFromX = 0;
454 int premoveFromY = 0;
455 int premovePromoChar = 0;
457 Boolean alarmSounded;
458 /* end premove variables */
460 char *ics_prefix = "$";
461 enum ICS_TYPE ics_type = ICS_GENERIC;
463 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
464 int pauseExamForwardMostMove = 0;
465 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
466 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
467 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
468 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
469 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
470 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
471 int whiteFlag = FALSE, blackFlag = FALSE;
472 int userOfferedDraw = FALSE;
473 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
474 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
475 int cmailMoveType[CMAIL_MAX_GAMES];
476 long ics_clock_paused = 0;
477 ProcRef icsPR = NoProc, cmailPR = NoProc;
478 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
479 GameMode gameMode = BeginningOfGame;
480 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
481 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
482 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
483 int hiddenThinkOutputState = 0; /* [AS] */
484 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
485 int adjudicateLossPlies = 6;
486 char white_holding[64], black_holding[64];
487 TimeMark lastNodeCountTime;
488 long lastNodeCount=0;
489 int shiftKey, controlKey; // [HGM] set by mouse handler
491 int have_sent_ICS_logon = 0;
493 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
494 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
495 Boolean adjustedClock;
496 long timeControl_2; /* [AS] Allow separate time controls */
497 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
498 long timeRemaining[2][MAX_MOVES];
499 int matchGame = 0, nextGame = 0, roundNr = 0;
500 Boolean waitingForGame = FALSE, startingEngine = FALSE;
501 TimeMark programStartTime, pauseStart;
502 char ics_handle[MSG_SIZ];
503 int have_set_title = 0;
505 /* animateTraining preserves the state of appData.animate
506 * when Training mode is activated. This allows the
507 * response to be animated when appData.animate == TRUE and
508 * appData.animateDragging == TRUE.
510 Boolean animateTraining;
516 Board boards[MAX_MOVES];
517 /* [HGM] Following 7 needed for accurate legality tests: */
518 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
519 unsigned char initialRights[BOARD_FILES];
520 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
521 int initialRulePlies, FENrulePlies;
522 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
524 Boolean shuffleOpenings;
525 int mute; // mute all sounds
527 // [HGM] vari: next 12 to save and restore variations
528 #define MAX_VARIATIONS 10
529 int framePtr = MAX_MOVES-1; // points to free stack entry
531 int savedFirst[MAX_VARIATIONS];
532 int savedLast[MAX_VARIATIONS];
533 int savedFramePtr[MAX_VARIATIONS];
534 char *savedDetails[MAX_VARIATIONS];
535 ChessMove savedResult[MAX_VARIATIONS];
537 void PushTail P((int firstMove, int lastMove));
538 Boolean PopTail P((Boolean annotate));
539 void PushInner P((int firstMove, int lastMove));
540 void PopInner P((Boolean annotate));
541 void CleanupTail P((void));
543 ChessSquare FIDEArray[2][BOARD_FILES] = {
544 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
545 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
546 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
547 BlackKing, BlackBishop, BlackKnight, BlackRook }
550 ChessSquare twoKingsArray[2][BOARD_FILES] = {
551 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
552 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
553 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
554 BlackKing, BlackKing, BlackKnight, BlackRook }
557 ChessSquare KnightmateArray[2][BOARD_FILES] = {
558 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
559 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
560 { BlackRook, BlackMan, BlackBishop, BlackQueen,
561 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
564 ChessSquare SpartanArray[2][BOARD_FILES] = {
565 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
566 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
567 { BlackAlfil, BlackDragon, BlackKing, BlackTower,
568 BlackTower, BlackKing, BlackAngel, BlackAlfil }
571 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
572 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
573 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
574 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
575 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
578 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
579 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
580 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
581 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
582 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
585 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
586 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
587 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
588 { BlackRook, BlackKnight, BlackMan, BlackFerz,
589 BlackKing, BlackMan, BlackKnight, BlackRook }
592 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
593 { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
594 WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
595 { BlackRook, BlackKnight, BlackMan, BlackFerz,
596 BlackKing, BlackMan, BlackKnight, BlackRook }
599 ChessSquare lionArray[2][BOARD_FILES] = {
600 { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
601 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
602 { BlackRook, BlackLion, BlackBishop, BlackQueen,
603 BlackKing, BlackBishop, BlackKnight, BlackRook }
607 #if (BOARD_FILES>=10)
608 ChessSquare ShogiArray[2][BOARD_FILES] = {
609 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
610 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
611 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
612 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
615 ChessSquare XiangqiArray[2][BOARD_FILES] = {
616 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
617 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
618 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
619 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
622 ChessSquare CapablancaArray[2][BOARD_FILES] = {
623 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
624 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
625 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
626 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
629 ChessSquare GreatArray[2][BOARD_FILES] = {
630 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
631 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
632 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
633 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
636 ChessSquare JanusArray[2][BOARD_FILES] = {
637 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
638 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
639 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
640 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
643 ChessSquare GrandArray[2][BOARD_FILES] = {
644 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
645 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
646 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
647 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
650 ChessSquare ChuChessArray[2][BOARD_FILES] = {
651 { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
652 WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
653 { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
654 BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
658 ChessSquare GothicArray[2][BOARD_FILES] = {
659 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
660 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
661 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
662 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
665 #define GothicArray CapablancaArray
669 ChessSquare FalconArray[2][BOARD_FILES] = {
670 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
671 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
672 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
673 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
676 #define FalconArray CapablancaArray
679 #else // !(BOARD_FILES>=10)
680 #define XiangqiPosition FIDEArray
681 #define CapablancaArray FIDEArray
682 #define GothicArray FIDEArray
683 #define GreatArray FIDEArray
684 #endif // !(BOARD_FILES>=10)
686 #if (BOARD_FILES>=12)
687 ChessSquare CourierArray[2][BOARD_FILES] = {
688 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
689 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
690 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
691 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
693 ChessSquare ChuArray[6][BOARD_FILES] = {
694 { WhiteLance, WhiteCat, WhiteCopper, WhiteFerz, WhiteWazir, WhiteKing,
695 WhiteAlfil, WhiteWazir, WhiteFerz, WhiteCopper, WhiteCat, WhiteLance },
696 { BlackLance, BlackCat, BlackCopper, BlackFerz, BlackWazir, BlackAlfil,
697 BlackKing, BlackWazir, BlackFerz, BlackCopper, BlackCat, BlackLance },
698 { WhiteAxe, EmptySquare, WhiteBishop, EmptySquare, WhiteClaw, WhiteMarshall,
699 WhiteAngel, WhiteClaw, EmptySquare, WhiteBishop, EmptySquare, WhiteAxe },
700 { BlackAxe, EmptySquare, BlackBishop, EmptySquare, BlackClaw, BlackAngel,
701 BlackMarshall, BlackClaw, EmptySquare, BlackBishop, EmptySquare, BlackAxe },
702 { WhiteDagger, WhiteSword, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
703 WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSword, WhiteDagger },
704 { BlackDagger, BlackSword, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
705 BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSword, BlackDagger }
707 #else // !(BOARD_FILES>=12)
708 #define CourierArray CapablancaArray
709 #define ChuArray CapablancaArray
710 #endif // !(BOARD_FILES>=12)
713 Board initialPosition;
716 /* Convert str to a rating. Checks for special cases of "----",
718 "++++", etc. Also strips ()'s */
720 string_to_rating (char *str)
722 while(*str && !isdigit(*str)) ++str;
724 return 0; /* One of the special "no rating" cases */
732 /* Init programStats */
733 programStats.movelist[0] = 0;
734 programStats.depth = 0;
735 programStats.nr_moves = 0;
736 programStats.moves_left = 0;
737 programStats.nodes = 0;
738 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
739 programStats.score = 0;
740 programStats.got_only_move = 0;
741 programStats.got_fail = 0;
742 programStats.line_is_book = 0;
747 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
748 if (appData.firstPlaysBlack) {
749 first.twoMachinesColor = "black\n";
750 second.twoMachinesColor = "white\n";
752 first.twoMachinesColor = "white\n";
753 second.twoMachinesColor = "black\n";
756 first.other = &second;
757 second.other = &first;
760 if(appData.timeOddsMode) {
761 norm = appData.timeOdds[0];
762 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
764 first.timeOdds = appData.timeOdds[0]/norm;
765 second.timeOdds = appData.timeOdds[1]/norm;
768 if(programVersion) free(programVersion);
769 if (appData.noChessProgram) {
770 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
771 sprintf(programVersion, "%s", PACKAGE_STRING);
773 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
774 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
775 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
780 UnloadEngine (ChessProgramState *cps)
782 /* Kill off first chess program */
783 if (cps->isr != NULL)
784 RemoveInputSource(cps->isr);
787 if (cps->pr != NoProc) {
789 DoSleep( appData.delayBeforeQuit );
790 SendToProgram("quit\n", cps);
791 DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
794 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
798 ClearOptions (ChessProgramState *cps)
801 cps->nrOptions = cps->comboCnt = 0;
802 for(i=0; i<MAX_OPTIONS; i++) {
803 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
804 cps->option[i].textValue = 0;
808 char *engineNames[] = {
809 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
810 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
812 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
813 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
818 InitEngine (ChessProgramState *cps, int n)
819 { // [HGM] all engine initialiation put in a function that does one engine
823 cps->which = engineNames[n];
824 cps->maybeThinking = FALSE;
828 cps->sendDrawOffers = 1;
830 cps->program = appData.chessProgram[n];
831 cps->host = appData.host[n];
832 cps->dir = appData.directory[n];
833 cps->initString = appData.engInitString[n];
834 cps->computerString = appData.computerString[n];
835 cps->useSigint = TRUE;
836 cps->useSigterm = TRUE;
837 cps->reuse = appData.reuse[n];
838 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
839 cps->useSetboard = FALSE;
841 cps->usePing = FALSE;
844 cps->usePlayother = FALSE;
845 cps->useColors = TRUE;
846 cps->useUsermove = FALSE;
847 cps->sendICS = FALSE;
848 cps->sendName = appData.icsActive;
849 cps->sdKludge = FALSE;
850 cps->stKludge = FALSE;
851 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
852 TidyProgramName(cps->program, cps->host, cps->tidy);
854 ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
855 cps->analysisSupport = 2; /* detect */
856 cps->analyzing = FALSE;
857 cps->initDone = FALSE;
859 cps->pseudo = appData.pseudo[n];
861 /* New features added by Tord: */
862 cps->useFEN960 = FALSE;
863 cps->useOOCastle = TRUE;
864 /* End of new features added by Tord. */
865 cps->fenOverride = appData.fenOverride[n];
867 /* [HGM] time odds: set factor for each machine */
868 cps->timeOdds = appData.timeOdds[n];
870 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
871 cps->accumulateTC = appData.accumulateTC[n];
872 cps->maxNrOfSessions = 1;
877 cps->drawDepth = appData.drawDepth[n];
878 cps->supportsNPS = UNKNOWN;
879 cps->memSize = FALSE;
880 cps->maxCores = FALSE;
881 ASSIGN(cps->egtFormats, "");
884 cps->optionSettings = appData.engOptions[n];
886 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
887 cps->isUCI = appData.isUCI[n]; /* [AS] */
888 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
891 if (appData.protocolVersion[n] > PROTOVER
892 || appData.protocolVersion[n] < 1)
897 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
898 appData.protocolVersion[n]);
899 if( (len >= MSG_SIZ) && appData.debugMode )
900 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
902 DisplayFatalError(buf, 0, 2);
906 cps->protocolVersion = appData.protocolVersion[n];
909 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
910 ParseFeatures(appData.featureDefaults, cps);
913 ChessProgramState *savCps;
921 if(WaitForEngine(savCps, LoadEngine)) return;
922 CommonEngineInit(); // recalculate time odds
923 if(gameInfo.variant != StringToVariant(appData.variant)) {
924 // we changed variant when loading the engine; this forces us to reset
925 Reset(TRUE, savCps != &first);
926 oldMode = BeginningOfGame; // to prevent restoring old mode
928 InitChessProgram(savCps, FALSE);
929 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
930 DisplayMessage("", "");
931 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
932 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
935 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
939 ReplaceEngine (ChessProgramState *cps, int n)
941 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
943 if(oldMode != BeginningOfGame) EditGameEvent();
946 appData.noChessProgram = FALSE;
947 appData.clockMode = TRUE;
950 if(n) return; // only startup first engine immediately; second can wait
951 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
955 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
956 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
958 static char resetOptions[] =
959 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
960 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
961 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
962 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
965 FloatToFront(char **list, char *engineLine)
967 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
969 if(appData.recentEngines <= 0) return;
970 TidyProgramName(engineLine, "localhost", tidy+1);
971 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
972 strncpy(buf+1, *list, MSG_SIZ-50);
973 if(p = strstr(buf, tidy)) { // tidy name appears in list
974 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
975 while(*p++ = *++q); // squeeze out
977 strcat(tidy, buf+1); // put list behind tidy name
978 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
979 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
980 ASSIGN(*list, tidy+1);
983 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
986 Load (ChessProgramState *cps, int i)
988 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
989 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
990 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
991 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
992 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
993 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
994 appData.firstProtocolVersion = PROTOVER;
995 ParseArgsFromString(buf);
997 ReplaceEngine(cps, i);
998 FloatToFront(&appData.recentEngineList, engineLine);
999 if(gameMode == BeginningOfGame) Reset(TRUE, TRUE);
1003 while(q = strchr(p, SLASH)) p = q+1;
1004 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1005 if(engineDir[0] != NULLCHAR) {
1006 ASSIGN(appData.directory[i], engineDir); p = engineName;
1007 } else if(p != engineName) { // derive directory from engine path, when not given
1009 ASSIGN(appData.directory[i], engineName);
1011 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1012 } else { ASSIGN(appData.directory[i], "."); }
1013 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1015 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1016 snprintf(command, MSG_SIZ, "%s %s", p, params);
1019 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1020 ASSIGN(appData.chessProgram[i], p);
1021 appData.isUCI[i] = isUCI;
1022 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1023 appData.hasOwnBookUCI[i] = hasBook;
1024 if(!nickName[0]) useNick = FALSE;
1025 if(useNick) ASSIGN(appData.pgnName[i], nickName);
1029 q = firstChessProgramNames;
1030 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1031 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1032 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1033 quote, p, quote, appData.directory[i],
1034 useNick ? " -fn \"" : "",
1035 useNick ? nickName : "",
1036 useNick ? "\"" : "",
1037 v1 ? " -firstProtocolVersion 1" : "",
1038 hasBook ? "" : " -fNoOwnBookUCI",
1039 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1040 storeVariant ? " -variant " : "",
1041 storeVariant ? VariantName(gameInfo.variant) : "");
1042 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1043 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1044 if(insert != q) insert[-1] = NULLCHAR;
1045 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1047 FloatToFront(&appData.recentEngineList, buf);
1049 ReplaceEngine(cps, i);
1055 int matched, min, sec;
1057 * Parse timeControl resource
1059 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1060 appData.movesPerSession)) {
1062 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1063 DisplayFatalError(buf, 0, 2);
1067 * Parse searchTime resource
1069 if (*appData.searchTime != NULLCHAR) {
1070 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1072 searchTime = min * 60;
1073 } else if (matched == 2) {
1074 searchTime = min * 60 + sec;
1077 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1078 DisplayFatalError(buf, 0, 2);
1087 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1088 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1090 GetTimeMark(&programStartTime);
1091 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1092 appData.seedBase = random() + (random()<<15);
1093 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1095 ClearProgramStats();
1096 programStats.ok_to_send = 1;
1097 programStats.seen_stat = 0;
1100 * Initialize game list
1106 * Internet chess server status
1108 if (appData.icsActive) {
1109 appData.matchMode = FALSE;
1110 appData.matchGames = 0;
1112 appData.noChessProgram = !appData.zippyPlay;
1114 appData.zippyPlay = FALSE;
1115 appData.zippyTalk = FALSE;
1116 appData.noChessProgram = TRUE;
1118 if (*appData.icsHelper != NULLCHAR) {
1119 appData.useTelnet = TRUE;
1120 appData.telnetProgram = appData.icsHelper;
1123 appData.zippyTalk = appData.zippyPlay = FALSE;
1126 /* [AS] Initialize pv info list [HGM] and game state */
1130 for( i=0; i<=framePtr; i++ ) {
1131 pvInfoList[i].depth = -1;
1132 boards[i][EP_STATUS] = EP_NONE;
1133 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1139 /* [AS] Adjudication threshold */
1140 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1142 InitEngine(&first, 0);
1143 InitEngine(&second, 1);
1146 pairing.which = "pairing"; // pairing engine
1147 pairing.pr = NoProc;
1149 pairing.program = appData.pairingEngine;
1150 pairing.host = "localhost";
1153 if (appData.icsActive) {
1154 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1155 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1156 appData.clockMode = FALSE;
1157 first.sendTime = second.sendTime = 0;
1161 /* Override some settings from environment variables, for backward
1162 compatibility. Unfortunately it's not feasible to have the env
1163 vars just set defaults, at least in xboard. Ugh.
1165 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1170 if (!appData.icsActive) {
1174 /* Check for variants that are supported only in ICS mode,
1175 or not at all. Some that are accepted here nevertheless
1176 have bugs; see comments below.
1178 VariantClass variant = StringToVariant(appData.variant);
1180 case VariantBughouse: /* need four players and two boards */
1181 case VariantKriegspiel: /* need to hide pieces and move details */
1182 /* case VariantFischeRandom: (Fabien: moved below) */
1183 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1184 if( (len >= MSG_SIZ) && appData.debugMode )
1185 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1187 DisplayFatalError(buf, 0, 2);
1190 case VariantUnknown:
1191 case VariantLoadable:
1201 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1202 if( (len >= MSG_SIZ) && appData.debugMode )
1203 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1205 DisplayFatalError(buf, 0, 2);
1208 case VariantNormal: /* definitely works! */
1209 if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1210 safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1213 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1214 case VariantFairy: /* [HGM] TestLegality definitely off! */
1215 case VariantGothic: /* [HGM] should work */
1216 case VariantCapablanca: /* [HGM] should work */
1217 case VariantCourier: /* [HGM] initial forced moves not implemented */
1218 case VariantShogi: /* [HGM] could still mate with pawn drop */
1219 case VariantChu: /* [HGM] experimental */
1220 case VariantKnightmate: /* [HGM] should work */
1221 case VariantCylinder: /* [HGM] untested */
1222 case VariantFalcon: /* [HGM] untested */
1223 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1224 offboard interposition not understood */
1225 case VariantWildCastle: /* pieces not automatically shuffled */
1226 case VariantNoCastle: /* pieces not automatically shuffled */
1227 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1228 case VariantLosers: /* should work except for win condition,
1229 and doesn't know captures are mandatory */
1230 case VariantSuicide: /* should work except for win condition,
1231 and doesn't know captures are mandatory */
1232 case VariantGiveaway: /* should work except for win condition,
1233 and doesn't know captures are mandatory */
1234 case VariantTwoKings: /* should work */
1235 case VariantAtomic: /* should work except for win condition */
1236 case Variant3Check: /* should work except for win condition */
1237 case VariantShatranj: /* should work except for all win conditions */
1238 case VariantMakruk: /* should work except for draw countdown */
1239 case VariantASEAN : /* should work except for draw countdown */
1240 case VariantBerolina: /* might work if TestLegality is off */
1241 case VariantCapaRandom: /* should work */
1242 case VariantJanus: /* should work */
1243 case VariantSuper: /* experimental */
1244 case VariantGreat: /* experimental, requires legality testing to be off */
1245 case VariantSChess: /* S-Chess, should work */
1246 case VariantGrand: /* should work */
1247 case VariantSpartan: /* should work */
1248 case VariantLion: /* should work */
1249 case VariantChuChess: /* should work */
1257 NextIntegerFromString (char ** str, long * value)
1262 while( *s == ' ' || *s == '\t' ) {
1268 if( *s >= '0' && *s <= '9' ) {
1269 while( *s >= '0' && *s <= '9' ) {
1270 *value = *value * 10 + (*s - '0');
1283 NextTimeControlFromString (char ** str, long * value)
1286 int result = NextIntegerFromString( str, &temp );
1289 *value = temp * 60; /* Minutes */
1290 if( **str == ':' ) {
1292 result = NextIntegerFromString( str, &temp );
1293 *value += temp; /* Seconds */
1301 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1302 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1303 int result = -1, type = 0; long temp, temp2;
1305 if(**str != ':') return -1; // old params remain in force!
1307 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1308 if( NextIntegerFromString( str, &temp ) ) return -1;
1309 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1312 /* time only: incremental or sudden-death time control */
1313 if(**str == '+') { /* increment follows; read it */
1315 if(**str == '!') type = *(*str)++; // Bronstein TC
1316 if(result = NextIntegerFromString( str, &temp2)) return -1;
1317 *inc = temp2 * 1000;
1318 if(**str == '.') { // read fraction of increment
1319 char *start = ++(*str);
1320 if(result = NextIntegerFromString( str, &temp2)) return -1;
1322 while(start++ < *str) temp2 /= 10;
1326 *moves = 0; *tc = temp * 1000; *incType = type;
1330 (*str)++; /* classical time control */
1331 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1343 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1344 { /* [HGM] get time to add from the multi-session time-control string */
1345 int incType, moves=1; /* kludge to force reading of first session */
1346 long time, increment;
1349 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1351 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1352 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1353 if(movenr == -1) return time; /* last move before new session */
1354 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1355 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1356 if(!moves) return increment; /* current session is incremental */
1357 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1358 } while(movenr >= -1); /* try again for next session */
1360 return 0; // no new time quota on this move
1364 ParseTimeControl (char *tc, float ti, int mps)
1368 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1371 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1372 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1373 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1377 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1379 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1382 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1384 snprintf(buf, MSG_SIZ, ":%s", mytc);
1386 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1388 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1393 /* Parse second time control */
1396 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1404 timeControl_2 = tc2 * 1000;
1414 timeControl = tc1 * 1000;
1417 timeIncrement = ti * 1000; /* convert to ms */
1418 movesPerSession = 0;
1421 movesPerSession = mps;
1429 if (appData.debugMode) {
1430 # ifdef __GIT_VERSION
1431 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1433 fprintf(debugFP, "Version: %s\n", programVersion);
1436 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1438 set_cont_sequence(appData.wrapContSeq);
1439 if (appData.matchGames > 0) {
1440 appData.matchMode = TRUE;
1441 } else if (appData.matchMode) {
1442 appData.matchGames = 1;
1444 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1445 appData.matchGames = appData.sameColorGames;
1446 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1447 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1448 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1451 if (appData.noChessProgram || first.protocolVersion == 1) {
1454 /* kludge: allow timeout for initial "feature" commands */
1456 DisplayMessage("", _("Starting chess program"));
1457 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1462 CalculateIndex (int index, int gameNr)
1463 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1465 if(index > 0) return index; // fixed nmber
1466 if(index == 0) return 1;
1467 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1468 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1473 LoadGameOrPosition (int gameNr)
1474 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1475 if (*appData.loadGameFile != NULLCHAR) {
1476 if (!LoadGameFromFile(appData.loadGameFile,
1477 CalculateIndex(appData.loadGameIndex, gameNr),
1478 appData.loadGameFile, FALSE)) {
1479 DisplayFatalError(_("Bad game file"), 0, 1);
1482 } else if (*appData.loadPositionFile != NULLCHAR) {
1483 if (!LoadPositionFromFile(appData.loadPositionFile,
1484 CalculateIndex(appData.loadPositionIndex, gameNr),
1485 appData.loadPositionFile)) {
1486 DisplayFatalError(_("Bad position file"), 0, 1);
1494 ReserveGame (int gameNr, char resChar)
1496 FILE *tf = fopen(appData.tourneyFile, "r+");
1497 char *p, *q, c, buf[MSG_SIZ];
1498 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1499 safeStrCpy(buf, lastMsg, MSG_SIZ);
1500 DisplayMessage(_("Pick new game"), "");
1501 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1502 ParseArgsFromFile(tf);
1503 p = q = appData.results;
1504 if(appData.debugMode) {
1505 char *r = appData.participants;
1506 fprintf(debugFP, "results = '%s'\n", p);
1507 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1508 fprintf(debugFP, "\n");
1510 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1512 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1513 safeStrCpy(q, p, strlen(p) + 2);
1514 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1515 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1516 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1517 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1520 fseek(tf, -(strlen(p)+4), SEEK_END);
1522 if(c != '"') // depending on DOS or Unix line endings we can be one off
1523 fseek(tf, -(strlen(p)+2), SEEK_END);
1524 else fseek(tf, -(strlen(p)+3), SEEK_END);
1525 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1526 DisplayMessage(buf, "");
1527 free(p); appData.results = q;
1528 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1529 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1530 int round = appData.defaultMatchGames * appData.tourneyType;
1531 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1532 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1533 UnloadEngine(&first); // next game belongs to other pairing;
1534 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1536 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1540 MatchEvent (int mode)
1541 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1543 if(matchMode) { // already in match mode: switch it off
1545 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1548 // if(gameMode != BeginningOfGame) {
1549 // DisplayError(_("You can only start a match from the initial position."), 0);
1553 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1554 /* Set up machine vs. machine match */
1556 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1557 if(appData.tourneyFile[0]) {
1559 if(nextGame > appData.matchGames) {
1561 if(strchr(appData.results, '*') == NULL) {
1563 appData.tourneyCycles++;
1564 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1566 NextTourneyGame(-1, &dummy);
1568 if(nextGame <= appData.matchGames) {
1569 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1571 ScheduleDelayedEvent(NextMatchGame, 10000);
1576 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1577 DisplayError(buf, 0);
1578 appData.tourneyFile[0] = 0;
1582 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1583 DisplayFatalError(_("Can't have a match with no chess programs"),
1588 matchGame = roundNr = 1;
1589 first.matchWins = second.matchWins = totalTime = 0; // [HGM] match: needed in later matches
1593 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1596 InitBackEnd3 P((void))
1598 GameMode initialMode;
1602 if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode && // mode involves only first engine
1603 !strcmp(appData.variant, "normal") && // no explicit variant request
1604 appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 && // no size overrides requested
1605 !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") && // but 'normal' won't work with engine
1606 !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1607 char c, *q = first.variants, *p = strchr(q, ',');
1608 if(p) *p = NULLCHAR;
1609 if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1611 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1612 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1613 ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1614 Reset(TRUE, FALSE); // and re-initialize
1619 InitChessProgram(&first, startedFromSetupPosition);
1621 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1622 free(programVersion);
1623 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1624 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1625 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1628 if (appData.icsActive) {
1630 /* [DM] Make a console window if needed [HGM] merged ifs */
1636 if (*appData.icsCommPort != NULLCHAR)
1637 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1638 appData.icsCommPort);
1640 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1641 appData.icsHost, appData.icsPort);
1643 if( (len >= MSG_SIZ) && appData.debugMode )
1644 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1646 DisplayFatalError(buf, err, 1);
1651 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1653 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1654 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1655 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1656 } else if (appData.noChessProgram) {
1662 if (*appData.cmailGameName != NULLCHAR) {
1664 OpenLoopback(&cmailPR);
1666 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1670 DisplayMessage("", "");
1671 if (StrCaseCmp(appData.initialMode, "") == 0) {
1672 initialMode = BeginningOfGame;
1673 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1674 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1675 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1676 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1679 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1680 initialMode = TwoMachinesPlay;
1681 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1682 initialMode = AnalyzeFile;
1683 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1684 initialMode = AnalyzeMode;
1685 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1686 initialMode = MachinePlaysWhite;
1687 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1688 initialMode = MachinePlaysBlack;
1689 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1690 initialMode = EditGame;
1691 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1692 initialMode = EditPosition;
1693 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1694 initialMode = Training;
1696 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1697 if( (len >= MSG_SIZ) && appData.debugMode )
1698 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1700 DisplayFatalError(buf, 0, 2);
1704 if (appData.matchMode) {
1705 if(appData.tourneyFile[0]) { // start tourney from command line
1707 if(f = fopen(appData.tourneyFile, "r")) {
1708 ParseArgsFromFile(f); // make sure tourney parmeters re known
1710 appData.clockMode = TRUE;
1712 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1715 } else if (*appData.cmailGameName != NULLCHAR) {
1716 /* Set up cmail mode */
1717 ReloadCmailMsgEvent(TRUE);
1719 /* Set up other modes */
1720 if (initialMode == AnalyzeFile) {
1721 if (*appData.loadGameFile == NULLCHAR) {
1722 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1726 if (*appData.loadGameFile != NULLCHAR) {
1727 (void) LoadGameFromFile(appData.loadGameFile,
1728 appData.loadGameIndex,
1729 appData.loadGameFile, TRUE);
1730 } else if (*appData.loadPositionFile != NULLCHAR) {
1731 (void) LoadPositionFromFile(appData.loadPositionFile,
1732 appData.loadPositionIndex,
1733 appData.loadPositionFile);
1734 /* [HGM] try to make self-starting even after FEN load */
1735 /* to allow automatic setup of fairy variants with wtm */
1736 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1737 gameMode = BeginningOfGame;
1738 setboardSpoiledMachineBlack = 1;
1740 /* [HGM] loadPos: make that every new game uses the setup */
1741 /* from file as long as we do not switch variant */
1742 if(!blackPlaysFirst) {
1743 startedFromPositionFile = TRUE;
1744 CopyBoard(filePosition, boards[0]);
1745 CopyBoard(initialPosition, boards[0]);
1747 } else if(*appData.fen != NULLCHAR) {
1748 if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
1749 startedFromPositionFile = TRUE;
1753 if (initialMode == AnalyzeMode) {
1754 if (appData.noChessProgram) {
1755 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1758 if (appData.icsActive) {
1759 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1763 } else if (initialMode == AnalyzeFile) {
1764 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1765 ShowThinkingEvent();
1767 AnalysisPeriodicEvent(1);
1768 } else if (initialMode == MachinePlaysWhite) {
1769 if (appData.noChessProgram) {
1770 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1774 if (appData.icsActive) {
1775 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1779 MachineWhiteEvent();
1780 } else if (initialMode == MachinePlaysBlack) {
1781 if (appData.noChessProgram) {
1782 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1786 if (appData.icsActive) {
1787 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1791 MachineBlackEvent();
1792 } else if (initialMode == TwoMachinesPlay) {
1793 if (appData.noChessProgram) {
1794 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1798 if (appData.icsActive) {
1799 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1804 } else if (initialMode == EditGame) {
1806 } else if (initialMode == EditPosition) {
1807 EditPositionEvent();
1808 } else if (initialMode == Training) {
1809 if (*appData.loadGameFile == NULLCHAR) {
1810 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1819 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1821 DisplayBook(current+1);
1823 MoveHistorySet( movelist, first, last, current, pvInfoList );
1825 EvalGraphSet( first, last, current, pvInfoList );
1827 MakeEngineOutputTitle();
1831 * Establish will establish a contact to a remote host.port.
1832 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1833 * used to talk to the host.
1834 * Returns 0 if okay, error code if not.
1841 if (*appData.icsCommPort != NULLCHAR) {
1842 /* Talk to the host through a serial comm port */
1843 return OpenCommPort(appData.icsCommPort, &icsPR);
1845 } else if (*appData.gateway != NULLCHAR) {
1846 if (*appData.remoteShell == NULLCHAR) {
1847 /* Use the rcmd protocol to run telnet program on a gateway host */
1848 snprintf(buf, sizeof(buf), "%s %s %s",
1849 appData.telnetProgram, appData.icsHost, appData.icsPort);
1850 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1853 /* Use the rsh program to run telnet program on a gateway host */
1854 if (*appData.remoteUser == NULLCHAR) {
1855 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1856 appData.gateway, appData.telnetProgram,
1857 appData.icsHost, appData.icsPort);
1859 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1860 appData.remoteShell, appData.gateway,
1861 appData.remoteUser, appData.telnetProgram,
1862 appData.icsHost, appData.icsPort);
1864 return StartChildProcess(buf, "", &icsPR);
1867 } else if (appData.useTelnet) {
1868 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1871 /* TCP socket interface differs somewhat between
1872 Unix and NT; handle details in the front end.
1874 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1879 EscapeExpand (char *p, char *q)
1880 { // [HGM] initstring: routine to shape up string arguments
1881 while(*p++ = *q++) if(p[-1] == '\\')
1883 case 'n': p[-1] = '\n'; break;
1884 case 'r': p[-1] = '\r'; break;
1885 case 't': p[-1] = '\t'; break;
1886 case '\\': p[-1] = '\\'; break;
1887 case 0: *p = 0; return;
1888 default: p[-1] = q[-1]; break;
1893 show_bytes (FILE *fp, char *buf, int count)
1896 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1897 fprintf(fp, "\\%03o", *buf & 0xff);
1906 /* Returns an errno value */
1908 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1910 char buf[8192], *p, *q, *buflim;
1911 int left, newcount, outcount;
1913 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1914 *appData.gateway != NULLCHAR) {
1915 if (appData.debugMode) {
1916 fprintf(debugFP, ">ICS: ");
1917 show_bytes(debugFP, message, count);
1918 fprintf(debugFP, "\n");
1920 return OutputToProcess(pr, message, count, outError);
1923 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
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 */
1943 } else if (((unsigned char) *p) == TN_IAC) {
1944 *q++ = (char) TN_IAC;
1951 if (appData.debugMode) {
1952 fprintf(debugFP, ">ICS: ");
1953 show_bytes(debugFP, buf, newcount);
1954 fprintf(debugFP, "\n");
1956 outcount = OutputToProcess(pr, buf, newcount, outError);
1957 if (outcount < newcount) return -1; /* to be sure */
1962 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1964 int outError, outCount;
1965 static int gotEof = 0;
1968 /* Pass data read from player on to ICS */
1971 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1972 if (outCount < count) {
1973 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1975 if(have_sent_ICS_logon == 2) {
1976 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1977 fprintf(ini, "%s", message);
1978 have_sent_ICS_logon = 3;
1980 have_sent_ICS_logon = 1;
1981 } else if(have_sent_ICS_logon == 3) {
1982 fprintf(ini, "%s", message);
1984 have_sent_ICS_logon = 1;
1986 } else if (count < 0) {
1987 RemoveInputSource(isr);
1988 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1989 } else if (gotEof++ > 0) {
1990 RemoveInputSource(isr);
1991 DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
1997 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1998 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1999 connectionAlive = FALSE; // only sticks if no response to 'date' command.
2000 SendToICS("date\n");
2001 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
2004 /* added routine for printf style output to ics */
2006 ics_printf (char *format, ...)
2008 char buffer[MSG_SIZ];
2011 va_start(args, format);
2012 vsnprintf(buffer, sizeof(buffer), format, args);
2013 buffer[sizeof(buffer)-1] = '\0';
2021 int count, outCount, outError;
2023 if (icsPR == NoProc) return;
2026 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2027 if (outCount < count) {
2028 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2032 /* This is used for sending logon scripts to the ICS. Sending
2033 without a delay causes problems when using timestamp on ICC
2034 (at least on my machine). */
2036 SendToICSDelayed (char *s, long msdelay)
2038 int count, outCount, outError;
2040 if (icsPR == NoProc) return;
2043 if (appData.debugMode) {
2044 fprintf(debugFP, ">ICS: ");
2045 show_bytes(debugFP, s, count);
2046 fprintf(debugFP, "\n");
2048 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2050 if (outCount < count) {
2051 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2056 /* Remove all highlighting escape sequences in s
2057 Also deletes any suffix starting with '('
2060 StripHighlightAndTitle (char *s)
2062 static char retbuf[MSG_SIZ];
2065 while (*s != NULLCHAR) {
2066 while (*s == '\033') {
2067 while (*s != NULLCHAR && !isalpha(*s)) s++;
2068 if (*s != NULLCHAR) s++;
2070 while (*s != NULLCHAR && *s != '\033') {
2071 if (*s == '(' || *s == '[') {
2082 /* Remove all highlighting escape sequences in s */
2084 StripHighlight (char *s)
2086 static char retbuf[MSG_SIZ];
2089 while (*s != NULLCHAR) {
2090 while (*s == '\033') {
2091 while (*s != NULLCHAR && !isalpha(*s)) s++;
2092 if (*s != NULLCHAR) s++;
2094 while (*s != NULLCHAR && *s != '\033') {
2102 char engineVariant[MSG_SIZ];
2103 char *variantNames[] = VARIANT_NAMES;
2105 VariantName (VariantClass v)
2107 if(v == VariantUnknown || *engineVariant) return engineVariant;
2108 return variantNames[v];
2112 /* Identify a variant from the strings the chess servers use or the
2113 PGN Variant tag names we use. */
2115 StringToVariant (char *e)
2119 VariantClass v = VariantNormal;
2120 int i, found = FALSE;
2121 char buf[MSG_SIZ], c;
2126 /* [HGM] skip over optional board-size prefixes */
2127 if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2128 sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2129 while( *e++ != '_');
2132 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2136 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2137 if (p = StrCaseStr(e, variantNames[i])) {
2138 if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2139 v = (VariantClass) i;
2146 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2147 || StrCaseStr(e, "wild/fr")
2148 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2149 v = VariantFischeRandom;
2150 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2151 (i = 1, p = StrCaseStr(e, "w"))) {
2153 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2160 case 0: /* FICS only, actually */
2162 /* Castling legal even if K starts on d-file */
2163 v = VariantWildCastle;
2168 /* Castling illegal even if K & R happen to start in
2169 normal positions. */
2170 v = VariantNoCastle;
2183 /* Castling legal iff K & R start in normal positions */
2189 /* Special wilds for position setup; unclear what to do here */
2190 v = VariantLoadable;
2193 /* Bizarre ICC game */
2194 v = VariantTwoKings;
2197 v = VariantKriegspiel;
2203 v = VariantFischeRandom;
2206 v = VariantCrazyhouse;
2209 v = VariantBughouse;
2215 /* Not quite the same as FICS suicide! */
2216 v = VariantGiveaway;
2222 v = VariantShatranj;
2225 /* Temporary names for future ICC types. The name *will* change in
2226 the next xboard/WinBoard release after ICC defines it. */
2264 v = VariantCapablanca;
2267 v = VariantKnightmate;
2273 v = VariantCylinder;
2279 v = VariantCapaRandom;
2282 v = VariantBerolina;
2294 /* Found "wild" or "w" in the string but no number;
2295 must assume it's normal chess. */
2299 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2300 if( (len >= MSG_SIZ) && appData.debugMode )
2301 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2303 DisplayError(buf, 0);
2309 if (appData.debugMode) {
2310 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2311 e, wnum, VariantName(v));
2316 static int leftover_start = 0, leftover_len = 0;
2317 char star_match[STAR_MATCH_N][MSG_SIZ];
2319 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2320 advance *index beyond it, and set leftover_start to the new value of
2321 *index; else return FALSE. If pattern contains the character '*', it
2322 matches any sequence of characters not containing '\r', '\n', or the
2323 character following the '*' (if any), and the matched sequence(s) are
2324 copied into star_match.
2327 looking_at ( char *buf, int *index, char *pattern)
2329 char *bufp = &buf[*index], *patternp = pattern;
2331 char *matchp = star_match[0];
2334 if (*patternp == NULLCHAR) {
2335 *index = leftover_start = bufp - buf;
2339 if (*bufp == NULLCHAR) return FALSE;
2340 if (*patternp == '*') {
2341 if (*bufp == *(patternp + 1)) {
2343 matchp = star_match[++star_count];
2347 } else if (*bufp == '\n' || *bufp == '\r') {
2349 if (*patternp == NULLCHAR)
2354 *matchp++ = *bufp++;
2358 if (*patternp != *bufp) return FALSE;
2365 SendToPlayer (char *data, int length)
2367 int error, outCount;
2368 outCount = OutputToProcess(NoProc, data, length, &error);
2369 if (outCount < length) {
2370 DisplayFatalError(_("Error writing to display"), error, 1);
2375 PackHolding (char packed[], char *holding)
2385 switch (runlength) {
2396 sprintf(q, "%d", runlength);
2408 /* Telnet protocol requests from the front end */
2410 TelnetRequest (unsigned char ddww, unsigned char option)
2412 unsigned char msg[3];
2413 int outCount, outError;
2415 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2417 if (appData.debugMode) {
2418 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2434 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2443 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2446 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2451 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2453 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2460 if (!appData.icsActive) return;
2461 TelnetRequest(TN_DO, TN_ECHO);
2467 if (!appData.icsActive) return;
2468 TelnetRequest(TN_DONT, TN_ECHO);
2472 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2474 /* put the holdings sent to us by the server on the board holdings area */
2475 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2479 if(gameInfo.holdingsWidth < 2) return;
2480 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2481 return; // prevent overwriting by pre-board holdings
2483 if( (int)lowestPiece >= BlackPawn ) {
2486 holdingsStartRow = BOARD_HEIGHT-1;
2489 holdingsColumn = BOARD_WIDTH-1;
2490 countsColumn = BOARD_WIDTH-2;
2491 holdingsStartRow = 0;
2495 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2496 board[i][holdingsColumn] = EmptySquare;
2497 board[i][countsColumn] = (ChessSquare) 0;
2499 while( (p=*holdings++) != NULLCHAR ) {
2500 piece = CharToPiece( ToUpper(p) );
2501 if(piece == EmptySquare) continue;
2502 /*j = (int) piece - (int) WhitePawn;*/
2503 j = PieceToNumber(piece);
2504 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2505 if(j < 0) continue; /* should not happen */
2506 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2507 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2508 board[holdingsStartRow+j*direction][countsColumn]++;
2514 VariantSwitch (Board board, VariantClass newVariant)
2516 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2517 static Board oldBoard;
2519 startedFromPositionFile = FALSE;
2520 if(gameInfo.variant == newVariant) return;
2522 /* [HGM] This routine is called each time an assignment is made to
2523 * gameInfo.variant during a game, to make sure the board sizes
2524 * are set to match the new variant. If that means adding or deleting
2525 * holdings, we shift the playing board accordingly
2526 * This kludge is needed because in ICS observe mode, we get boards
2527 * of an ongoing game without knowing the variant, and learn about the
2528 * latter only later. This can be because of the move list we requested,
2529 * in which case the game history is refilled from the beginning anyway,
2530 * but also when receiving holdings of a crazyhouse game. In the latter
2531 * case we want to add those holdings to the already received position.
2535 if (appData.debugMode) {
2536 fprintf(debugFP, "Switch board from %s to %s\n",
2537 VariantName(gameInfo.variant), VariantName(newVariant));
2538 setbuf(debugFP, NULL);
2540 shuffleOpenings = 0; /* [HGM] shuffle */
2541 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2545 newWidth = 9; newHeight = 9;
2546 gameInfo.holdingsSize = 7;
2547 case VariantBughouse:
2548 case VariantCrazyhouse:
2549 newHoldingsWidth = 2; break;
2553 newHoldingsWidth = 2;
2554 gameInfo.holdingsSize = 8;
2557 case VariantCapablanca:
2558 case VariantCapaRandom:
2561 newHoldingsWidth = gameInfo.holdingsSize = 0;
2564 if(newWidth != gameInfo.boardWidth ||
2565 newHeight != gameInfo.boardHeight ||
2566 newHoldingsWidth != gameInfo.holdingsWidth ) {
2568 /* shift position to new playing area, if needed */
2569 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2570 for(i=0; i<BOARD_HEIGHT; i++)
2571 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2572 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2574 for(i=0; i<newHeight; i++) {
2575 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2576 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2578 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2579 for(i=0; i<BOARD_HEIGHT; i++)
2580 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2581 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2584 board[HOLDINGS_SET] = 0;
2585 gameInfo.boardWidth = newWidth;
2586 gameInfo.boardHeight = newHeight;
2587 gameInfo.holdingsWidth = newHoldingsWidth;
2588 gameInfo.variant = newVariant;
2589 InitDrawingSizes(-2, 0);
2590 } else gameInfo.variant = newVariant;
2591 CopyBoard(oldBoard, board); // remember correctly formatted board
2592 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2593 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2596 static int loggedOn = FALSE;
2598 /*-- Game start info cache: --*/
2600 char gs_kind[MSG_SIZ];
2601 static char player1Name[128] = "";
2602 static char player2Name[128] = "";
2603 static char cont_seq[] = "\n\\ ";
2604 static int player1Rating = -1;
2605 static int player2Rating = -1;
2606 /*----------------------------*/
2608 ColorClass curColor = ColorNormal;
2609 int suppressKibitz = 0;
2612 Boolean soughtPending = FALSE;
2613 Boolean seekGraphUp;
2614 #define MAX_SEEK_ADS 200
2616 char *seekAdList[MAX_SEEK_ADS];
2617 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2618 float tcList[MAX_SEEK_ADS];
2619 char colorList[MAX_SEEK_ADS];
2620 int nrOfSeekAds = 0;
2621 int minRating = 1010, maxRating = 2800;
2622 int hMargin = 10, vMargin = 20, h, w;
2623 extern int squareSize, lineGap;
2628 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2629 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2630 if(r < minRating+100 && r >=0 ) r = minRating+100;
2631 if(r > maxRating) r = maxRating;
2632 if(tc < 1.f) tc = 1.f;
2633 if(tc > 95.f) tc = 95.f;
2634 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2635 y = ((double)r - minRating)/(maxRating - minRating)
2636 * (h-vMargin-squareSize/8-1) + vMargin;
2637 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2638 if(strstr(seekAdList[i], " u ")) color = 1;
2639 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2640 !strstr(seekAdList[i], "bullet") &&
2641 !strstr(seekAdList[i], "blitz") &&
2642 !strstr(seekAdList[i], "standard") ) color = 2;
2643 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2644 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2648 PlotSingleSeekAd (int i)
2654 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2656 char buf[MSG_SIZ], *ext = "";
2657 VariantClass v = StringToVariant(type);
2658 if(strstr(type, "wild")) {
2659 ext = type + 4; // append wild number
2660 if(v == VariantFischeRandom) type = "chess960"; else
2661 if(v == VariantLoadable) type = "setup"; else
2662 type = VariantName(v);
2664 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2665 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2666 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2667 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2668 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2669 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2670 seekNrList[nrOfSeekAds] = nr;
2671 zList[nrOfSeekAds] = 0;
2672 seekAdList[nrOfSeekAds++] = StrSave(buf);
2673 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2678 EraseSeekDot (int i)
2680 int x = xList[i], y = yList[i], d=squareSize/4, k;
2681 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2682 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2683 // now replot every dot that overlapped
2684 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2685 int xx = xList[k], yy = yList[k];
2686 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2687 DrawSeekDot(xx, yy, colorList[k]);
2692 RemoveSeekAd (int nr)
2695 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2697 if(seekAdList[i]) free(seekAdList[i]);
2698 seekAdList[i] = seekAdList[--nrOfSeekAds];
2699 seekNrList[i] = seekNrList[nrOfSeekAds];
2700 ratingList[i] = ratingList[nrOfSeekAds];
2701 colorList[i] = colorList[nrOfSeekAds];
2702 tcList[i] = tcList[nrOfSeekAds];
2703 xList[i] = xList[nrOfSeekAds];
2704 yList[i] = yList[nrOfSeekAds];
2705 zList[i] = zList[nrOfSeekAds];
2706 seekAdList[nrOfSeekAds] = NULL;
2712 MatchSoughtLine (char *line)
2714 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2715 int nr, base, inc, u=0; char dummy;
2717 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2718 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2720 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2721 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2722 // match: compact and save the line
2723 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2733 if(!seekGraphUp) return FALSE;
2734 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2735 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap + 2*border;
2737 DrawSeekBackground(0, 0, w, h);
2738 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2739 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2740 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2741 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2743 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2746 snprintf(buf, MSG_SIZ, "%d", i);
2747 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2750 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2751 for(i=1; i<100; i+=(i<10?1:5)) {
2752 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2753 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2754 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2756 snprintf(buf, MSG_SIZ, "%d", i);
2757 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2760 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2765 SeekGraphClick (ClickType click, int x, int y, int moving)
2767 static int lastDown = 0, displayed = 0, lastSecond;
2768 if(y < 0) return FALSE;
2769 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2770 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2771 if(!seekGraphUp) return FALSE;
2772 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2773 DrawPosition(TRUE, NULL);
2776 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2777 if(click == Release || moving) return FALSE;
2779 soughtPending = TRUE;
2780 SendToICS(ics_prefix);
2781 SendToICS("sought\n"); // should this be "sought all"?
2782 } else { // issue challenge based on clicked ad
2783 int dist = 10000; int i, closest = 0, second = 0;
2784 for(i=0; i<nrOfSeekAds; i++) {
2785 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2786 if(d < dist) { dist = d; closest = i; }
2787 second += (d - zList[i] < 120); // count in-range ads
2788 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2792 second = (second > 1);
2793 if(displayed != closest || second != lastSecond) {
2794 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2795 lastSecond = second; displayed = closest;
2797 if(click == Press) {
2798 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2801 } // on press 'hit', only show info
2802 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2803 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2804 SendToICS(ics_prefix);
2806 return TRUE; // let incoming board of started game pop down the graph
2807 } else if(click == Release) { // release 'miss' is ignored
2808 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2809 if(moving == 2) { // right up-click
2810 nrOfSeekAds = 0; // refresh graph
2811 soughtPending = TRUE;
2812 SendToICS(ics_prefix);
2813 SendToICS("sought\n"); // should this be "sought all"?
2816 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2817 // press miss or release hit 'pop down' seek graph
2818 seekGraphUp = FALSE;
2819 DrawPosition(TRUE, NULL);
2825 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2827 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2828 #define STARTED_NONE 0
2829 #define STARTED_MOVES 1
2830 #define STARTED_BOARD 2
2831 #define STARTED_OBSERVE 3
2832 #define STARTED_HOLDINGS 4
2833 #define STARTED_CHATTER 5
2834 #define STARTED_COMMENT 6
2835 #define STARTED_MOVES_NOHIDE 7
2837 static int started = STARTED_NONE;
2838 static char parse[20000];
2839 static int parse_pos = 0;
2840 static char buf[BUF_SIZE + 1];
2841 static int firstTime = TRUE, intfSet = FALSE;
2842 static ColorClass prevColor = ColorNormal;
2843 static int savingComment = FALSE;
2844 static int cmatch = 0; // continuation sequence match
2851 int backup; /* [DM] For zippy color lines */
2853 char talker[MSG_SIZ]; // [HGM] chat
2854 int channel, collective=0;
2856 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2858 if (appData.debugMode) {
2860 fprintf(debugFP, "<ICS: ");
2861 show_bytes(debugFP, data, count);
2862 fprintf(debugFP, "\n");
2866 if (appData.debugMode) { int f = forwardMostMove;
2867 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2868 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2869 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2872 /* If last read ended with a partial line that we couldn't parse,
2873 prepend it to the new read and try again. */
2874 if (leftover_len > 0) {
2875 for (i=0; i<leftover_len; i++)
2876 buf[i] = buf[leftover_start + i];
2879 /* copy new characters into the buffer */
2880 bp = buf + leftover_len;
2881 buf_len=leftover_len;
2882 for (i=0; i<count; i++)
2885 if (data[i] == '\r')
2888 // join lines split by ICS?
2889 if (!appData.noJoin)
2892 Joining just consists of finding matches against the
2893 continuation sequence, and discarding that sequence
2894 if found instead of copying it. So, until a match
2895 fails, there's nothing to do since it might be the
2896 complete sequence, and thus, something we don't want
2899 if (data[i] == cont_seq[cmatch])
2902 if (cmatch == strlen(cont_seq))
2904 cmatch = 0; // complete match. just reset the counter
2907 it's possible for the ICS to not include the space
2908 at the end of the last word, making our [correct]
2909 join operation fuse two separate words. the server
2910 does this when the space occurs at the width setting.
2912 if (!buf_len || buf[buf_len-1] != ' ')
2923 match failed, so we have to copy what matched before
2924 falling through and copying this character. In reality,
2925 this will only ever be just the newline character, but
2926 it doesn't hurt to be precise.
2928 strncpy(bp, cont_seq, cmatch);
2940 buf[buf_len] = NULLCHAR;
2941 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2946 while (i < buf_len) {
2947 /* Deal with part of the TELNET option negotiation
2948 protocol. We refuse to do anything beyond the
2949 defaults, except that we allow the WILL ECHO option,
2950 which ICS uses to turn off password echoing when we are
2951 directly connected to it. We reject this option
2952 if localLineEditing mode is on (always on in xboard)
2953 and we are talking to port 23, which might be a real
2954 telnet server that will try to keep WILL ECHO on permanently.
2956 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2957 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2958 unsigned char option;
2960 switch ((unsigned char) buf[++i]) {
2962 if (appData.debugMode)
2963 fprintf(debugFP, "\n<WILL ");
2964 switch (option = (unsigned char) buf[++i]) {
2966 if (appData.debugMode)
2967 fprintf(debugFP, "ECHO ");
2968 /* Reply only if this is a change, according
2969 to the protocol rules. */
2970 if (remoteEchoOption) break;
2971 if (appData.localLineEditing &&
2972 atoi(appData.icsPort) == TN_PORT) {
2973 TelnetRequest(TN_DONT, TN_ECHO);
2976 TelnetRequest(TN_DO, TN_ECHO);
2977 remoteEchoOption = TRUE;
2981 if (appData.debugMode)
2982 fprintf(debugFP, "%d ", option);
2983 /* Whatever this is, we don't want it. */
2984 TelnetRequest(TN_DONT, option);
2989 if (appData.debugMode)
2990 fprintf(debugFP, "\n<WONT ");
2991 switch (option = (unsigned char) buf[++i]) {
2993 if (appData.debugMode)
2994 fprintf(debugFP, "ECHO ");
2995 /* Reply only if this is a change, according
2996 to the protocol rules. */
2997 if (!remoteEchoOption) break;
2999 TelnetRequest(TN_DONT, TN_ECHO);
3000 remoteEchoOption = FALSE;
3003 if (appData.debugMode)
3004 fprintf(debugFP, "%d ", (unsigned char) option);
3005 /* Whatever this is, it must already be turned
3006 off, because we never agree to turn on
3007 anything non-default, so according to the
3008 protocol rules, we don't reply. */
3013 if (appData.debugMode)
3014 fprintf(debugFP, "\n<DO ");
3015 switch (option = (unsigned char) buf[++i]) {
3017 /* Whatever this is, we refuse to do it. */
3018 if (appData.debugMode)
3019 fprintf(debugFP, "%d ", option);
3020 TelnetRequest(TN_WONT, option);
3025 if (appData.debugMode)
3026 fprintf(debugFP, "\n<DONT ");
3027 switch (option = (unsigned char) buf[++i]) {
3029 if (appData.debugMode)
3030 fprintf(debugFP, "%d ", option);
3031 /* Whatever this is, we are already not doing
3032 it, because we never agree to do anything
3033 non-default, so according to the protocol
3034 rules, we don't reply. */
3039 if (appData.debugMode)
3040 fprintf(debugFP, "\n<IAC ");
3041 /* Doubled IAC; pass it through */
3045 if (appData.debugMode)
3046 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3047 /* Drop all other telnet commands on the floor */
3050 if (oldi > next_out)
3051 SendToPlayer(&buf[next_out], oldi - next_out);
3057 /* OK, this at least will *usually* work */
3058 if (!loggedOn && looking_at(buf, &i, "ics%")) {
3062 if (loggedOn && !intfSet) {
3063 if (ics_type == ICS_ICC) {
3064 snprintf(str, MSG_SIZ,
3065 "/set-quietly interface %s\n/set-quietly style 12\n",
3067 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3068 strcat(str, "/set-2 51 1\n/set seek 1\n");
3069 } else if (ics_type == ICS_CHESSNET) {
3070 snprintf(str, MSG_SIZ, "/style 12\n");
3072 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3073 strcat(str, programVersion);
3074 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3075 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3076 strcat(str, "$iset seekremove 1\n$set seek 1\n");
3078 strcat(str, "$iset nohighlight 1\n");
3080 strcat(str, "$iset lock 1\n$style 12\n");
3083 NotifyFrontendLogin();
3087 if (started == STARTED_COMMENT) {
3088 /* Accumulate characters in comment */
3089 parse[parse_pos++] = buf[i];
3090 if (buf[i] == '\n') {
3091 parse[parse_pos] = NULLCHAR;
3092 if(chattingPartner>=0) {
3094 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3095 OutputChatMessage(chattingPartner, mess);
3096 if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3098 talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3099 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3100 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3101 OutputChatMessage(p, mess);
3105 chattingPartner = -1;
3106 if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3109 if(!suppressKibitz) // [HGM] kibitz
3110 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3111 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3112 int nrDigit = 0, nrAlph = 0, j;
3113 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3114 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3115 parse[parse_pos] = NULLCHAR;
3116 // try to be smart: if it does not look like search info, it should go to
3117 // ICS interaction window after all, not to engine-output window.
3118 for(j=0; j<parse_pos; j++) { // count letters and digits
3119 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3120 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3121 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3123 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3124 int depth=0; float score;
3125 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3126 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3127 pvInfoList[forwardMostMove-1].depth = depth;
3128 pvInfoList[forwardMostMove-1].score = 100*score;
3130 OutputKibitz(suppressKibitz, parse);
3133 if(gameMode == IcsObserving) // restore original ICS messages
3134 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3135 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3137 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3138 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3139 SendToPlayer(tmp, strlen(tmp));
3141 next_out = i+1; // [HGM] suppress printing in ICS window
3143 started = STARTED_NONE;
3145 /* Don't match patterns against characters in comment */
3150 if (started == STARTED_CHATTER) {
3151 if (buf[i] != '\n') {
3152 /* Don't match patterns against characters in chatter */
3156 started = STARTED_NONE;
3157 if(suppressKibitz) next_out = i+1;
3160 /* Kludge to deal with rcmd protocol */
3161 if (firstTime && looking_at(buf, &i, "\001*")) {
3162 DisplayFatalError(&buf[1], 0, 1);
3168 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3171 if (appData.debugMode)
3172 fprintf(debugFP, "ics_type %d\n", ics_type);
3175 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3176 ics_type = ICS_FICS;
3178 if (appData.debugMode)
3179 fprintf(debugFP, "ics_type %d\n", ics_type);
3182 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3183 ics_type = ICS_CHESSNET;
3185 if (appData.debugMode)
3186 fprintf(debugFP, "ics_type %d\n", ics_type);
3191 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3192 looking_at(buf, &i, "Logging you in as \"*\"") ||
3193 looking_at(buf, &i, "will be \"*\""))) {
3194 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3198 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3200 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3201 DisplayIcsInteractionTitle(buf);
3202 have_set_title = TRUE;
3205 /* skip finger notes */
3206 if (started == STARTED_NONE &&
3207 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3208 (buf[i] == '1' && buf[i+1] == '0')) &&
3209 buf[i+2] == ':' && buf[i+3] == ' ') {
3210 started = STARTED_CHATTER;
3216 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3217 if(appData.seekGraph) {
3218 if(soughtPending && MatchSoughtLine(buf+i)) {
3219 i = strstr(buf+i, "rated") - buf;
3220 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3221 next_out = leftover_start = i;
3222 started = STARTED_CHATTER;
3223 suppressKibitz = TRUE;
3226 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3227 && looking_at(buf, &i, "* ads displayed")) {
3228 soughtPending = FALSE;
3233 if(appData.autoRefresh) {
3234 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3235 int s = (ics_type == ICS_ICC); // ICC format differs
3237 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3238 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3239 looking_at(buf, &i, "*% "); // eat prompt
3240 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3241 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3242 next_out = i; // suppress
3245 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3246 char *p = star_match[0];
3248 if(seekGraphUp) RemoveSeekAd(atoi(p));
3249 while(*p && *p++ != ' '); // next
3251 looking_at(buf, &i, "*% "); // eat prompt
3252 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3259 /* skip formula vars */
3260 if (started == STARTED_NONE &&
3261 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3262 started = STARTED_CHATTER;
3267 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3268 if (appData.autoKibitz && started == STARTED_NONE &&
3269 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3270 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3271 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3272 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3273 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3274 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3275 suppressKibitz = TRUE;
3276 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3278 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3279 && (gameMode == IcsPlayingWhite)) ||
3280 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3281 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3282 started = STARTED_CHATTER; // own kibitz we simply discard
3284 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3285 parse_pos = 0; parse[0] = NULLCHAR;
3286 savingComment = TRUE;
3287 suppressKibitz = gameMode != IcsObserving ? 2 :
3288 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3292 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3293 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3294 && atoi(star_match[0])) {
3295 // suppress the acknowledgements of our own autoKibitz
3297 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3298 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3299 SendToPlayer(star_match[0], strlen(star_match[0]));
3300 if(looking_at(buf, &i, "*% ")) // eat prompt
3301 suppressKibitz = FALSE;
3305 } // [HGM] kibitz: end of patch
3307 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3309 // [HGM] chat: intercept tells by users for which we have an open chat window
3311 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3312 looking_at(buf, &i, "* whispers:") ||
3313 looking_at(buf, &i, "* kibitzes:") ||
3314 looking_at(buf, &i, "* shouts:") ||
3315 looking_at(buf, &i, "* c-shouts:") ||
3316 looking_at(buf, &i, "--> * ") ||
3317 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3318 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3319 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3320 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3322 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3323 chattingPartner = -1; collective = 0;
3325 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3326 for(p=0; p<MAX_CHAT; p++) {
3328 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3329 talker[0] = '['; strcat(talker, "] ");
3330 Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3331 chattingPartner = p; break;
3334 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3335 for(p=0; p<MAX_CHAT; p++) {
3337 if(!strcmp("kibitzes", chatPartner[p])) {
3338 talker[0] = '['; strcat(talker, "] ");
3339 chattingPartner = p; break;
3342 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3343 for(p=0; p<MAX_CHAT; p++) {
3345 if(!strcmp("whispers", chatPartner[p])) {
3346 talker[0] = '['; strcat(talker, "] ");
3347 chattingPartner = p; break;
3350 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3351 if(buf[i-8] == '-' && buf[i-3] == 't')
3352 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3354 if(!strcmp("c-shouts", chatPartner[p])) {
3355 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3356 chattingPartner = p; break;
3359 if(chattingPartner < 0)
3360 for(p=0; p<MAX_CHAT; p++) {
3362 if(!strcmp("shouts", chatPartner[p])) {
3363 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3364 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3365 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3366 chattingPartner = p; break;
3370 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3371 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3373 Colorize(ColorTell, FALSE);
3374 if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3376 chattingPartner = p; break;
3378 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3379 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3380 started = STARTED_COMMENT;
3381 parse_pos = 0; parse[0] = NULLCHAR;
3382 savingComment = 3 + chattingPartner; // counts as TRUE
3383 if(collective == 3) i = oldi; else {
3384 suppressKibitz = TRUE;
3385 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3386 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3390 } // [HGM] chat: end of patch
3393 if (appData.zippyTalk || appData.zippyPlay) {
3394 /* [DM] Backup address for color zippy lines */
3396 if (loggedOn == TRUE)
3397 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3398 (appData.zippyPlay && ZippyMatch(buf, &backup)))
3401 } // [DM] 'else { ' deleted
3403 /* Regular tells and says */
3404 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3405 looking_at(buf, &i, "* (your partner) tells you: ") ||
3406 looking_at(buf, &i, "* says: ") ||
3407 /* Don't color "message" or "messages" output */
3408 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3409 looking_at(buf, &i, "*. * at *:*: ") ||
3410 looking_at(buf, &i, "--* (*:*): ") ||
3411 /* Message notifications (same color as tells) */
3412 looking_at(buf, &i, "* has left a message ") ||
3413 looking_at(buf, &i, "* just sent you a message:\n") ||
3414 /* Whispers and kibitzes */
3415 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3416 looking_at(buf, &i, "* kibitzes: ") ||
3418 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3420 if (tkind == 1 && strchr(star_match[0], ':')) {
3421 /* Avoid "tells you:" spoofs in channels */
3424 if (star_match[0][0] == NULLCHAR ||
3425 strchr(star_match[0], ' ') ||
3426 (tkind == 3 && strchr(star_match[1], ' '))) {
3427 /* Reject bogus matches */
3430 if (appData.colorize) {
3431 if (oldi > next_out) {
3432 SendToPlayer(&buf[next_out], oldi - next_out);
3437 Colorize(ColorTell, FALSE);
3438 curColor = ColorTell;
3441 Colorize(ColorKibitz, FALSE);
3442 curColor = ColorKibitz;
3445 p = strrchr(star_match[1], '(');
3452 Colorize(ColorChannel1, FALSE);
3453 curColor = ColorChannel1;
3455 Colorize(ColorChannel, FALSE);
3456 curColor = ColorChannel;
3460 curColor = ColorNormal;
3464 if (started == STARTED_NONE && appData.autoComment &&
3465 (gameMode == IcsObserving ||
3466 gameMode == IcsPlayingWhite ||
3467 gameMode == IcsPlayingBlack)) {
3468 parse_pos = i - oldi;
3469 memcpy(parse, &buf[oldi], parse_pos);
3470 parse[parse_pos] = NULLCHAR;
3471 started = STARTED_COMMENT;
3472 savingComment = TRUE;
3473 } else if(collective != 3) {
3474 started = STARTED_CHATTER;
3475 savingComment = FALSE;
3482 if (looking_at(buf, &i, "* s-shouts: ") ||
3483 looking_at(buf, &i, "* c-shouts: ")) {
3484 if (appData.colorize) {
3485 if (oldi > next_out) {
3486 SendToPlayer(&buf[next_out], oldi - next_out);
3489 Colorize(ColorSShout, FALSE);
3490 curColor = ColorSShout;
3493 started = STARTED_CHATTER;
3497 if (looking_at(buf, &i, "--->")) {
3502 if (looking_at(buf, &i, "* shouts: ") ||
3503 looking_at(buf, &i, "--> ")) {
3504 if (appData.colorize) {
3505 if (oldi > next_out) {
3506 SendToPlayer(&buf[next_out], oldi - next_out);
3509 Colorize(ColorShout, FALSE);
3510 curColor = ColorShout;
3513 started = STARTED_CHATTER;
3517 if (looking_at( buf, &i, "Challenge:")) {
3518 if (appData.colorize) {
3519 if (oldi > next_out) {
3520 SendToPlayer(&buf[next_out], oldi - next_out);
3523 Colorize(ColorChallenge, FALSE);
3524 curColor = ColorChallenge;
3530 if (looking_at(buf, &i, "* offers you") ||
3531 looking_at(buf, &i, "* offers to be") ||
3532 looking_at(buf, &i, "* would like to") ||
3533 looking_at(buf, &i, "* requests to") ||
3534 looking_at(buf, &i, "Your opponent offers") ||
3535 looking_at(buf, &i, "Your opponent requests")) {
3537 if (appData.colorize) {
3538 if (oldi > next_out) {
3539 SendToPlayer(&buf[next_out], oldi - next_out);
3542 Colorize(ColorRequest, FALSE);
3543 curColor = ColorRequest;
3548 if (looking_at(buf, &i, "* (*) seeking")) {
3549 if (appData.colorize) {
3550 if (oldi > next_out) {
3551 SendToPlayer(&buf[next_out], oldi - next_out);
3554 Colorize(ColorSeek, FALSE);
3555 curColor = ColorSeek;
3560 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3562 if (looking_at(buf, &i, "\\ ")) {
3563 if (prevColor != ColorNormal) {
3564 if (oldi > next_out) {
3565 SendToPlayer(&buf[next_out], oldi - next_out);
3568 Colorize(prevColor, TRUE);
3569 curColor = prevColor;
3571 if (savingComment) {
3572 parse_pos = i - oldi;
3573 memcpy(parse, &buf[oldi], parse_pos);
3574 parse[parse_pos] = NULLCHAR;
3575 started = STARTED_COMMENT;
3576 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3577 chattingPartner = savingComment - 3; // kludge to remember the box
3579 started = STARTED_CHATTER;
3584 if (looking_at(buf, &i, "Black Strength :") ||
3585 looking_at(buf, &i, "<<< style 10 board >>>") ||
3586 looking_at(buf, &i, "<10>") ||
3587 looking_at(buf, &i, "#@#")) {
3588 /* Wrong board style */
3590 SendToICS(ics_prefix);
3591 SendToICS("set style 12\n");
3592 SendToICS(ics_prefix);
3593 SendToICS("refresh\n");
3597 if (looking_at(buf, &i, "login:")) {
3598 if (!have_sent_ICS_logon) {
3600 have_sent_ICS_logon = 1;
3601 else // no init script was found
3602 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3603 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3604 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3609 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3610 (looking_at(buf, &i, "\n<12> ") ||
3611 looking_at(buf, &i, "<12> "))) {
3613 if (oldi > next_out) {
3614 SendToPlayer(&buf[next_out], oldi - next_out);
3617 started = STARTED_BOARD;
3622 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3623 looking_at(buf, &i, "<b1> ")) {
3624 if (oldi > next_out) {
3625 SendToPlayer(&buf[next_out], oldi - next_out);
3628 started = STARTED_HOLDINGS;
3633 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3635 /* Header for a move list -- first line */
3637 switch (ics_getting_history) {
3641 case BeginningOfGame:
3642 /* User typed "moves" or "oldmoves" while we
3643 were idle. Pretend we asked for these
3644 moves and soak them up so user can step
3645 through them and/or save them.
3648 gameMode = IcsObserving;
3651 ics_getting_history = H_GOT_UNREQ_HEADER;
3653 case EditGame: /*?*/
3654 case EditPosition: /*?*/
3655 /* Should above feature work in these modes too? */
3656 /* For now it doesn't */
3657 ics_getting_history = H_GOT_UNWANTED_HEADER;
3660 ics_getting_history = H_GOT_UNWANTED_HEADER;
3665 /* Is this the right one? */
3666 if (gameInfo.white && gameInfo.black &&
3667 strcmp(gameInfo.white, star_match[0]) == 0 &&
3668 strcmp(gameInfo.black, star_match[2]) == 0) {
3670 ics_getting_history = H_GOT_REQ_HEADER;
3673 case H_GOT_REQ_HEADER:
3674 case H_GOT_UNREQ_HEADER:
3675 case H_GOT_UNWANTED_HEADER:
3676 case H_GETTING_MOVES:
3677 /* Should not happen */
3678 DisplayError(_("Error gathering move list: two headers"), 0);
3679 ics_getting_history = H_FALSE;
3683 /* Save player ratings into gameInfo if needed */
3684 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3685 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3686 (gameInfo.whiteRating == -1 ||
3687 gameInfo.blackRating == -1)) {
3689 gameInfo.whiteRating = string_to_rating(star_match[1]);
3690 gameInfo.blackRating = string_to_rating(star_match[3]);
3691 if (appData.debugMode)
3692 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3693 gameInfo.whiteRating, gameInfo.blackRating);
3698 if (looking_at(buf, &i,
3699 "* * match, initial time: * minute*, increment: * second")) {
3700 /* Header for a move list -- second line */
3701 /* Initial board will follow if this is a wild game */
3702 if (gameInfo.event != NULL) free(gameInfo.event);
3703 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3704 gameInfo.event = StrSave(str);
3705 /* [HGM] we switched variant. Translate boards if needed. */
3706 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3710 if (looking_at(buf, &i, "Move ")) {
3711 /* Beginning of a move list */
3712 switch (ics_getting_history) {
3714 /* Normally should not happen */
3715 /* Maybe user hit reset while we were parsing */
3718 /* Happens if we are ignoring a move list that is not
3719 * the one we just requested. Common if the user
3720 * tries to observe two games without turning off
3723 case H_GETTING_MOVES:
3724 /* Should not happen */
3725 DisplayError(_("Error gathering move list: nested"), 0);
3726 ics_getting_history = H_FALSE;
3728 case H_GOT_REQ_HEADER:
3729 ics_getting_history = H_GETTING_MOVES;
3730 started = STARTED_MOVES;
3732 if (oldi > next_out) {
3733 SendToPlayer(&buf[next_out], oldi - next_out);
3736 case H_GOT_UNREQ_HEADER:
3737 ics_getting_history = H_GETTING_MOVES;
3738 started = STARTED_MOVES_NOHIDE;
3741 case H_GOT_UNWANTED_HEADER:
3742 ics_getting_history = H_FALSE;
3748 if (looking_at(buf, &i, "% ") ||
3749 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3750 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3751 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3752 soughtPending = FALSE;
3756 if(suppressKibitz) next_out = i;
3757 savingComment = FALSE;
3761 case STARTED_MOVES_NOHIDE:
3762 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3763 parse[parse_pos + i - oldi] = NULLCHAR;
3764 ParseGameHistory(parse);
3766 if (appData.zippyPlay && first.initDone) {
3767 FeedMovesToProgram(&first, forwardMostMove);
3768 if (gameMode == IcsPlayingWhite) {
3769 if (WhiteOnMove(forwardMostMove)) {
3770 if (first.sendTime) {
3771 if (first.useColors) {
3772 SendToProgram("black\n", &first);
3774 SendTimeRemaining(&first, TRUE);
3776 if (first.useColors) {
3777 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3779 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3780 first.maybeThinking = TRUE;
3782 if (first.usePlayother) {
3783 if (first.sendTime) {
3784 SendTimeRemaining(&first, TRUE);
3786 SendToProgram("playother\n", &first);
3792 } else if (gameMode == IcsPlayingBlack) {
3793 if (!WhiteOnMove(forwardMostMove)) {
3794 if (first.sendTime) {
3795 if (first.useColors) {
3796 SendToProgram("white\n", &first);
3798 SendTimeRemaining(&first, FALSE);
3800 if (first.useColors) {
3801 SendToProgram("black\n", &first);
3803 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3804 first.maybeThinking = TRUE;
3806 if (first.usePlayother) {
3807 if (first.sendTime) {
3808 SendTimeRemaining(&first, FALSE);
3810 SendToProgram("playother\n", &first);
3819 if (gameMode == IcsObserving && ics_gamenum == -1) {
3820 /* Moves came from oldmoves or moves command
3821 while we weren't doing anything else.
3823 currentMove = forwardMostMove;
3824 ClearHighlights();/*!!could figure this out*/
3825 flipView = appData.flipView;
3826 DrawPosition(TRUE, boards[currentMove]);
3827 DisplayBothClocks();
3828 snprintf(str, MSG_SIZ, "%s %s %s",
3829 gameInfo.white, _("vs."), gameInfo.black);
3833 /* Moves were history of an active game */
3834 if (gameInfo.resultDetails != NULL) {
3835 free(gameInfo.resultDetails);
3836 gameInfo.resultDetails = NULL;
3839 HistorySet(parseList, backwardMostMove,
3840 forwardMostMove, currentMove-1);
3841 DisplayMove(currentMove - 1);
3842 if (started == STARTED_MOVES) next_out = i;
3843 started = STARTED_NONE;
3844 ics_getting_history = H_FALSE;
3847 case STARTED_OBSERVE:
3848 started = STARTED_NONE;
3849 SendToICS(ics_prefix);
3850 SendToICS("refresh\n");
3856 if(bookHit) { // [HGM] book: simulate book reply
3857 static char bookMove[MSG_SIZ]; // a bit generous?
3859 programStats.nodes = programStats.depth = programStats.time =
3860 programStats.score = programStats.got_only_move = 0;
3861 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3863 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3864 strcat(bookMove, bookHit);
3865 HandleMachineMove(bookMove, &first);
3870 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3871 started == STARTED_HOLDINGS ||
3872 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3873 /* Accumulate characters in move list or board */
3874 parse[parse_pos++] = buf[i];
3877 /* Start of game messages. Mostly we detect start of game
3878 when the first board image arrives. On some versions
3879 of the ICS, though, we need to do a "refresh" after starting
3880 to observe in order to get the current board right away. */
3881 if (looking_at(buf, &i, "Adding game * to observation list")) {
3882 started = STARTED_OBSERVE;
3886 /* Handle auto-observe */
3887 if (appData.autoObserve &&
3888 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3889 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3891 /* Choose the player that was highlighted, if any. */
3892 if (star_match[0][0] == '\033' ||
3893 star_match[1][0] != '\033') {
3894 player = star_match[0];
3896 player = star_match[2];
3898 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3899 ics_prefix, StripHighlightAndTitle(player));
3902 /* Save ratings from notify string */
3903 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3904 player1Rating = string_to_rating(star_match[1]);
3905 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3906 player2Rating = string_to_rating(star_match[3]);
3908 if (appData.debugMode)
3910 "Ratings from 'Game notification:' %s %d, %s %d\n",
3911 player1Name, player1Rating,
3912 player2Name, player2Rating);
3917 /* Deal with automatic examine mode after a game,
3918 and with IcsObserving -> IcsExamining transition */
3919 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3920 looking_at(buf, &i, "has made you an examiner of game *")) {
3922 int gamenum = atoi(star_match[0]);
3923 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3924 gamenum == ics_gamenum) {
3925 /* We were already playing or observing this game;
3926 no need to refetch history */
3927 gameMode = IcsExamining;
3929 pauseExamForwardMostMove = forwardMostMove;
3930 } else if (currentMove < forwardMostMove) {
3931 ForwardInner(forwardMostMove);
3934 /* I don't think this case really can happen */
3935 SendToICS(ics_prefix);
3936 SendToICS("refresh\n");
3941 /* Error messages */
3942 // if (ics_user_moved) {
3943 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3944 if (looking_at(buf, &i, "Illegal move") ||
3945 looking_at(buf, &i, "Not a legal move") ||
3946 looking_at(buf, &i, "Your king is in check") ||
3947 looking_at(buf, &i, "It isn't your turn") ||
3948 looking_at(buf, &i, "It is not your move")) {
3950 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3951 currentMove = forwardMostMove-1;
3952 DisplayMove(currentMove - 1); /* before DMError */
3953 DrawPosition(FALSE, boards[currentMove]);
3954 SwitchClocks(forwardMostMove-1); // [HGM] race
3955 DisplayBothClocks();
3957 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3963 if (looking_at(buf, &i, "still have time") ||
3964 looking_at(buf, &i, "not out of time") ||
3965 looking_at(buf, &i, "either player is out of time") ||
3966 looking_at(buf, &i, "has timeseal; checking")) {
3967 /* We must have called his flag a little too soon */
3968 whiteFlag = blackFlag = FALSE;
3972 if (looking_at(buf, &i, "added * seconds to") ||
3973 looking_at(buf, &i, "seconds were added to")) {
3974 /* Update the clocks */
3975 SendToICS(ics_prefix);
3976 SendToICS("refresh\n");
3980 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3981 ics_clock_paused = TRUE;
3986 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3987 ics_clock_paused = FALSE;
3992 /* Grab player ratings from the Creating: message.
3993 Note we have to check for the special case when
3994 the ICS inserts things like [white] or [black]. */
3995 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3996 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3998 0 player 1 name (not necessarily white)
4000 2 empty, white, or black (IGNORED)
4001 3 player 2 name (not necessarily black)
4004 The names/ratings are sorted out when the game
4005 actually starts (below).
4007 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4008 player1Rating = string_to_rating(star_match[1]);
4009 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4010 player2Rating = string_to_rating(star_match[4]);
4012 if (appData.debugMode)
4014 "Ratings from 'Creating:' %s %d, %s %d\n",
4015 player1Name, player1Rating,
4016 player2Name, player2Rating);
4021 /* Improved generic start/end-of-game messages */
4022 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4023 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4024 /* If tkind == 0: */
4025 /* star_match[0] is the game number */
4026 /* [1] is the white player's name */
4027 /* [2] is the black player's name */
4028 /* For end-of-game: */
4029 /* [3] is the reason for the game end */
4030 /* [4] is a PGN end game-token, preceded by " " */
4031 /* For start-of-game: */
4032 /* [3] begins with "Creating" or "Continuing" */
4033 /* [4] is " *" or empty (don't care). */
4034 int gamenum = atoi(star_match[0]);
4035 char *whitename, *blackname, *why, *endtoken;
4036 ChessMove endtype = EndOfFile;
4039 whitename = star_match[1];
4040 blackname = star_match[2];
4041 why = star_match[3];
4042 endtoken = star_match[4];
4044 whitename = star_match[1];
4045 blackname = star_match[3];
4046 why = star_match[5];
4047 endtoken = star_match[6];
4050 /* Game start messages */
4051 if (strncmp(why, "Creating ", 9) == 0 ||
4052 strncmp(why, "Continuing ", 11) == 0) {
4053 gs_gamenum = gamenum;
4054 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4055 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4056 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4058 if (appData.zippyPlay) {
4059 ZippyGameStart(whitename, blackname);
4062 partnerBoardValid = FALSE; // [HGM] bughouse
4066 /* Game end messages */
4067 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4068 ics_gamenum != gamenum) {
4071 while (endtoken[0] == ' ') endtoken++;
4072 switch (endtoken[0]) {
4075 endtype = GameUnfinished;
4078 endtype = BlackWins;
4081 if (endtoken[1] == '/')
4082 endtype = GameIsDrawn;
4084 endtype = WhiteWins;
4087 GameEnds(endtype, why, GE_ICS);
4089 if (appData.zippyPlay && first.initDone) {
4090 ZippyGameEnd(endtype, why);
4091 if (first.pr == NoProc) {
4092 /* Start the next process early so that we'll
4093 be ready for the next challenge */
4094 StartChessProgram(&first);
4096 /* Send "new" early, in case this command takes
4097 a long time to finish, so that we'll be ready
4098 for the next challenge. */
4099 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4103 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4107 if (looking_at(buf, &i, "Removing game * from observation") ||
4108 looking_at(buf, &i, "no longer observing game *") ||
4109 looking_at(buf, &i, "Game * (*) has no examiners")) {
4110 if (gameMode == IcsObserving &&
4111 atoi(star_match[0]) == ics_gamenum)
4113 /* icsEngineAnalyze */
4114 if (appData.icsEngineAnalyze) {
4121 ics_user_moved = FALSE;
4126 if (looking_at(buf, &i, "no longer examining game *")) {
4127 if (gameMode == IcsExamining &&
4128 atoi(star_match[0]) == ics_gamenum)
4132 ics_user_moved = FALSE;
4137 /* Advance leftover_start past any newlines we find,
4138 so only partial lines can get reparsed */
4139 if (looking_at(buf, &i, "\n")) {
4140 prevColor = curColor;
4141 if (curColor != ColorNormal) {
4142 if (oldi > next_out) {
4143 SendToPlayer(&buf[next_out], oldi - next_out);
4146 Colorize(ColorNormal, FALSE);
4147 curColor = ColorNormal;
4149 if (started == STARTED_BOARD) {
4150 started = STARTED_NONE;
4151 parse[parse_pos] = NULLCHAR;
4152 ParseBoard12(parse);
4155 /* Send premove here */
4156 if (appData.premove) {
4158 if (currentMove == 0 &&
4159 gameMode == IcsPlayingWhite &&
4160 appData.premoveWhite) {
4161 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4162 if (appData.debugMode)
4163 fprintf(debugFP, "Sending premove:\n");
4165 } else if (currentMove == 1 &&
4166 gameMode == IcsPlayingBlack &&
4167 appData.premoveBlack) {
4168 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4169 if (appData.debugMode)
4170 fprintf(debugFP, "Sending premove:\n");
4172 } else if (gotPremove) {
4173 int oldFMM = forwardMostMove;
4175 ClearPremoveHighlights();
4176 if (appData.debugMode)
4177 fprintf(debugFP, "Sending premove:\n");
4178 UserMoveEvent(premoveFromX, premoveFromY,
4179 premoveToX, premoveToY,
4181 if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4182 if(moveList[oldFMM-1][1] != '@')
4183 SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4184 moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4186 SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4191 /* Usually suppress following prompt */
4192 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4193 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4194 if (looking_at(buf, &i, "*% ")) {
4195 savingComment = FALSE;
4200 } else if (started == STARTED_HOLDINGS) {
4202 char new_piece[MSG_SIZ];
4203 started = STARTED_NONE;
4204 parse[parse_pos] = NULLCHAR;
4205 if (appData.debugMode)
4206 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4207 parse, currentMove);
4208 if (sscanf(parse, " game %d", &gamenum) == 1) {
4209 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4210 if (gameInfo.variant == VariantNormal) {
4211 /* [HGM] We seem to switch variant during a game!
4212 * Presumably no holdings were displayed, so we have
4213 * to move the position two files to the right to
4214 * create room for them!
4216 VariantClass newVariant;
4217 switch(gameInfo.boardWidth) { // base guess on board width
4218 case 9: newVariant = VariantShogi; break;
4219 case 10: newVariant = VariantGreat; break;
4220 default: newVariant = VariantCrazyhouse; break;
4222 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4223 /* Get a move list just to see the header, which
4224 will tell us whether this is really bug or zh */
4225 if (ics_getting_history == H_FALSE) {
4226 ics_getting_history = H_REQUESTED;
4227 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4231 new_piece[0] = NULLCHAR;
4232 sscanf(parse, "game %d white [%s black [%s <- %s",
4233 &gamenum, white_holding, black_holding,
4235 white_holding[strlen(white_holding)-1] = NULLCHAR;
4236 black_holding[strlen(black_holding)-1] = NULLCHAR;
4237 /* [HGM] copy holdings to board holdings area */
4238 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4239 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4240 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4242 if (appData.zippyPlay && first.initDone) {
4243 ZippyHoldings(white_holding, black_holding,
4247 if (tinyLayout || smallLayout) {
4248 char wh[16], bh[16];
4249 PackHolding(wh, white_holding);
4250 PackHolding(bh, black_holding);
4251 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4252 gameInfo.white, gameInfo.black);
4254 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4255 gameInfo.white, white_holding, _("vs."),
4256 gameInfo.black, black_holding);
4258 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4259 DrawPosition(FALSE, boards[currentMove]);
4261 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4262 sscanf(parse, "game %d white [%s black [%s <- %s",
4263 &gamenum, white_holding, black_holding,
4265 white_holding[strlen(white_holding)-1] = NULLCHAR;
4266 black_holding[strlen(black_holding)-1] = NULLCHAR;
4267 /* [HGM] copy holdings to partner-board holdings area */
4268 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4269 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4270 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4271 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4272 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4275 /* Suppress following prompt */
4276 if (looking_at(buf, &i, "*% ")) {
4277 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4278 savingComment = FALSE;
4286 i++; /* skip unparsed character and loop back */
4289 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4290 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4291 // SendToPlayer(&buf[next_out], i - next_out);
4292 started != STARTED_HOLDINGS && leftover_start > next_out) {
4293 SendToPlayer(&buf[next_out], leftover_start - next_out);
4297 leftover_len = buf_len - leftover_start;
4298 /* if buffer ends with something we couldn't parse,
4299 reparse it after appending the next read */
4301 } else if (count == 0) {
4302 RemoveInputSource(isr);
4303 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4305 DisplayFatalError(_("Error reading from ICS"), error, 1);
4310 /* Board style 12 looks like this:
4312 <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
4314 * The "<12> " is stripped before it gets to this routine. The two
4315 * trailing 0's (flip state and clock ticking) are later addition, and
4316 * some chess servers may not have them, or may have only the first.
4317 * Additional trailing fields may be added in the future.
4320 #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"
4322 #define RELATION_OBSERVING_PLAYED 0
4323 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4324 #define RELATION_PLAYING_MYMOVE 1
4325 #define RELATION_PLAYING_NOTMYMOVE -1
4326 #define RELATION_EXAMINING 2
4327 #define RELATION_ISOLATED_BOARD -3
4328 #define RELATION_STARTING_POSITION -4 /* FICS only */
4331 ParseBoard12 (char *string)
4335 char *bookHit = NULL; // [HGM] book
4337 GameMode newGameMode;
4338 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4339 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4340 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4341 char to_play, board_chars[200];
4342 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4343 char black[32], white[32];
4345 int prevMove = currentMove;
4348 int fromX, fromY, toX, toY;
4350 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4351 Boolean weird = FALSE, reqFlag = FALSE;
4353 fromX = fromY = toX = toY = -1;
4357 if (appData.debugMode)
4358 fprintf(debugFP, "Parsing board: %s\n", string);
4360 move_str[0] = NULLCHAR;
4361 elapsed_time[0] = NULLCHAR;
4362 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4364 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4365 if(string[i] == ' ') { ranks++; files = 0; }
4367 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4370 for(j = 0; j <i; j++) board_chars[j] = string[j];
4371 board_chars[i] = '\0';
4374 n = sscanf(string, PATTERN, &to_play, &double_push,
4375 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4376 &gamenum, white, black, &relation, &basetime, &increment,
4377 &white_stren, &black_stren, &white_time, &black_time,
4378 &moveNum, str, elapsed_time, move_str, &ics_flip,
4382 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4383 DisplayError(str, 0);
4387 /* Convert the move number to internal form */
4388 moveNum = (moveNum - 1) * 2;
4389 if (to_play == 'B') moveNum++;
4390 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4391 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4397 case RELATION_OBSERVING_PLAYED:
4398 case RELATION_OBSERVING_STATIC:
4399 if (gamenum == -1) {
4400 /* Old ICC buglet */
4401 relation = RELATION_OBSERVING_STATIC;
4403 newGameMode = IcsObserving;
4405 case RELATION_PLAYING_MYMOVE:
4406 case RELATION_PLAYING_NOTMYMOVE:
4408 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4409 IcsPlayingWhite : IcsPlayingBlack;
4410 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4412 case RELATION_EXAMINING:
4413 newGameMode = IcsExamining;
4415 case RELATION_ISOLATED_BOARD:
4417 /* Just display this board. If user was doing something else,
4418 we will forget about it until the next board comes. */
4419 newGameMode = IcsIdle;
4421 case RELATION_STARTING_POSITION:
4422 newGameMode = gameMode;
4426 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4427 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4428 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4429 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4430 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4431 static int lastBgGame = -1;
4433 for (k = 0; k < ranks; k++) {
4434 for (j = 0; j < files; j++)
4435 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4436 if(gameInfo.holdingsWidth > 1) {
4437 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4438 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4441 CopyBoard(partnerBoard, board);
4442 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4443 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4444 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4445 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4446 if(toSqr = strchr(str, '-')) {
4447 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4448 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4449 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4450 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4451 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4452 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4454 DisplayWhiteClock(white_time*fac, to_play == 'W');
4455 DisplayBlackClock(black_time*fac, to_play != 'W');
4456 activePartner = to_play;
4457 if(gamenum != lastBgGame) {
4459 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4462 lastBgGame = gamenum;
4463 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4464 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4465 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4466 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4467 if(!twoBoards) DisplayMessage(partnerStatus, "");
4468 partnerBoardValid = TRUE;
4472 if(appData.dualBoard && appData.bgObserve) {
4473 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4474 SendToICS(ics_prefix), SendToICS("pobserve\n");
4475 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4477 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4482 /* Modify behavior for initial board display on move listing
4485 switch (ics_getting_history) {
4489 case H_GOT_REQ_HEADER:
4490 case H_GOT_UNREQ_HEADER:
4491 /* This is the initial position of the current game */
4492 gamenum = ics_gamenum;
4493 moveNum = 0; /* old ICS bug workaround */
4494 if (to_play == 'B') {
4495 startedFromSetupPosition = TRUE;
4496 blackPlaysFirst = TRUE;
4498 if (forwardMostMove == 0) forwardMostMove = 1;
4499 if (backwardMostMove == 0) backwardMostMove = 1;
4500 if (currentMove == 0) currentMove = 1;
4502 newGameMode = gameMode;
4503 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4505 case H_GOT_UNWANTED_HEADER:
4506 /* This is an initial board that we don't want */
4508 case H_GETTING_MOVES:
4509 /* Should not happen */
4510 DisplayError(_("Error gathering move list: extra board"), 0);
4511 ics_getting_history = H_FALSE;
4515 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4516 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4517 weird && (int)gameInfo.variant < (int)VariantShogi) {
4518 /* [HGM] We seem to have switched variant unexpectedly
4519 * Try to guess new variant from board size
4521 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4522 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4523 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4524 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4525 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4526 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4527 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4528 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4529 /* Get a move list just to see the header, which
4530 will tell us whether this is really bug or zh */
4531 if (ics_getting_history == H_FALSE) {
4532 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4533 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4538 /* Take action if this is the first board of a new game, or of a
4539 different game than is currently being displayed. */
4540 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4541 relation == RELATION_ISOLATED_BOARD) {
4543 /* Forget the old game and get the history (if any) of the new one */
4544 if (gameMode != BeginningOfGame) {
4548 if (appData.autoRaiseBoard) BoardToTop();
4550 if (gamenum == -1) {
4551 newGameMode = IcsIdle;
4552 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4553 appData.getMoveList && !reqFlag) {
4554 /* Need to get game history */
4555 ics_getting_history = H_REQUESTED;
4556 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4560 /* Initially flip the board to have black on the bottom if playing
4561 black or if the ICS flip flag is set, but let the user change
4562 it with the Flip View button. */
4563 flipView = appData.autoFlipView ?
4564 (newGameMode == IcsPlayingBlack) || ics_flip :
4567 /* Done with values from previous mode; copy in new ones */
4568 gameMode = newGameMode;
4570 ics_gamenum = gamenum;
4571 if (gamenum == gs_gamenum) {
4572 int klen = strlen(gs_kind);
4573 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4574 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4575 gameInfo.event = StrSave(str);
4577 gameInfo.event = StrSave("ICS game");
4579 gameInfo.site = StrSave(appData.icsHost);
4580 gameInfo.date = PGNDate();
4581 gameInfo.round = StrSave("-");
4582 gameInfo.white = StrSave(white);
4583 gameInfo.black = StrSave(black);
4584 timeControl = basetime * 60 * 1000;
4586 timeIncrement = increment * 1000;
4587 movesPerSession = 0;
4588 gameInfo.timeControl = TimeControlTagValue();
4589 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4590 if (appData.debugMode) {
4591 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4592 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4593 setbuf(debugFP, NULL);
4596 gameInfo.outOfBook = NULL;
4598 /* Do we have the ratings? */
4599 if (strcmp(player1Name, white) == 0 &&
4600 strcmp(player2Name, black) == 0) {
4601 if (appData.debugMode)
4602 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4603 player1Rating, player2Rating);
4604 gameInfo.whiteRating = player1Rating;
4605 gameInfo.blackRating = player2Rating;
4606 } else if (strcmp(player2Name, white) == 0 &&
4607 strcmp(player1Name, black) == 0) {
4608 if (appData.debugMode)
4609 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4610 player2Rating, player1Rating);
4611 gameInfo.whiteRating = player2Rating;
4612 gameInfo.blackRating = player1Rating;
4614 player1Name[0] = player2Name[0] = NULLCHAR;
4616 /* Silence shouts if requested */
4617 if (appData.quietPlay &&
4618 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4619 SendToICS(ics_prefix);
4620 SendToICS("set shout 0\n");
4624 /* Deal with midgame name changes */
4626 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4627 if (gameInfo.white) free(gameInfo.white);
4628 gameInfo.white = StrSave(white);
4630 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4631 if (gameInfo.black) free(gameInfo.black);
4632 gameInfo.black = StrSave(black);
4636 /* Throw away game result if anything actually changes in examine mode */
4637 if (gameMode == IcsExamining && !newGame) {
4638 gameInfo.result = GameUnfinished;
4639 if (gameInfo.resultDetails != NULL) {
4640 free(gameInfo.resultDetails);
4641 gameInfo.resultDetails = NULL;
4645 /* In pausing && IcsExamining mode, we ignore boards coming
4646 in if they are in a different variation than we are. */
4647 if (pauseExamInvalid) return;
4648 if (pausing && gameMode == IcsExamining) {
4649 if (moveNum <= pauseExamForwardMostMove) {
4650 pauseExamInvalid = TRUE;
4651 forwardMostMove = pauseExamForwardMostMove;
4656 if (appData.debugMode) {
4657 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4659 /* Parse the board */
4660 for (k = 0; k < ranks; k++) {
4661 for (j = 0; j < files; j++)
4662 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4663 if(gameInfo.holdingsWidth > 1) {
4664 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4665 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4668 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4669 board[5][BOARD_RGHT+1] = WhiteAngel;
4670 board[6][BOARD_RGHT+1] = WhiteMarshall;
4671 board[1][0] = BlackMarshall;
4672 board[2][0] = BlackAngel;
4673 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4675 CopyBoard(boards[moveNum], board);
4676 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4678 startedFromSetupPosition =
4679 !CompareBoards(board, initialPosition);
4680 if(startedFromSetupPosition)
4681 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4684 /* [HGM] Set castling rights. Take the outermost Rooks,
4685 to make it also work for FRC opening positions. Note that board12
4686 is really defective for later FRC positions, as it has no way to
4687 indicate which Rook can castle if they are on the same side of King.
4688 For the initial position we grant rights to the outermost Rooks,
4689 and remember thos rights, and we then copy them on positions
4690 later in an FRC game. This means WB might not recognize castlings with
4691 Rooks that have moved back to their original position as illegal,
4692 but in ICS mode that is not its job anyway.
4694 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4695 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4697 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4698 if(board[0][i] == WhiteRook) j = i;
4699 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4700 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4701 if(board[0][i] == WhiteRook) j = i;
4702 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4703 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4704 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4705 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4706 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4707 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4708 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4710 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4711 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4712 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4713 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4714 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4715 if(board[BOARD_HEIGHT-1][k] == bKing)
4716 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4717 if(gameInfo.variant == VariantTwoKings) {
4718 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4719 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4720 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4723 r = boards[moveNum][CASTLING][0] = initialRights[0];
4724 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4725 r = boards[moveNum][CASTLING][1] = initialRights[1];
4726 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4727 r = boards[moveNum][CASTLING][3] = initialRights[3];
4728 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4729 r = boards[moveNum][CASTLING][4] = initialRights[4];
4730 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4731 /* wildcastle kludge: always assume King has rights */
4732 r = boards[moveNum][CASTLING][2] = initialRights[2];
4733 r = boards[moveNum][CASTLING][5] = initialRights[5];
4735 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4736 boards[moveNum][EP_STATUS] = EP_NONE;
4737 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4738 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4739 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4742 if (ics_getting_history == H_GOT_REQ_HEADER ||
4743 ics_getting_history == H_GOT_UNREQ_HEADER) {
4744 /* This was an initial position from a move list, not
4745 the current position */
4749 /* Update currentMove and known move number limits */
4750 newMove = newGame || moveNum > forwardMostMove;
4753 forwardMostMove = backwardMostMove = currentMove = moveNum;
4754 if (gameMode == IcsExamining && moveNum == 0) {
4755 /* Workaround for ICS limitation: we are not told the wild
4756 type when starting to examine a game. But if we ask for
4757 the move list, the move list header will tell us */
4758 ics_getting_history = H_REQUESTED;
4759 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4762 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4763 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4765 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4766 /* [HGM] applied this also to an engine that is silently watching */
4767 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4768 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4769 gameInfo.variant == currentlyInitializedVariant) {
4770 takeback = forwardMostMove - moveNum;
4771 for (i = 0; i < takeback; i++) {
4772 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4773 SendToProgram("undo\n", &first);
4778 forwardMostMove = moveNum;
4779 if (!pausing || currentMove > forwardMostMove)
4780 currentMove = forwardMostMove;
4782 /* New part of history that is not contiguous with old part */
4783 if (pausing && gameMode == IcsExamining) {
4784 pauseExamInvalid = TRUE;
4785 forwardMostMove = pauseExamForwardMostMove;
4788 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4790 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4791 // [HGM] when we will receive the move list we now request, it will be
4792 // fed to the engine from the first move on. So if the engine is not
4793 // in the initial position now, bring it there.
4794 InitChessProgram(&first, 0);
4797 ics_getting_history = H_REQUESTED;
4798 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4801 forwardMostMove = backwardMostMove = currentMove = moveNum;
4804 /* Update the clocks */
4805 if (strchr(elapsed_time, '.')) {
4807 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4808 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4810 /* Time is in seconds */
4811 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4812 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4817 if (appData.zippyPlay && newGame &&
4818 gameMode != IcsObserving && gameMode != IcsIdle &&
4819 gameMode != IcsExamining)
4820 ZippyFirstBoard(moveNum, basetime, increment);
4823 /* Put the move on the move list, first converting
4824 to canonical algebraic form. */
4826 if (appData.debugMode) {
4827 int f = forwardMostMove;
4828 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4829 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4830 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4831 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4832 fprintf(debugFP, "moveNum = %d\n", moveNum);
4833 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4834 setbuf(debugFP, NULL);
4836 if (moveNum <= backwardMostMove) {
4837 /* We don't know what the board looked like before
4839 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4840 strcat(parseList[moveNum - 1], " ");
4841 strcat(parseList[moveNum - 1], elapsed_time);
4842 moveList[moveNum - 1][0] = NULLCHAR;
4843 } else if (strcmp(move_str, "none") == 0) {
4844 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4845 /* Again, we don't know what the board looked like;
4846 this is really the start of the game. */
4847 parseList[moveNum - 1][0] = NULLCHAR;
4848 moveList[moveNum - 1][0] = NULLCHAR;
4849 backwardMostMove = moveNum;
4850 startedFromSetupPosition = TRUE;
4851 fromX = fromY = toX = toY = -1;
4853 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4854 // So we parse the long-algebraic move string in stead of the SAN move
4855 int valid; char buf[MSG_SIZ], *prom;
4857 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4858 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4859 // str looks something like "Q/a1-a2"; kill the slash
4861 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4862 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4863 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4864 strcat(buf, prom); // long move lacks promo specification!
4865 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4866 if(appData.debugMode)
4867 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4868 safeStrCpy(move_str, buf, MSG_SIZ);
4870 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4871 &fromX, &fromY, &toX, &toY, &promoChar)
4872 || ParseOneMove(buf, moveNum - 1, &moveType,
4873 &fromX, &fromY, &toX, &toY, &promoChar);
4874 // end of long SAN patch
4876 (void) CoordsToAlgebraic(boards[moveNum - 1],
4877 PosFlags(moveNum - 1),
4878 fromY, fromX, toY, toX, promoChar,
4879 parseList[moveNum-1]);
4880 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4886 if(!IS_SHOGI(gameInfo.variant))
4887 strcat(parseList[moveNum - 1], "+");
4890 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4891 strcat(parseList[moveNum - 1], "#");
4894 strcat(parseList[moveNum - 1], " ");
4895 strcat(parseList[moveNum - 1], elapsed_time);
4896 /* currentMoveString is set as a side-effect of ParseOneMove */
4897 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4898 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4899 strcat(moveList[moveNum - 1], "\n");
4901 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4902 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4903 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4904 ChessSquare old, new = boards[moveNum][k][j];
4905 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4906 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4907 if(old == new) continue;
4908 if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4909 else if(new == WhiteWazir || new == BlackWazir) {
4910 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4911 boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4912 else boards[moveNum][k][j] = old; // preserve type of Gold
4913 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4914 boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4917 /* Move from ICS was illegal!? Punt. */
4918 if (appData.debugMode) {
4919 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4920 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4922 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4923 strcat(parseList[moveNum - 1], " ");
4924 strcat(parseList[moveNum - 1], elapsed_time);
4925 moveList[moveNum - 1][0] = NULLCHAR;
4926 fromX = fromY = toX = toY = -1;
4929 if (appData.debugMode) {
4930 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4931 setbuf(debugFP, NULL);
4935 /* Send move to chess program (BEFORE animating it). */
4936 if (appData.zippyPlay && !newGame && newMove &&
4937 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4939 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4940 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4941 if (moveList[moveNum - 1][0] == NULLCHAR) {
4942 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4944 DisplayError(str, 0);
4946 if (first.sendTime) {
4947 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4949 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4950 if (firstMove && !bookHit) {
4952 if (first.useColors) {
4953 SendToProgram(gameMode == IcsPlayingWhite ?
4955 "black\ngo\n", &first);
4957 SendToProgram("go\n", &first);
4959 first.maybeThinking = TRUE;
4962 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4963 if (moveList[moveNum - 1][0] == NULLCHAR) {
4964 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4965 DisplayError(str, 0);
4967 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4968 SendMoveToProgram(moveNum - 1, &first);
4975 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4976 /* If move comes from a remote source, animate it. If it
4977 isn't remote, it will have already been animated. */
4978 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4979 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4981 if (!pausing && appData.highlightLastMove) {
4982 SetHighlights(fromX, fromY, toX, toY);
4986 /* Start the clocks */
4987 whiteFlag = blackFlag = FALSE;
4988 appData.clockMode = !(basetime == 0 && increment == 0);
4990 ics_clock_paused = TRUE;
4992 } else if (ticking == 1) {
4993 ics_clock_paused = FALSE;
4995 if (gameMode == IcsIdle ||
4996 relation == RELATION_OBSERVING_STATIC ||
4997 relation == RELATION_EXAMINING ||
4999 DisplayBothClocks();
5003 /* Display opponents and material strengths */
5004 if (gameInfo.variant != VariantBughouse &&
5005 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
5006 if (tinyLayout || smallLayout) {
5007 if(gameInfo.variant == VariantNormal)
5008 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5009 gameInfo.white, white_stren, gameInfo.black, black_stren,
5010 basetime, increment);
5012 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5013 gameInfo.white, white_stren, gameInfo.black, black_stren,
5014 basetime, increment, (int) gameInfo.variant);
5016 if(gameInfo.variant == VariantNormal)
5017 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5018 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5019 basetime, increment);
5021 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5022 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5023 basetime, increment, VariantName(gameInfo.variant));
5026 if (appData.debugMode) {
5027 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5032 /* Display the board */
5033 if (!pausing && !appData.noGUI) {
5035 if (appData.premove)
5037 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5038 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5039 ClearPremoveHighlights();
5041 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5042 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5043 DrawPosition(j, boards[currentMove]);
5045 DisplayMove(moveNum - 1);
5046 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5047 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5048 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
5049 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5053 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5055 if(bookHit) { // [HGM] book: simulate book reply
5056 static char bookMove[MSG_SIZ]; // a bit generous?
5058 programStats.nodes = programStats.depth = programStats.time =
5059 programStats.score = programStats.got_only_move = 0;
5060 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5062 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5063 strcat(bookMove, bookHit);
5064 HandleMachineMove(bookMove, &first);
5073 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5074 ics_getting_history = H_REQUESTED;
5075 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5081 SendToBoth (char *msg)
5082 { // to make it easy to keep two engines in step in dual analysis
5083 SendToProgram(msg, &first);
5084 if(second.analyzing) SendToProgram(msg, &second);
5088 AnalysisPeriodicEvent (int force)
5090 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5091 && !force) || !appData.periodicUpdates)
5094 /* Send . command to Crafty to collect stats */
5097 /* Don't send another until we get a response (this makes
5098 us stop sending to old Crafty's which don't understand
5099 the "." command (sending illegal cmds resets node count & time,
5100 which looks bad)) */
5101 programStats.ok_to_send = 0;
5105 ics_update_width (int new_width)
5107 ics_printf("set width %d\n", new_width);
5111 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5115 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5116 if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5117 sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5118 SendToProgram(buf, cps);
5121 // null move in variant where engine does not understand it (for analysis purposes)
5122 SendBoard(cps, moveNum + 1); // send position after move in stead.
5125 if (cps->useUsermove) {
5126 SendToProgram("usermove ", cps);
5130 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5131 int len = space - parseList[moveNum];
5132 memcpy(buf, parseList[moveNum], len);
5134 buf[len] = NULLCHAR;
5136 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5138 SendToProgram(buf, cps);
5140 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5141 AlphaRank(moveList[moveNum], 4);
5142 SendToProgram(moveList[moveNum], cps);
5143 AlphaRank(moveList[moveNum], 4); // and back
5145 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5146 * the engine. It would be nice to have a better way to identify castle
5148 if(appData.fischerCastling && cps->useOOCastle) {
5149 int fromX = moveList[moveNum][0] - AAA;
5150 int fromY = moveList[moveNum][1] - ONE;
5151 int toX = moveList[moveNum][2] - AAA;
5152 int toY = moveList[moveNum][3] - ONE;
5153 if((boards[moveNum][fromY][fromX] == WhiteKing
5154 && boards[moveNum][toY][toX] == WhiteRook)
5155 || (boards[moveNum][fromY][fromX] == BlackKing
5156 && boards[moveNum][toY][toX] == BlackRook)) {
5157 if(toX > fromX) SendToProgram("O-O\n", cps);
5158 else SendToProgram("O-O-O\n", cps);
5160 else SendToProgram(moveList[moveNum], cps);
5162 if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5163 char *m = moveList[moveNum];
5165 *c = m[7]; // promoChar
5166 if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
5167 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5170 m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5171 else if(*c && m[8]) { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5173 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to three moves
5178 m[2], m[3] - '0', c);
5180 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5183 m[2], m[3] - '0', c);
5184 SendToProgram(buf, cps);
5186 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5187 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5188 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5189 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5190 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5192 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5193 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5194 SendToProgram(buf, cps);
5196 else SendToProgram(moveList[moveNum], cps);
5197 /* End of additions by Tord */
5200 /* [HGM] setting up the opening has brought engine in force mode! */
5201 /* Send 'go' if we are in a mode where machine should play. */
5202 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5203 (gameMode == TwoMachinesPlay ||
5205 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5207 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5208 SendToProgram("go\n", cps);
5209 if (appData.debugMode) {
5210 fprintf(debugFP, "(extra)\n");
5213 setboardSpoiledMachineBlack = 0;
5217 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5219 char user_move[MSG_SIZ];
5222 if(gameInfo.variant == VariantSChess && promoChar) {
5223 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5224 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5225 } else suffix[0] = NULLCHAR;
5229 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5230 (int)moveType, fromX, fromY, toX, toY);
5231 DisplayError(user_move + strlen("say "), 0);
5233 case WhiteKingSideCastle:
5234 case BlackKingSideCastle:
5235 case WhiteQueenSideCastleWild:
5236 case BlackQueenSideCastleWild:
5238 case WhiteHSideCastleFR:
5239 case BlackHSideCastleFR:
5241 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5243 case WhiteQueenSideCastle:
5244 case BlackQueenSideCastle:
5245 case WhiteKingSideCastleWild:
5246 case BlackKingSideCastleWild:
5248 case WhiteASideCastleFR:
5249 case BlackASideCastleFR:
5251 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5253 case WhiteNonPromotion:
5254 case BlackNonPromotion:
5255 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5257 case WhitePromotion:
5258 case BlackPromotion:
5259 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5260 gameInfo.variant == VariantMakruk)
5261 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5262 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5263 PieceToChar(WhiteFerz));
5264 else if(gameInfo.variant == VariantGreat)
5265 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5266 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5267 PieceToChar(WhiteMan));
5269 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5270 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5276 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5277 ToUpper(PieceToChar((ChessSquare) fromX)),
5278 AAA + toX, ONE + toY);
5280 case IllegalMove: /* could be a variant we don't quite understand */
5281 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5283 case WhiteCapturesEnPassant:
5284 case BlackCapturesEnPassant:
5285 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5286 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5289 SendToICS(user_move);
5290 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5291 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5296 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5297 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5298 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5299 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5300 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5303 if(gameMode != IcsExamining) { // is this ever not the case?
5304 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5306 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5307 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5308 } else { // on FICS we must first go to general examine mode
5309 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5311 if(gameInfo.variant != VariantNormal) {
5312 // try figure out wild number, as xboard names are not always valid on ICS
5313 for(i=1; i<=36; i++) {
5314 snprintf(buf, MSG_SIZ, "wild/%d", i);
5315 if(StringToVariant(buf) == gameInfo.variant) break;
5317 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5318 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5319 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5320 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5321 SendToICS(ics_prefix);
5323 if(startedFromSetupPosition || backwardMostMove != 0) {
5324 fen = PositionToFEN(backwardMostMove, NULL, 1);
5325 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5326 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5328 } else { // FICS: everything has to set by separate bsetup commands
5329 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5330 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5332 if(!WhiteOnMove(backwardMostMove)) {
5333 SendToICS("bsetup tomove black\n");
5335 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5336 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5338 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5339 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5341 i = boards[backwardMostMove][EP_STATUS];
5342 if(i >= 0) { // set e.p.
5343 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5349 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5350 SendToICS("bsetup done\n"); // switch to normal examining.
5352 for(i = backwardMostMove; i<last; i++) {
5354 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5355 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5356 int len = strlen(moveList[i]);
5357 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5358 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5362 SendToICS(ics_prefix);
5363 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5366 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5370 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5372 if (rf == DROP_RANK) {
5373 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5374 sprintf(move, "%c@%c%c\n",
5375 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5377 if (promoChar == 'x' || promoChar == NULLCHAR) {
5378 sprintf(move, "%c%c%c%c\n",
5379 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5380 if(killX >= 0 && killY >= 0) {
5381 sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5382 if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5385 sprintf(move, "%c%c%c%c%c\n",
5386 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5387 if(killX >= 0 && killY >= 0) {
5388 sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5389 if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5396 ProcessICSInitScript (FILE *f)
5400 while (fgets(buf, MSG_SIZ, f)) {
5401 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5408 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5410 static ClickType lastClickType;
5413 PieceInString (char *s, ChessSquare piece)
5415 char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5416 while((p = strchr(s, ID))) {
5417 if(!suffix || p[1] == suffix) return TRUE;
5424 Partner (ChessSquare *p)
5425 { // change piece into promotion partner if one shogi-promotes to the other
5426 ChessSquare partner = promoPartner[*p];
5427 if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5428 if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5436 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5437 static int toggleFlag;
5438 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5439 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5440 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5441 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5442 if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5443 if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5445 if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5446 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5447 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5448 else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5449 else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5450 if(!step) step = -1;
5451 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5452 !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5453 promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it
5454 promoSweep == pawn ||
5455 appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5456 (promoSweep == WhiteLion || promoSweep == BlackLion)));
5458 int victim = boards[currentMove][toY][toX];
5459 boards[currentMove][toY][toX] = promoSweep;
5460 DrawPosition(FALSE, boards[currentMove]);
5461 boards[currentMove][toY][toX] = victim;
5463 ChangeDragPiece(promoSweep);
5467 PromoScroll (int x, int y)
5471 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5472 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5473 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5474 if(!step) return FALSE;
5475 lastX = x; lastY = y;
5476 if((promoSweep < BlackPawn) == flipView) step = -step;
5477 if(step > 0) selectFlag = 1;
5478 if(!selectFlag) Sweep(step);
5483 NextPiece (int step)
5485 ChessSquare piece = boards[currentMove][toY][toX];
5488 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5489 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5490 if(!step) step = -1;
5491 } while(PieceToChar(pieceSweep) == '.');
5492 boards[currentMove][toY][toX] = pieceSweep;
5493 DrawPosition(FALSE, boards[currentMove]);
5494 boards[currentMove][toY][toX] = piece;
5496 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5498 AlphaRank (char *move, int n)
5500 // char *p = move, c; int x, y;
5502 if (appData.debugMode) {
5503 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5507 move[2]>='0' && move[2]<='9' &&
5508 move[3]>='a' && move[3]<='x' ) {
5510 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5511 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5513 if(move[0]>='0' && move[0]<='9' &&
5514 move[1]>='a' && move[1]<='x' &&
5515 move[2]>='0' && move[2]<='9' &&
5516 move[3]>='a' && move[3]<='x' ) {
5517 /* input move, Shogi -> normal */
5518 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5519 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5520 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5521 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5524 move[3]>='0' && move[3]<='9' &&
5525 move[2]>='a' && move[2]<='x' ) {
5527 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5528 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5531 move[0]>='a' && move[0]<='x' &&
5532 move[3]>='0' && move[3]<='9' &&
5533 move[2]>='a' && move[2]<='x' ) {
5534 /* output move, normal -> Shogi */
5535 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5536 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5537 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5538 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5539 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5541 if (appData.debugMode) {
5542 fprintf(debugFP, " out = '%s'\n", move);
5546 char yy_textstr[8000];
5548 /* Parser for moves from gnuchess, ICS, or user typein box */
5550 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5552 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5554 switch (*moveType) {
5555 case WhitePromotion:
5556 case BlackPromotion:
5557 case WhiteNonPromotion:
5558 case BlackNonPromotion:
5561 case WhiteCapturesEnPassant:
5562 case BlackCapturesEnPassant:
5563 case WhiteKingSideCastle:
5564 case WhiteQueenSideCastle:
5565 case BlackKingSideCastle:
5566 case BlackQueenSideCastle:
5567 case WhiteKingSideCastleWild:
5568 case WhiteQueenSideCastleWild:
5569 case BlackKingSideCastleWild:
5570 case BlackQueenSideCastleWild:
5571 /* Code added by Tord: */
5572 case WhiteHSideCastleFR:
5573 case WhiteASideCastleFR:
5574 case BlackHSideCastleFR:
5575 case BlackASideCastleFR:
5576 /* End of code added by Tord */
5577 case IllegalMove: /* bug or odd chess variant */
5578 if(currentMoveString[1] == '@') { // illegal drop
5579 *fromX = WhiteOnMove(moveNum) ?
5580 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5581 (int) CharToPiece(ToLower(currentMoveString[0]));
5584 *fromX = currentMoveString[0] - AAA;
5585 *fromY = currentMoveString[1] - ONE;
5586 *toX = currentMoveString[2] - AAA;
5587 *toY = currentMoveString[3] - ONE;
5588 *promoChar = currentMoveString[4];
5589 if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5590 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5591 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5592 if (appData.debugMode) {
5593 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5595 *fromX = *fromY = *toX = *toY = 0;
5598 if (appData.testLegality) {
5599 return (*moveType != IllegalMove);
5601 return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5602 // [HGM] lion: if this is a double move we are less critical
5603 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5608 *fromX = *moveType == WhiteDrop ?
5609 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5610 (int) CharToPiece(ToLower(currentMoveString[0]));
5613 *toX = currentMoveString[2] - AAA;
5614 *toY = currentMoveString[3] - ONE;
5615 *promoChar = NULLCHAR;
5619 case ImpossibleMove:
5629 if (appData.debugMode) {
5630 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5633 *fromX = *fromY = *toX = *toY = 0;
5634 *promoChar = NULLCHAR;
5639 Boolean pushed = FALSE;
5640 char *lastParseAttempt;
5643 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5644 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5645 int fromX, fromY, toX, toY; char promoChar;
5650 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5651 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5652 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5655 endPV = forwardMostMove;
5657 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5658 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5659 lastParseAttempt = pv;
5660 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5661 if(!valid && nr == 0 &&
5662 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5663 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5664 // Hande case where played move is different from leading PV move
5665 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5666 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5667 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5668 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5669 endPV += 2; // if position different, keep this
5670 moveList[endPV-1][0] = fromX + AAA;
5671 moveList[endPV-1][1] = fromY + ONE;
5672 moveList[endPV-1][2] = toX + AAA;
5673 moveList[endPV-1][3] = toY + ONE;
5674 parseList[endPV-1][0] = NULLCHAR;
5675 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5678 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5679 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5680 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5681 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5682 valid++; // allow comments in PV
5686 if(endPV+1 > framePtr) break; // no space, truncate
5689 CopyBoard(boards[endPV], boards[endPV-1]);
5690 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5691 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5692 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5693 CoordsToAlgebraic(boards[endPV - 1],
5694 PosFlags(endPV - 1),
5695 fromY, fromX, toY, toX, promoChar,
5696 parseList[endPV - 1]);
5698 if(atEnd == 2) return; // used hidden, for PV conversion
5699 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5700 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5701 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5702 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5703 DrawPosition(TRUE, boards[currentMove]);
5707 MultiPV (ChessProgramState *cps, int kind)
5708 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5710 for(i=0; i<cps->nrOptions; i++) {
5711 char *s = cps->option[i].name;
5712 if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5713 if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5714 && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5719 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5720 static int multi, pv_margin;
5721 static ChessProgramState *activeCps;
5724 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5726 int startPV, lineStart, origIndex = index;
5727 char *p, buf2[MSG_SIZ];
5728 ChessProgramState *cps = (pane ? &second : &first);
5730 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5731 lastX = x; lastY = y;
5732 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5733 lineStart = startPV = index;
5734 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5735 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5737 do{ while(buf[index] && buf[index] != '\n') index++;
5738 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5740 if(lineStart == 0 && gameMode == AnalyzeMode) {
5742 if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5743 if(n == 0) { // click not on "fewer" or "more"
5744 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5745 pv_margin = cps->option[multi].value;
5746 activeCps = cps; // non-null signals margin adjustment
5748 } else if((multi = MultiPV(cps, 1)) >= 0) {
5749 n += cps->option[multi].value; if(n < 1) n = 1;
5750 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5751 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5752 cps->option[multi].value = n;
5756 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5757 ExcludeClick(origIndex - lineStart);
5759 } else if(!strncmp(buf+lineStart, "dep\t", 4)) { // column headers clicked
5760 Collapse(origIndex - lineStart);
5763 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5764 *start = startPV; *end = index-1;
5765 extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5772 static char buf[10*MSG_SIZ];
5773 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5775 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5776 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5777 for(i = forwardMostMove; i<endPV; i++){
5778 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5779 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5782 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5783 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5784 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5790 LoadPV (int x, int y)
5791 { // called on right mouse click to load PV
5792 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5793 lastX = x; lastY = y;
5794 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5802 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5804 if(pv_margin != activeCps->option[multi].value) {
5806 snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5807 SendToProgram(buf, activeCps);
5808 activeCps->option[multi].value = pv_margin;
5813 if(endPV < 0) return;
5814 if(appData.autoCopyPV) CopyFENToClipboard();
5816 if(extendGame && currentMove > forwardMostMove) {
5817 Boolean saveAnimate = appData.animate;
5819 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5820 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5821 } else storedGames--; // abandon shelved tail of original game
5824 forwardMostMove = currentMove;
5825 currentMove = oldFMM;
5826 appData.animate = FALSE;
5827 ToNrEvent(forwardMostMove);
5828 appData.animate = saveAnimate;
5830 currentMove = forwardMostMove;
5831 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5832 ClearPremoveHighlights();
5833 DrawPosition(TRUE, boards[currentMove]);
5837 MovePV (int x, int y, int h)
5838 { // step through PV based on mouse coordinates (called on mouse move)
5839 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5841 if(activeCps) { // adjusting engine's multi-pv margin
5842 if(x > lastX) pv_margin++; else
5843 if(x < lastX) pv_margin -= (pv_margin > 0);
5846 snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5847 DisplayMessage(buf, "");
5852 // we must somehow check if right button is still down (might be released off board!)
5853 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5854 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5855 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5857 lastX = x; lastY = y;
5859 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5860 if(endPV < 0) return;
5861 if(y < margin) step = 1; else
5862 if(y > h - margin) step = -1;
5863 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5864 currentMove += step;
5865 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5866 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5867 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5868 DrawPosition(FALSE, boards[currentMove]);
5872 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5873 // All positions will have equal probability, but the current method will not provide a unique
5874 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5880 int piecesLeft[(int)BlackPawn];
5881 int seed, nrOfShuffles;
5884 GetPositionNumber ()
5885 { // sets global variable seed
5888 seed = appData.defaultFrcPosition;
5889 if(seed < 0) { // randomize based on time for negative FRC position numbers
5890 for(i=0; i<50; i++) seed += random();
5891 seed = random() ^ random() >> 8 ^ random() << 8;
5892 if(seed<0) seed = -seed;
5897 put (Board board, int pieceType, int rank, int n, int shade)
5898 // put the piece on the (n-1)-th empty squares of the given shade
5902 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5903 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5904 board[rank][i] = (ChessSquare) pieceType;
5905 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5907 piecesLeft[pieceType]--;
5916 AddOnePiece (Board board, int pieceType, int rank, int shade)
5917 // calculate where the next piece goes, (any empty square), and put it there
5921 i = seed % squaresLeft[shade];
5922 nrOfShuffles *= squaresLeft[shade];
5923 seed /= squaresLeft[shade];
5924 put(board, pieceType, rank, i, shade);
5928 AddTwoPieces (Board board, int pieceType, int rank)
5929 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5931 int i, n=squaresLeft[ANY], j=n-1, k;
5933 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5934 i = seed % k; // pick one
5937 while(i >= j) i -= j--;
5938 j = n - 1 - j; i += j;
5939 put(board, pieceType, rank, j, ANY);
5940 put(board, pieceType, rank, i, ANY);
5944 SetUpShuffle (Board board, int number)
5948 GetPositionNumber(); nrOfShuffles = 1;
5950 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5951 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5952 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5954 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5956 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5957 p = (int) board[0][i];
5958 if(p < (int) BlackPawn) piecesLeft[p] ++;
5959 board[0][i] = EmptySquare;
5962 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5963 // shuffles restricted to allow normal castling put KRR first
5964 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5965 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5966 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5967 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5968 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5969 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5970 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5971 put(board, WhiteRook, 0, 0, ANY);
5972 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5975 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5976 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5977 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5978 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5979 while(piecesLeft[p] >= 2) {
5980 AddOnePiece(board, p, 0, LITE);
5981 AddOnePiece(board, p, 0, DARK);
5983 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5986 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5987 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5988 // but we leave King and Rooks for last, to possibly obey FRC restriction
5989 if(p == (int)WhiteRook) continue;
5990 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5991 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5994 // now everything is placed, except perhaps King (Unicorn) and Rooks
5996 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5997 // Last King gets castling rights
5998 while(piecesLeft[(int)WhiteUnicorn]) {
5999 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6000 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
6003 while(piecesLeft[(int)WhiteKing]) {
6004 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6005 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
6010 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
6011 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6014 // Only Rooks can be left; simply place them all
6015 while(piecesLeft[(int)WhiteRook]) {
6016 i = put(board, WhiteRook, 0, 0, ANY);
6017 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6020 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
6022 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
6025 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6026 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6029 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6033 ptclen (const char *s, char *escapes)
6036 if(!*escapes) return strlen(s);
6037 while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6042 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6043 /* [HGM] moved here from winboard.c because of its general usefulness */
6044 /* Basically a safe strcpy that uses the last character as King */
6046 int result = FALSE; int NrPieces;
6047 unsigned char partner[EmptySquare];
6049 if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6050 && NrPieces >= 12 && !(NrPieces&1)) {
6051 int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6053 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6054 for( i=offs=0; i<NrPieces/2-1; i++ ) {
6056 if(map[j] == '/') offs = WhitePBishop - i, j++;
6057 if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6058 table[i+offs] = map[j++];
6059 if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6060 if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6061 if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6063 table[(int) WhiteKing] = map[j++];
6064 for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6066 if(map[j] == '/') offs = WhitePBishop - ii, j++;
6067 i = WHITE_TO_BLACK ii;
6068 if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6069 table[i+offs] = map[j++];
6070 if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6071 if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6072 if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6074 table[(int) BlackKing] = map[j++];
6077 if(*escapes) { // set up promotion pairing
6078 for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6079 // pieceToChar entirely filled, so we can look up specified partners
6080 for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6082 if(c == '^' || c == '-') { // has specified partner
6084 for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6085 if(c == '^') table[i] = '+';
6086 if(p < EmptySquare) {
6087 if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6088 if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6089 promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6091 } else if(c == '*') {
6092 table[i] = partner[i];
6093 promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6105 SetCharTable (unsigned char *table, const char * map)
6107 return SetCharTableEsc(table, map, "");
6111 Prelude (Board board)
6112 { // [HGM] superchess: random selection of exo-pieces
6113 int i, j, k; ChessSquare p;
6114 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6116 GetPositionNumber(); // use FRC position number
6118 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6119 SetCharTable(pieceToChar, appData.pieceToCharTable);
6120 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6121 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6124 j = seed%4; seed /= 4;
6125 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6126 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6127 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
6128 j = seed%3 + (seed%3 >= j); seed /= 3;
6129 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6130 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6131 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
6132 j = seed%3; seed /= 3;
6133 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6134 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6135 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
6136 j = seed%2 + (seed%2 >= j); seed /= 2;
6137 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6138 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6139 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
6140 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
6141 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
6142 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6143 put(board, exoPieces[0], 0, 0, ANY);
6144 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6148 InitPosition (int redraw)
6150 ChessSquare (* pieces)[BOARD_FILES];
6151 int i, j, pawnRow=1, pieceRows=1, overrule,
6152 oldx = gameInfo.boardWidth,
6153 oldy = gameInfo.boardHeight,
6154 oldh = gameInfo.holdingsWidth;
6157 if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6159 /* [AS] Initialize pv info list [HGM] and game status */
6161 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6162 pvInfoList[i].depth = 0;
6163 boards[i][EP_STATUS] = EP_NONE;
6164 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6167 initialRulePlies = 0; /* 50-move counter start */
6169 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6170 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6174 /* [HGM] logic here is completely changed. In stead of full positions */
6175 /* the initialized data only consist of the two backranks. The switch */
6176 /* selects which one we will use, which is than copied to the Board */
6177 /* initialPosition, which for the rest is initialized by Pawns and */
6178 /* empty squares. This initial position is then copied to boards[0], */
6179 /* possibly after shuffling, so that it remains available. */
6181 gameInfo.holdingsWidth = 0; /* default board sizes */
6182 gameInfo.boardWidth = 8;
6183 gameInfo.boardHeight = 8;
6184 gameInfo.holdingsSize = 0;
6185 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6186 for(i=0; i<BOARD_FILES-6; i++)
6187 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6188 initialPosition[EP_STATUS] = EP_NONE;
6189 initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6190 SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6191 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6192 SetCharTable(pieceNickName, appData.pieceNickNames);
6193 else SetCharTable(pieceNickName, "............");
6196 switch (gameInfo.variant) {
6197 case VariantFischeRandom:
6198 shuffleOpenings = TRUE;
6199 appData.fischerCastling = TRUE;
6202 case VariantShatranj:
6203 pieces = ShatranjArray;
6204 nrCastlingRights = 0;
6205 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6208 pieces = makrukArray;
6209 nrCastlingRights = 0;
6210 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6213 pieces = aseanArray;
6214 nrCastlingRights = 0;
6215 SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6217 case VariantTwoKings:
6218 pieces = twoKingsArray;
6221 pieces = GrandArray;
6222 nrCastlingRights = 0;
6223 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6224 gameInfo.boardWidth = 10;
6225 gameInfo.boardHeight = 10;
6226 gameInfo.holdingsSize = 7;
6228 case VariantCapaRandom:
6229 shuffleOpenings = TRUE;
6230 appData.fischerCastling = TRUE;
6231 case VariantCapablanca:
6232 pieces = CapablancaArray;
6233 gameInfo.boardWidth = 10;
6234 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6237 pieces = GothicArray;
6238 gameInfo.boardWidth = 10;
6239 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6242 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6243 gameInfo.holdingsSize = 7;
6244 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6247 pieces = JanusArray;
6248 gameInfo.boardWidth = 10;
6249 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6250 nrCastlingRights = 6;
6251 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6252 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6253 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6254 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6255 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6256 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6259 pieces = FalconArray;
6260 gameInfo.boardWidth = 10;
6261 SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6263 case VariantXiangqi:
6264 pieces = XiangqiArray;
6265 gameInfo.boardWidth = 9;
6266 gameInfo.boardHeight = 10;
6267 nrCastlingRights = 0;
6268 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6271 pieces = ShogiArray;
6272 gameInfo.boardWidth = 9;
6273 gameInfo.boardHeight = 9;
6274 gameInfo.holdingsSize = 7;
6275 nrCastlingRights = 0;
6276 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6279 pieces = ChuArray; pieceRows = 3;
6280 gameInfo.boardWidth = 12;
6281 gameInfo.boardHeight = 12;
6282 nrCastlingRights = 0;
6283 // SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6284 // "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6285 SetCharTableEsc(pieceToChar, "P.BRQSEXOG...HD..^DLI^HNV........^T..^L.C...A^AFT/^F^G^M.^E^X^O^I.^P.^B^R..M^S^C^VK"
6286 "p.brqsexog...hd..^dli^hnv........^t..^l.c...a^aft/^f^g^m.^e^x^o^i.^p.^b^r..m^s^c^vk", SUFFIXES);
6288 case VariantCourier:
6289 pieces = CourierArray;
6290 gameInfo.boardWidth = 12;
6291 nrCastlingRights = 0;
6292 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6294 case VariantKnightmate:
6295 pieces = KnightmateArray;
6296 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6298 case VariantSpartan:
6299 pieces = SpartanArray;
6300 SetCharTable(pieceToChar, "PNBRQ.....................K......lw......g...h......ck");
6304 SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6306 case VariantChuChess:
6307 pieces = ChuChessArray;
6308 gameInfo.boardWidth = 10;
6309 gameInfo.boardHeight = 10;
6310 SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6313 pieces = fairyArray;
6314 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6317 pieces = GreatArray;
6318 gameInfo.boardWidth = 10;
6319 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6320 gameInfo.holdingsSize = 8;
6324 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6325 gameInfo.holdingsSize = 8;
6326 startedFromSetupPosition = TRUE;
6328 case VariantCrazyhouse:
6329 case VariantBughouse:
6331 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6332 gameInfo.holdingsSize = 5;
6334 case VariantWildCastle:
6336 /* !!?shuffle with kings guaranteed to be on d or e file */
6337 shuffleOpenings = 1;
6339 case VariantNoCastle:
6341 nrCastlingRights = 0;
6342 /* !!?unconstrained back-rank shuffle */
6343 shuffleOpenings = 1;
6348 if(appData.NrFiles >= 0) {
6349 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6350 gameInfo.boardWidth = appData.NrFiles;
6352 if(appData.NrRanks >= 0) {
6353 gameInfo.boardHeight = appData.NrRanks;
6355 if(appData.holdingsSize >= 0) {
6356 i = appData.holdingsSize;
6357 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6358 gameInfo.holdingsSize = i;
6360 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6361 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6362 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6364 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6365 if(pawnRow < 1) pawnRow = 1;
6366 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6367 gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6368 if(gameInfo.variant == VariantChu) pawnRow = 3;
6370 /* User pieceToChar list overrules defaults */
6371 if(appData.pieceToCharTable != NULL)
6372 SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6374 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6376 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6377 s = (ChessSquare) 0; /* account holding counts in guard band */
6378 for( i=0; i<BOARD_HEIGHT; i++ )
6379 initialPosition[i][j] = s;
6381 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6382 initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6383 initialPosition[pawnRow][j] = WhitePawn;
6384 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6385 if(gameInfo.variant == VariantXiangqi) {
6387 initialPosition[pawnRow][j] =
6388 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6389 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6390 initialPosition[2][j] = WhiteCannon;
6391 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6395 if(gameInfo.variant == VariantChu) {
6396 if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6397 initialPosition[pawnRow+1][j] = WhiteCobra,
6398 initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6399 for(i=1; i<pieceRows; i++) {
6400 initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6401 initialPosition[BOARD_HEIGHT-1-i][j] = pieces[2*i+1][j-gameInfo.holdingsWidth];
6404 if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6405 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6406 initialPosition[0][j] = WhiteRook;
6407 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6410 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] = pieces[1][j-gameInfo.holdingsWidth];
6412 if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6413 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6416 initialPosition[1][j] = WhiteBishop;
6417 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6419 initialPosition[1][j] = WhiteRook;
6420 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6423 if( nrCastlingRights == -1) {
6424 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6425 /* This sets default castling rights from none to normal corners */
6426 /* Variants with other castling rights must set them themselves above */
6427 nrCastlingRights = 6;
6429 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6430 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6431 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6432 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6433 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6434 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6437 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6438 if(gameInfo.variant == VariantGreat) { // promotion commoners
6439 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6440 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6441 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6442 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6444 if( gameInfo.variant == VariantSChess ) {
6445 initialPosition[1][0] = BlackMarshall;
6446 initialPosition[2][0] = BlackAngel;
6447 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6448 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6449 initialPosition[1][1] = initialPosition[2][1] =
6450 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6452 if (appData.debugMode) {
6453 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6455 if(shuffleOpenings) {
6456 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6457 startedFromSetupPosition = TRUE;
6459 if(startedFromPositionFile) {
6460 /* [HGM] loadPos: use PositionFile for every new game */
6461 CopyBoard(initialPosition, filePosition);
6462 for(i=0; i<nrCastlingRights; i++)
6463 initialRights[i] = filePosition[CASTLING][i];
6464 startedFromSetupPosition = TRUE;
6466 if(*appData.men) LoadPieceDesc(appData.men);
6468 CopyBoard(boards[0], initialPosition);
6470 if(oldx != gameInfo.boardWidth ||
6471 oldy != gameInfo.boardHeight ||
6472 oldv != gameInfo.variant ||
6473 oldh != gameInfo.holdingsWidth
6475 InitDrawingSizes(-2 ,0);
6477 oldv = gameInfo.variant;
6479 DrawPosition(TRUE, boards[currentMove]);
6483 SendBoard (ChessProgramState *cps, int moveNum)
6485 char message[MSG_SIZ];
6487 if (cps->useSetboard) {
6488 char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6489 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6490 SendToProgram(message, cps);
6495 int i, j, left=0, right=BOARD_WIDTH;
6496 /* Kludge to set black to move, avoiding the troublesome and now
6497 * deprecated "black" command.
6499 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6500 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6502 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6504 SendToProgram("edit\n", cps);
6505 SendToProgram("#\n", cps);
6506 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6507 bp = &boards[moveNum][i][left];
6508 for (j = left; j < right; j++, bp++) {
6509 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6510 if ((int) *bp < (int) BlackPawn) {
6511 if(j == BOARD_RGHT+1)
6512 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6513 else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6514 if(message[0] == '+' || message[0] == '~') {
6515 snprintf(message, MSG_SIZ,"%c%c%d+\n",
6516 PieceToChar((ChessSquare)(DEMOTED(*bp))),
6517 AAA + j, ONE + i - '0');
6519 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6520 message[1] = BOARD_RGHT - 1 - j + '1';
6521 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6523 SendToProgram(message, cps);
6528 SendToProgram("c\n", cps);
6529 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6530 bp = &boards[moveNum][i][left];
6531 for (j = left; j < right; j++, bp++) {
6532 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6533 if (((int) *bp != (int) EmptySquare)
6534 && ((int) *bp >= (int) BlackPawn)) {
6535 if(j == BOARD_LEFT-2)
6536 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6537 else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6538 AAA + j, ONE + i - '0');
6539 if(message[0] == '+' || message[0] == '~') {
6540 snprintf(message, MSG_SIZ,"%c%c%d+\n",
6541 PieceToChar((ChessSquare)(DEMOTED(*bp))),
6542 AAA + j, ONE + i - '0');
6544 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6545 message[1] = BOARD_RGHT - 1 - j + '1';
6546 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6548 SendToProgram(message, cps);
6553 SendToProgram(".\n", cps);
6555 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6558 char exclusionHeader[MSG_SIZ];
6559 int exCnt, excludePtr;
6560 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6561 static Exclusion excluTab[200];
6562 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6568 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6569 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6575 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6576 excludePtr = 24; exCnt = 0;
6581 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6582 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6583 char buf[2*MOVE_LEN], *p;
6584 Exclusion *e = excluTab;
6586 for(i=0; i<exCnt; i++)
6587 if(e[i].ff == fromX && e[i].fr == fromY &&
6588 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6589 if(i == exCnt) { // was not in exclude list; add it
6590 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6591 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6592 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6595 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6596 excludePtr++; e[i].mark = excludePtr++;
6597 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6600 exclusionHeader[e[i].mark] = state;
6604 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6605 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6609 if((signed char)promoChar == -1) { // kludge to indicate best move
6610 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6611 return 1; // if unparsable, abort
6613 // update exclusion map (resolving toggle by consulting existing state)
6614 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6616 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6617 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6618 excludeMap[k] |= 1<<j;
6619 else excludeMap[k] &= ~(1<<j);
6621 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6623 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6624 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6626 return (state == '+');
6630 ExcludeClick (int index)
6633 Exclusion *e = excluTab;
6634 if(index < 25) { // none, best or tail clicked
6635 if(index < 13) { // none: include all
6636 WriteMap(0); // clear map
6637 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6638 SendToBoth("include all\n"); // and inform engine
6639 } else if(index > 18) { // tail
6640 if(exclusionHeader[19] == '-') { // tail was excluded
6641 SendToBoth("include all\n");
6642 WriteMap(0); // clear map completely
6643 // now re-exclude selected moves
6644 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6645 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6646 } else { // tail was included or in mixed state
6647 SendToBoth("exclude all\n");
6648 WriteMap(0xFF); // fill map completely
6649 // now re-include selected moves
6650 j = 0; // count them
6651 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6652 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6653 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6656 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6659 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6660 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6661 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6668 DefaultPromoChoice (int white)
6671 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6672 gameInfo.variant == VariantMakruk)
6673 result = WhiteFerz; // no choice
6674 else if(gameInfo.variant == VariantASEAN)
6675 result = WhiteRook; // no choice
6676 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6677 result= WhiteKing; // in Suicide Q is the last thing we want
6678 else if(gameInfo.variant == VariantSpartan)
6679 result = white ? WhiteQueen : WhiteAngel;
6680 else result = WhiteQueen;
6681 if(!white) result = WHITE_TO_BLACK result;
6685 static int autoQueen; // [HGM] oneclick
6688 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6690 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6691 /* [HGM] add Shogi promotions */
6692 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6693 ChessSquare piece, partner;
6697 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6698 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6700 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6701 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6704 piece = boards[currentMove][fromY][fromX];
6705 if(gameInfo.variant == VariantChu) {
6706 promotionZoneSize = BOARD_HEIGHT/3;
6707 highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6708 } else if(gameInfo.variant == VariantShogi) {
6709 promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6710 highestPromotingPiece = (int)WhiteAlfil;
6711 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6712 promotionZoneSize = 3;
6715 // Treat Lance as Pawn when it is not representing Amazon or Lance
6716 if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6717 if(piece == WhiteLance) piece = WhitePawn; else
6718 if(piece == BlackLance) piece = BlackPawn;
6721 // next weed out all moves that do not touch the promotion zone at all
6722 if((int)piece >= BlackPawn) {
6723 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6725 if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6726 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6728 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6729 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6730 if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6734 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6736 // weed out mandatory Shogi promotions
6737 if(gameInfo.variant == VariantShogi) {
6738 if(piece >= BlackPawn) {
6739 if(toY == 0 && piece == BlackPawn ||
6740 toY == 0 && piece == BlackQueen ||
6741 toY <= 1 && piece == BlackKnight) {
6746 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6747 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6748 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6755 // weed out obviously illegal Pawn moves
6756 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6757 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6758 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6759 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6760 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6761 // note we are not allowed to test for valid (non-)capture, due to premove
6764 // we either have a choice what to promote to, or (in Shogi) whether to promote
6765 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6766 gameInfo.variant == VariantMakruk) {
6767 ChessSquare p=BlackFerz; // no choice
6768 while(p < EmptySquare) { //but make sure we use piece that exists
6769 *promoChoice = PieceToChar(p++);
6770 if(*promoChoice != '.') break;
6772 if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6774 // no sense asking what we must promote to if it is going to explode...
6775 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6776 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6779 // give caller the default choice even if we will not make it
6780 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6781 partner = piece; // pieces can promote if the pieceToCharTable says so
6782 if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6783 else if(Partner(&partner)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6784 if( sweepSelect && gameInfo.variant != VariantGreat
6785 && gameInfo.variant != VariantGrand
6786 && gameInfo.variant != VariantSuper) return FALSE;
6787 if(autoQueen) return FALSE; // predetermined
6789 // suppress promotion popup on illegal moves that are not premoves
6790 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6791 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6792 if(appData.testLegality && !premove) {
6793 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6794 fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6795 if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6796 if(moveType != WhitePromotion && moveType != BlackPromotion)
6804 InPalace (int row, int column)
6805 { /* [HGM] for Xiangqi */
6806 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6807 column < (BOARD_WIDTH + 4)/2 &&
6808 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6813 PieceForSquare (int x, int y)
6815 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6818 return boards[currentMove][y][x];
6822 OKToStartUserMove (int x, int y)
6824 ChessSquare from_piece;
6827 if (matchMode) return FALSE;
6828 if (gameMode == EditPosition) return TRUE;
6830 if (x >= 0 && y >= 0)
6831 from_piece = boards[currentMove][y][x];
6833 from_piece = EmptySquare;
6835 if (from_piece == EmptySquare) return FALSE;
6837 white_piece = (int)from_piece >= (int)WhitePawn &&
6838 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6842 case TwoMachinesPlay:
6850 case MachinePlaysWhite:
6851 case IcsPlayingBlack:
6852 if (appData.zippyPlay) return FALSE;
6854 DisplayMoveError(_("You are playing Black"));
6859 case MachinePlaysBlack:
6860 case IcsPlayingWhite:
6861 if (appData.zippyPlay) return FALSE;
6863 DisplayMoveError(_("You are playing White"));
6868 case PlayFromGameFile:
6869 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6872 if (!white_piece && WhiteOnMove(currentMove)) {
6873 DisplayMoveError(_("It is White's turn"));
6876 if (white_piece && !WhiteOnMove(currentMove)) {
6877 DisplayMoveError(_("It is Black's turn"));
6880 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6881 /* Editing correspondence game history */
6882 /* Could disallow this or prompt for confirmation */
6887 case BeginningOfGame:
6888 if (appData.icsActive) return FALSE;
6889 if (!appData.noChessProgram) {
6891 DisplayMoveError(_("You are playing White"));
6898 if (!white_piece && WhiteOnMove(currentMove)) {
6899 DisplayMoveError(_("It is White's turn"));
6902 if (white_piece && !WhiteOnMove(currentMove)) {
6903 DisplayMoveError(_("It is Black's turn"));
6912 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6913 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6914 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6915 && gameMode != AnalyzeFile && gameMode != Training) {
6916 DisplayMoveError(_("Displayed position is not current"));
6923 OnlyMove (int *x, int *y, Boolean captures)
6925 DisambiguateClosure cl;
6926 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6928 case MachinePlaysBlack:
6929 case IcsPlayingWhite:
6930 case BeginningOfGame:
6931 if(!WhiteOnMove(currentMove)) return FALSE;
6933 case MachinePlaysWhite:
6934 case IcsPlayingBlack:
6935 if(WhiteOnMove(currentMove)) return FALSE;
6942 cl.pieceIn = EmptySquare;
6947 cl.promoCharIn = NULLCHAR;
6948 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6949 if( cl.kind == NormalMove ||
6950 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6951 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6952 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6959 if(cl.kind != ImpossibleMove) return FALSE;
6960 cl.pieceIn = EmptySquare;
6965 cl.promoCharIn = NULLCHAR;
6966 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6967 if( cl.kind == NormalMove ||
6968 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6969 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6970 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6975 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6981 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6982 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6983 int lastLoadGameUseList = FALSE;
6984 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6985 ChessMove lastLoadGameStart = EndOfFile;
6987 Boolean addToBookFlag;
6988 static Board rightsBoard, nullBoard;
6991 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
6995 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6997 /* Check if the user is playing in turn. This is complicated because we
6998 let the user "pick up" a piece before it is his turn. So the piece he
6999 tried to pick up may have been captured by the time he puts it down!
7000 Therefore we use the color the user is supposed to be playing in this
7001 test, not the color of the piece that is currently on the starting
7002 square---except in EditGame mode, where the user is playing both
7003 sides; fortunately there the capture race can't happen. (It can
7004 now happen in IcsExamining mode, but that's just too bad. The user
7005 will get a somewhat confusing message in that case.)
7010 case TwoMachinesPlay:
7014 /* We switched into a game mode where moves are not accepted,
7015 perhaps while the mouse button was down. */
7018 case MachinePlaysWhite:
7019 /* User is moving for Black */
7020 if (WhiteOnMove(currentMove)) {
7021 DisplayMoveError(_("It is White's turn"));
7026 case MachinePlaysBlack:
7027 /* User is moving for White */
7028 if (!WhiteOnMove(currentMove)) {
7029 DisplayMoveError(_("It is Black's turn"));
7034 case PlayFromGameFile:
7035 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7038 case BeginningOfGame:
7041 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7042 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7043 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7044 /* User is moving for Black */
7045 if (WhiteOnMove(currentMove)) {
7046 DisplayMoveError(_("It is White's turn"));
7050 /* User is moving for White */
7051 if (!WhiteOnMove(currentMove)) {
7052 DisplayMoveError(_("It is Black's turn"));
7058 case IcsPlayingBlack:
7059 /* User is moving for Black */
7060 if (WhiteOnMove(currentMove)) {
7061 if (!appData.premove) {
7062 DisplayMoveError(_("It is White's turn"));
7063 } else if (toX >= 0 && toY >= 0) {
7066 premoveFromX = fromX;
7067 premoveFromY = fromY;
7068 premovePromoChar = promoChar;
7070 if (appData.debugMode)
7071 fprintf(debugFP, "Got premove: fromX %d,"
7072 "fromY %d, toX %d, toY %d\n",
7073 fromX, fromY, toX, toY);
7075 DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7080 case IcsPlayingWhite:
7081 /* User is moving for White */
7082 if (!WhiteOnMove(currentMove)) {
7083 if (!appData.premove) {
7084 DisplayMoveError(_("It is Black's turn"));
7085 } else if (toX >= 0 && toY >= 0) {
7088 premoveFromX = fromX;
7089 premoveFromY = fromY;
7090 premovePromoChar = promoChar;
7092 if (appData.debugMode)
7093 fprintf(debugFP, "Got premove: fromX %d,"
7094 "fromY %d, toX %d, toY %d\n",
7095 fromX, fromY, toX, toY);
7097 DrawPosition(TRUE, boards[currentMove]);
7106 /* EditPosition, empty square, or different color piece;
7107 click-click move is possible */
7108 if (toX == -2 || toY == -2) {
7109 boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7110 DrawPosition(FALSE, boards[currentMove]);
7112 } else if (toX >= 0 && toY >= 0) {
7113 if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7114 ChessSquare p = boards[0][rf][ff];
7115 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7116 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7117 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
7118 int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7119 DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7122 } else rightsBoard[toY][toX] = 0; // revoke rights on moving
7123 boards[0][toY][toX] = boards[0][fromY][fromX];
7124 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7125 if(boards[0][fromY][0] != EmptySquare) {
7126 if(boards[0][fromY][1]) boards[0][fromY][1]--;
7127 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
7130 if(fromX == BOARD_RGHT+1) {
7131 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7132 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7133 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7136 boards[0][fromY][fromX] = gatingPiece;
7138 DrawPosition(FALSE, boards[currentMove]);
7144 if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7145 pup = boards[currentMove][toY][toX];
7147 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7148 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7149 if( pup != EmptySquare ) return;
7150 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7151 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7152 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7153 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7154 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7155 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7156 while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7160 /* [HGM] always test for legality, to get promotion info */
7161 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7162 fromY, fromX, toY, toX, promoChar);
7164 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7166 if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7168 /* [HGM] but possibly ignore an IllegalMove result */
7169 if (appData.testLegality) {
7170 if (moveType == IllegalMove || moveType == ImpossibleMove) {
7171 DisplayMoveError(_("Illegal move"));
7176 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7177 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7178 ClearPremoveHighlights(); // was included
7179 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
7180 DrawPosition(FALSE, NULL);
7184 if(addToBookFlag) { // adding moves to book
7185 char buf[MSG_SIZ], move[MSG_SIZ];
7186 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7187 if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7188 killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7189 snprintf(buf, MSG_SIZ, " 0.0%% 1 %s\n", move);
7191 addToBookFlag = FALSE;
7196 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7199 /* Common tail of UserMoveEvent and DropMenuEvent */
7201 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7205 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7206 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7207 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7208 if(WhiteOnMove(currentMove)) {
7209 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7211 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7215 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7216 move type in caller when we know the move is a legal promotion */
7217 if(moveType == NormalMove && promoChar)
7218 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7220 /* [HGM] <popupFix> The following if has been moved here from
7221 UserMoveEvent(). Because it seemed to belong here (why not allow
7222 piece drops in training games?), and because it can only be
7223 performed after it is known to what we promote. */
7224 if (gameMode == Training) {
7225 /* compare the move played on the board to the next move in the
7226 * game. If they match, display the move and the opponent's response.
7227 * If they don't match, display an error message.
7231 CopyBoard(testBoard, boards[currentMove]);
7232 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7234 if (CompareBoards(testBoard, boards[currentMove+1])) {
7235 ForwardInner(currentMove+1);
7237 /* Autoplay the opponent's response.
7238 * if appData.animate was TRUE when Training mode was entered,
7239 * the response will be animated.
7241 saveAnimate = appData.animate;
7242 appData.animate = animateTraining;
7243 ForwardInner(currentMove+1);
7244 appData.animate = saveAnimate;
7246 /* check for the end of the game */
7247 if (currentMove >= forwardMostMove) {
7248 gameMode = PlayFromGameFile;
7250 SetTrainingModeOff();
7251 DisplayInformation(_("End of game"));
7254 DisplayError(_("Incorrect move"), 0);
7259 /* Ok, now we know that the move is good, so we can kill
7260 the previous line in Analysis Mode */
7261 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7262 && currentMove < forwardMostMove) {
7263 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7264 else forwardMostMove = currentMove;
7269 /* If we need the chess program but it's dead, restart it */
7270 ResurrectChessProgram();
7272 /* A user move restarts a paused game*/
7276 thinkOutput[0] = NULLCHAR;
7278 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7280 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7281 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7285 if (gameMode == BeginningOfGame) {
7286 if (appData.noChessProgram) {
7287 gameMode = EditGame;
7291 gameMode = MachinePlaysBlack;
7294 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7296 if (first.sendName) {
7297 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7298 SendToProgram(buf, &first);
7305 /* Relay move to ICS or chess engine */
7306 if (appData.icsActive) {
7307 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7308 gameMode == IcsExamining) {
7309 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7310 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7312 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7314 // also send plain move, in case ICS does not understand atomic claims
7315 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7319 if (first.sendTime && (gameMode == BeginningOfGame ||
7320 gameMode == MachinePlaysWhite ||
7321 gameMode == MachinePlaysBlack)) {
7322 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7324 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7325 // [HGM] book: if program might be playing, let it use book
7326 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7327 first.maybeThinking = TRUE;
7328 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7329 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7330 SendBoard(&first, currentMove+1);
7331 if(second.analyzing) {
7332 if(!second.useSetboard) SendToProgram("undo\n", &second);
7333 SendBoard(&second, currentMove+1);
7336 SendMoveToProgram(forwardMostMove-1, &first);
7337 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7339 if (currentMove == cmailOldMove + 1) {
7340 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7344 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7348 if(appData.testLegality)
7349 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7355 if (WhiteOnMove(currentMove)) {
7356 GameEnds(BlackWins, "Black mates", GE_PLAYER);
7358 GameEnds(WhiteWins, "White mates", GE_PLAYER);
7362 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7367 case MachinePlaysBlack:
7368 case MachinePlaysWhite:
7369 /* disable certain menu options while machine is thinking */
7370 SetMachineThinkingEnables();
7377 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7378 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7380 if(bookHit) { // [HGM] book: simulate book reply
7381 static char bookMove[MSG_SIZ]; // a bit generous?
7383 programStats.nodes = programStats.depth = programStats.time =
7384 programStats.score = programStats.got_only_move = 0;
7385 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7387 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7388 strcat(bookMove, bookHit);
7389 HandleMachineMove(bookMove, &first);
7395 MarkByFEN(char *fen)
7398 if(!appData.markers || !appData.highlightDragging) return;
7399 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7400 r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7404 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7405 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7406 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7407 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7408 if(*fen == 'T') marker[r][f++] = 0; else
7409 if(*fen == 'Y') marker[r][f++] = 1; else
7410 if(*fen == 'G') marker[r][f++] = 3; else
7411 if(*fen == 'B') marker[r][f++] = 4; else
7412 if(*fen == 'C') marker[r][f++] = 5; else
7413 if(*fen == 'M') marker[r][f++] = 6; else
7414 if(*fen == 'W') marker[r][f++] = 7; else
7415 if(*fen == 'D') marker[r][f++] = 8; else
7416 if(*fen == 'R') marker[r][f++] = 2; else {
7417 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7420 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7424 DrawPosition(TRUE, NULL);
7427 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7430 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7432 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7433 Markers *m = (Markers *) closure;
7434 if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7435 kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7436 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7437 || kind == WhiteCapturesEnPassant
7438 || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7439 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7442 static int hoverSavedValid;
7445 MarkTargetSquares (int clear)
7448 if(clear) { // no reason to ever suppress clearing
7449 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7450 hoverSavedValid = 0;
7451 if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7454 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7455 !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7456 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7457 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7458 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7460 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7463 DrawPosition(FALSE, NULL);
7467 Explode (Board board, int fromX, int fromY, int toX, int toY)
7469 if(gameInfo.variant == VariantAtomic &&
7470 (board[toY][toX] != EmptySquare || // capture?
7471 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7472 board[fromY][fromX] == BlackPawn )
7474 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7480 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7483 CanPromote (ChessSquare piece, int y)
7485 int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7486 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7487 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7488 if(IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantXiangqi ||
7489 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7490 (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7491 gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7492 return (piece == BlackPawn && y <= zone ||
7493 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7494 piece == BlackLance && y <= zone ||
7495 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7499 HoverEvent (int xPix, int yPix, int x, int y)
7501 static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7503 if(!first.highlight) return;
7504 if(fromX != oldFromX || fromY != oldFromY) oldX = oldY = -1; // kludge to fake entry on from-click
7505 if(x == oldX && y == oldY) return; // only do something if we enter new square
7506 oldFromX = fromX; oldFromY = fromY;
7507 if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7508 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7509 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7510 hoverSavedValid = 1;
7511 } else if(oldX != x || oldY != y) {
7512 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7513 if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7514 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7515 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7516 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7518 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7519 SendToProgram(buf, &first);
7522 // SetHighlights(fromX, fromY, x, y);
7526 void ReportClick(char *action, int x, int y)
7528 char buf[MSG_SIZ]; // Inform engine of what user does
7530 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7531 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7532 legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7533 if(!first.highlight || gameMode == EditPosition) return;
7534 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7535 SendToProgram(buf, &first);
7538 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7541 LeftClick (ClickType clickType, int xPix, int yPix)
7544 Boolean saveAnimate;
7545 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7546 char promoChoice = NULLCHAR;
7548 static TimeMark lastClickTime, prevClickTime;
7550 if(flashing) return;
7552 x = EventToSquare(xPix, BOARD_WIDTH);
7553 y = EventToSquare(yPix, BOARD_HEIGHT);
7554 if (!flipView && y >= 0) {
7555 y = BOARD_HEIGHT - 1 - y;
7557 if (flipView && x >= 0) {
7558 x = BOARD_WIDTH - 1 - x;
7561 if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7563 RightClick(clickType, xPix, yPix, &dummy, &dummy);
7568 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7570 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7572 if (clickType == Press) ErrorPopDown();
7573 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7575 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7576 defaultPromoChoice = promoSweep;
7577 promoSweep = EmptySquare; // terminate sweep
7578 promoDefaultAltered = TRUE;
7579 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7582 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7583 if(clickType == Release) return; // ignore upclick of click-click destination
7584 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7585 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7586 if(gameInfo.holdingsWidth &&
7587 (WhiteOnMove(currentMove)
7588 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7589 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7590 // click in right holdings, for determining promotion piece
7591 ChessSquare p = boards[currentMove][y][x];
7592 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7593 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7594 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7595 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7600 DrawPosition(FALSE, boards[currentMove]);
7604 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7605 if(clickType == Press
7606 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7607 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7608 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7611 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7612 // could be static click on premove from-square: abort premove
7614 ClearPremoveHighlights();
7617 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7618 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7620 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7621 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7622 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7623 defaultPromoChoice = DefaultPromoChoice(side);
7626 autoQueen = appData.alwaysPromoteToQueen;
7630 gatingPiece = EmptySquare;
7631 if (clickType != Press) {
7632 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7633 DragPieceEnd(xPix, yPix); dragging = 0;
7634 DrawPosition(FALSE, NULL);
7638 doubleClick = FALSE;
7639 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7640 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7642 fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1;
7643 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7644 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7645 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7647 if (OKToStartUserMove(fromX, fromY)) {
7649 ReportClick("lift", x, y);
7650 MarkTargetSquares(0);
7651 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7652 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7653 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7654 promoSweep = defaultPromoChoice;
7655 selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7656 Sweep(0); // Pawn that is going to promote: preview promotion piece
7657 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7659 if (appData.highlightDragging) {
7660 SetHighlights(fromX, fromY, -1, -1);
7664 } else fromX = fromY = -1;
7670 if (clickType == Press && gameMode != EditPosition) {
7675 // ignore off-board to clicks
7676 if(y < 0 || x < 0) return;
7678 /* Check if clicking again on the same color piece */
7679 fromP = boards[currentMove][fromY][fromX];
7680 toP = boards[currentMove][y][x];
7681 frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7682 if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7683 marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7684 ((WhitePawn <= fromP && fromP <= WhiteKing &&
7685 WhitePawn <= toP && toP <= WhiteKing &&
7686 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7687 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7688 (BlackPawn <= fromP && fromP <= BlackKing &&
7689 BlackPawn <= toP && toP <= BlackKing &&
7690 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7691 !(fromP == BlackKing && toP == BlackRook && frc)))) {
7692 /* Clicked again on same color piece -- changed his mind */
7693 second = (x == fromX && y == fromY);
7694 killX = killY = kill2X = kill2Y = -1;
7695 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7696 second = FALSE; // first double-click rather than scond click
7697 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7699 promoDefaultAltered = FALSE;
7700 if(!second) MarkTargetSquares(1);
7701 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7702 if (appData.highlightDragging) {
7703 SetHighlights(x, y, -1, -1);
7707 if (OKToStartUserMove(x, y)) {
7708 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7709 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7710 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7711 gatingPiece = boards[currentMove][fromY][fromX];
7712 else gatingPiece = doubleClick ? fromP : EmptySquare;
7714 fromY = y; dragging = 1;
7715 if(!second) ReportClick("lift", x, y);
7716 MarkTargetSquares(0);
7717 DragPieceBegin(xPix, yPix, FALSE);
7718 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7719 promoSweep = defaultPromoChoice;
7720 selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7721 Sweep(0); // Pawn that is going to promote: preview promotion piece
7725 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7728 // ignore clicks on holdings
7729 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7732 if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7733 gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7734 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7738 if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7739 DragPieceEnd(xPix, yPix); dragging = 0;
7741 // a deferred attempt to click-click move an empty square on top of a piece
7742 boards[currentMove][y][x] = EmptySquare;
7744 DrawPosition(FALSE, boards[currentMove]);
7745 fromX = fromY = -1; clearFlag = 0;
7748 if (appData.animateDragging) {
7749 /* Undo animation damage if any */
7750 DrawPosition(FALSE, NULL);
7753 /* Second up/down in same square; just abort move */
7756 gatingPiece = EmptySquare;
7759 ClearPremoveHighlights();
7760 MarkTargetSquares(-1);
7761 DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7763 /* First upclick in same square; start click-click mode */
7764 SetHighlights(x, y, -1, -1);
7771 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7772 fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7773 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7774 DisplayMessage(_("only marked squares are legal"),"");
7775 DrawPosition(TRUE, NULL);
7776 return; // ignore to-click
7779 /* we now have a different from- and (possibly off-board) to-square */
7780 /* Completed move */
7781 if(!sweepSelecting) {
7786 piece = boards[currentMove][fromY][fromX];
7788 saveAnimate = appData.animate;
7789 if (clickType == Press) {
7790 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7791 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7792 // must be Edit Position mode with empty-square selected
7793 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7794 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7797 if(dragging == 2) { // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7800 if(x == killX && y == killY) { // second click on this square, which was selected as first-leg target
7801 killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1; // this informs us no second leg is coming, so treat as to-click without intermediate
7803 if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7804 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7805 if(appData.sweepSelect) {
7806 promoSweep = defaultPromoChoice;
7807 if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7808 selectFlag = 0; lastX = xPix; lastY = yPix;
7809 ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7810 saveFlash = appData.flashCount; appData.flashCount = 0;
7811 Sweep(0); // Pawn that is going to promote: preview promotion piece
7813 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7814 MarkTargetSquares(1);
7816 return; // promo popup appears on up-click
7818 /* Finish clickclick move */
7819 if (appData.animate || appData.highlightLastMove) {
7820 SetHighlights(fromX, fromY, toX, toY);
7824 MarkTargetSquares(1);
7825 } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7826 sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7827 *promoRestrict = 0; appData.flashCount = saveFlash;
7828 if (appData.animate || appData.highlightLastMove) {
7829 SetHighlights(fromX, fromY, toX, toY);
7833 MarkTargetSquares(1);
7836 // [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
7837 /* Finish drag move */
7838 if (appData.highlightLastMove) {
7839 SetHighlights(fromX, fromY, toX, toY);
7844 if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7845 defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7846 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7847 if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7848 dragging *= 2; // flag button-less dragging if we are dragging
7849 MarkTargetSquares(1);
7850 if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7852 kill2X = killX; kill2Y = killY;
7853 killX = x; killY = y; // remember this square as intermediate
7854 ReportClick("put", x, y); // and inform engine
7855 ReportClick("lift", x, y);
7856 MarkTargetSquares(0);
7860 DragPieceEnd(xPix, yPix); dragging = 0;
7861 /* Don't animate move and drag both */
7862 appData.animate = FALSE;
7863 MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7866 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7867 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7868 ChessSquare piece = boards[currentMove][fromY][fromX];
7869 if(gameMode == EditPosition && piece != EmptySquare &&
7870 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7873 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7874 n = PieceToNumber(piece - (int)BlackPawn);
7875 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7876 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7877 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7879 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7880 n = PieceToNumber(piece);
7881 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7882 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7883 boards[currentMove][n][BOARD_WIDTH-2]++;
7885 boards[currentMove][fromY][fromX] = EmptySquare;
7889 MarkTargetSquares(1);
7890 DrawPosition(TRUE, boards[currentMove]);
7894 // off-board moves should not be highlighted
7895 if(x < 0 || y < 0) ClearHighlights();
7896 else ReportClick("put", x, y);
7898 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7900 if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7902 if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7903 SetHighlights(fromX, fromY, toX, toY);
7904 MarkTargetSquares(1);
7905 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7906 // [HGM] super: promotion to captured piece selected from holdings
7907 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7908 promotionChoice = TRUE;
7909 // kludge follows to temporarily execute move on display, without promoting yet
7910 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7911 boards[currentMove][toY][toX] = p;
7912 DrawPosition(FALSE, boards[currentMove]);
7913 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7914 boards[currentMove][toY][toX] = q;
7915 DisplayMessage("Click in holdings to choose piece", "");
7918 DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
7919 PromotionPopUp(promoChoice);
7921 int oldMove = currentMove;
7922 flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
7923 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7924 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7925 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
7926 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7927 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7928 DrawPosition(TRUE, boards[currentMove]);
7932 appData.animate = saveAnimate;
7933 if (appData.animate || appData.animateDragging) {
7934 /* Undo animation damage if needed */
7935 // DrawPosition(FALSE, NULL);
7940 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7941 { // front-end-free part taken out of PieceMenuPopup
7942 int whichMenu; int xSqr, ySqr;
7944 if(seekGraphUp) { // [HGM] seekgraph
7945 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7946 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7950 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7951 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7952 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7953 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7954 if(action == Press) {
7955 originalFlip = flipView;
7956 flipView = !flipView; // temporarily flip board to see game from partners perspective
7957 DrawPosition(TRUE, partnerBoard);
7958 DisplayMessage(partnerStatus, "");
7960 } else if(action == Release) {
7961 flipView = originalFlip;
7962 DrawPosition(TRUE, boards[currentMove]);
7968 xSqr = EventToSquare(x, BOARD_WIDTH);
7969 ySqr = EventToSquare(y, BOARD_HEIGHT);
7970 if (action == Release) {
7971 if(pieceSweep != EmptySquare) {
7972 EditPositionMenuEvent(pieceSweep, toX, toY);
7973 pieceSweep = EmptySquare;
7974 } else UnLoadPV(); // [HGM] pv
7976 if (action != Press) return -2; // return code to be ignored
7979 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7981 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7982 if (xSqr < 0 || ySqr < 0) return -1;
7983 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7984 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7985 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7986 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7990 if(!appData.icsEngineAnalyze) return -1;
7991 case IcsPlayingWhite:
7992 case IcsPlayingBlack:
7993 if(!appData.zippyPlay) goto noZip;
7996 case MachinePlaysWhite:
7997 case MachinePlaysBlack:
7998 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7999 if (!appData.dropMenu) {
8001 return 2; // flag front-end to grab mouse events
8003 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8004 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8007 if (xSqr < 0 || ySqr < 0) return -1;
8008 if (!appData.dropMenu || appData.testLegality &&
8009 gameInfo.variant != VariantBughouse &&
8010 gameInfo.variant != VariantCrazyhouse) return -1;
8011 whichMenu = 1; // drop menu
8017 if (((*fromX = xSqr) < 0) ||
8018 ((*fromY = ySqr) < 0)) {
8019 *fromX = *fromY = -1;
8023 *fromX = BOARD_WIDTH - 1 - *fromX;
8025 *fromY = BOARD_HEIGHT - 1 - *fromY;
8031 Wheel (int dir, int x, int y)
8033 if(gameMode == EditPosition) {
8034 int xSqr = EventToSquare(x, BOARD_WIDTH);
8035 int ySqr = EventToSquare(y, BOARD_HEIGHT);
8036 if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8037 if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8039 boards[currentMove][ySqr][xSqr] += dir;
8040 if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8041 if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8042 } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8043 DrawPosition(FALSE, boards[currentMove]);
8044 } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8048 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8050 // char * hint = lastHint;
8051 FrontEndProgramStats stats;
8053 stats.which = cps == &first ? 0 : 1;
8054 stats.depth = cpstats->depth;
8055 stats.nodes = cpstats->nodes;
8056 stats.score = cpstats->score;
8057 stats.time = cpstats->time;
8058 stats.pv = cpstats->movelist;
8059 stats.hint = lastHint;
8060 stats.an_move_index = 0;
8061 stats.an_move_count = 0;
8063 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8064 stats.hint = cpstats->move_name;
8065 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8066 stats.an_move_count = cpstats->nr_moves;
8069 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
8071 if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8072 && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8074 SetProgramStats( &stats );
8078 ClearEngineOutputPane (int which)
8080 static FrontEndProgramStats dummyStats;
8081 dummyStats.which = which;
8082 dummyStats.pv = "#";
8083 SetProgramStats( &dummyStats );
8086 #define MAXPLAYERS 500
8089 TourneyStandings (int display)
8091 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8092 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8093 char result, *p, *names[MAXPLAYERS];
8095 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8096 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8097 names[0] = p = strdup(appData.participants);
8098 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8100 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8102 while(result = appData.results[nr]) {
8103 color = Pairing(nr, nPlayers, &w, &b, &dummy);
8104 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8105 wScore = bScore = 0;
8107 case '+': wScore = 2; break;
8108 case '-': bScore = 2; break;
8109 case '=': wScore = bScore = 1; break;
8111 case '*': return strdup("busy"); // tourney not finished
8119 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8120 for(w=0; w<nPlayers; w++) {
8122 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8123 ranking[w] = b; points[w] = bScore; score[b] = -2;
8125 p = malloc(nPlayers*34+1);
8126 for(w=0; w<nPlayers && w<display; w++)
8127 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8133 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8134 { // count all piece types
8136 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8137 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8138 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8141 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8142 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8143 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8144 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8145 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
8146 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8151 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8153 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8154 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8156 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8157 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8158 if(myPawns == 2 && nMine == 3) // KPP
8159 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8160 if(myPawns == 1 && nMine == 2) // KP
8161 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8162 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8163 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8164 if(myPawns) return FALSE;
8165 if(pCnt[WhiteRook+side])
8166 return pCnt[BlackRook-side] ||
8167 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8168 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8169 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8170 if(pCnt[WhiteCannon+side]) {
8171 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8172 return majorDefense || pCnt[BlackAlfil-side] >= 2;
8174 if(pCnt[WhiteKnight+side])
8175 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8180 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8182 VariantClass v = gameInfo.variant;
8184 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8185 if(v == VariantShatranj) return TRUE; // always winnable through baring
8186 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8187 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8189 if(v == VariantXiangqi) {
8190 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8192 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8193 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8194 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8195 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8196 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8197 if(stale) // we have at least one last-rank P plus perhaps C
8198 return majors // KPKX
8199 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8201 return pCnt[WhiteFerz+side] // KCAK
8202 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8203 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8204 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8206 } else if(v == VariantKnightmate) {
8207 if(nMine == 1) return FALSE;
8208 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8209 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8210 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8212 if(nMine == 1) return FALSE; // bare King
8213 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
8214 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8215 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8216 // by now we have King + 1 piece (or multiple Bishops on the same color)
8217 if(pCnt[WhiteKnight+side])
8218 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8219 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8220 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8222 return (pCnt[BlackKnight-side]); // KBKN, KFKN
8223 if(pCnt[WhiteAlfil+side])
8224 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8225 if(pCnt[WhiteWazir+side])
8226 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8233 CompareWithRights (Board b1, Board b2)
8236 if(!CompareBoards(b1, b2)) return FALSE;
8237 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8238 /* compare castling rights */
8239 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8240 rights++; /* King lost rights, while rook still had them */
8241 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8242 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8243 rights++; /* but at least one rook lost them */
8245 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8247 if( b1[CASTLING][5] != NoRights ) {
8248 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8255 Adjudicate (ChessProgramState *cps)
8256 { // [HGM] some adjudications useful with buggy engines
8257 // [HGM] adjudicate: made into separate routine, which now can be called after every move
8258 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8259 // Actually ending the game is now based on the additional internal condition canAdjudicate.
8260 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8261 int k, drop, count = 0; static int bare = 1;
8262 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8263 Boolean canAdjudicate = !appData.icsActive;
8265 // most tests only when we understand the game, i.e. legality-checking on
8266 if( appData.testLegality )
8267 { /* [HGM] Some more adjudications for obstinate engines */
8268 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8269 static int moveCount = 6;
8271 char *reason = NULL;
8273 /* Count what is on board. */
8274 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8276 /* Some material-based adjudications that have to be made before stalemate test */
8277 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8278 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8279 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8280 if(canAdjudicate && appData.checkMates) {
8282 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8283 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8284 "Xboard adjudication: King destroyed", GE_XBOARD );
8289 /* Bare King in Shatranj (loses) or Losers (wins) */
8290 if( nrW == 1 || nrB == 1) {
8291 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8292 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
8293 if(canAdjudicate && appData.checkMates) {
8295 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8296 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8297 "Xboard adjudication: Bare king", GE_XBOARD );
8301 if( gameInfo.variant == VariantShatranj && --bare < 0)
8303 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8304 if(canAdjudicate && appData.checkMates) {
8305 /* but only adjudicate if adjudication enabled */
8307 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8308 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8309 "Xboard adjudication: Bare king", GE_XBOARD );
8316 // don't wait for engine to announce game end if we can judge ourselves
8317 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8319 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8320 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
8321 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8322 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8325 reason = "Xboard adjudication: 3rd check";
8326 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8337 reason = "Xboard adjudication: Stalemate";
8338 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8339 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
8340 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8341 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
8342 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8343 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8344 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8345 EP_CHECKMATE : EP_WINS);
8346 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8347 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8351 reason = "Xboard adjudication: Checkmate";
8352 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8353 if(gameInfo.variant == VariantShogi) {
8354 if(forwardMostMove > backwardMostMove
8355 && moveList[forwardMostMove-1][1] == '@'
8356 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8357 reason = "XBoard adjudication: pawn-drop mate";
8358 boards[forwardMostMove][EP_STATUS] = EP_WINS;
8364 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8366 result = GameIsDrawn; break;
8368 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8370 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8374 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8376 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8377 GameEnds( result, reason, GE_XBOARD );
8381 /* Next absolutely insufficient mating material. */
8382 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8383 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8384 { /* includes KBK, KNK, KK of KBKB with like Bishops */
8386 /* always flag draws, for judging claims */
8387 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8389 if(canAdjudicate && appData.materialDraws) {
8390 /* but only adjudicate them if adjudication enabled */
8391 if(engineOpponent) {
8392 SendToProgram("force\n", engineOpponent); // suppress reply
8393 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8395 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8400 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8401 if(gameInfo.variant == VariantXiangqi ?
8402 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8404 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8405 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
8406 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
8407 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8409 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8410 { /* if the first 3 moves do not show a tactical win, declare draw */
8411 if(engineOpponent) {
8412 SendToProgram("force\n", engineOpponent); // suppress reply
8413 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8415 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8418 } else moveCount = 6;
8421 // Repetition draws and 50-move rule can be applied independently of legality testing
8423 /* Check for rep-draws */
8425 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8426 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8427 for(k = forwardMostMove-2;
8428 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8429 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8430 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8433 if(CompareBoards(boards[k], boards[forwardMostMove])) {
8434 /* compare castling rights */
8435 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8436 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8437 rights++; /* King lost rights, while rook still had them */
8438 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8439 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8440 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8441 rights++; /* but at least one rook lost them */
8443 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8444 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8446 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8447 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8448 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8451 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8452 && appData.drawRepeats > 1) {
8453 /* adjudicate after user-specified nr of repeats */
8454 int result = GameIsDrawn;
8455 char *details = "XBoard adjudication: repetition draw";
8456 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8457 // [HGM] xiangqi: check for forbidden perpetuals
8458 int m, ourPerpetual = 1, hisPerpetual = 1;
8459 for(m=forwardMostMove; m>k; m-=2) {
8460 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8461 ourPerpetual = 0; // the current mover did not always check
8462 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8463 hisPerpetual = 0; // the opponent did not always check
8465 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8466 ourPerpetual, hisPerpetual);
8467 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8468 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8469 details = "Xboard adjudication: perpetual checking";
8471 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8472 break; // (or we would have caught him before). Abort repetition-checking loop.
8474 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8475 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8477 details = "Xboard adjudication: repetition";
8479 } else // it must be XQ
8480 // Now check for perpetual chases
8481 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8482 hisPerpetual = PerpetualChase(k, forwardMostMove);
8483 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8484 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8485 static char resdet[MSG_SIZ];
8486 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8488 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8490 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8491 break; // Abort repetition-checking loop.
8493 // if neither of us is checking or chasing all the time, or both are, it is draw
8495 if(engineOpponent) {
8496 SendToProgram("force\n", engineOpponent); // suppress reply
8497 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8499 GameEnds( result, details, GE_XBOARD );
8502 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8503 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8507 /* Now we test for 50-move draws. Determine ply count */
8508 count = forwardMostMove;
8509 /* look for last irreversble move */
8510 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8512 /* if we hit starting position, add initial plies */
8513 if( count == backwardMostMove )
8514 count -= initialRulePlies;
8515 count = forwardMostMove - count;
8516 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8517 // adjust reversible move counter for checks in Xiangqi
8518 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8519 if(i < backwardMostMove) i = backwardMostMove;
8520 while(i <= forwardMostMove) {
8521 lastCheck = inCheck; // check evasion does not count
8522 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8523 if(inCheck || lastCheck) count--; // check does not count
8528 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8529 /* this is used to judge if draw claims are legal */
8530 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8531 if(engineOpponent) {
8532 SendToProgram("force\n", engineOpponent); // suppress reply
8533 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8535 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8539 /* if draw offer is pending, treat it as a draw claim
8540 * when draw condition present, to allow engines a way to
8541 * claim draws before making their move to avoid a race
8542 * condition occurring after their move
8544 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8546 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8547 p = "Draw claim: 50-move rule";
8548 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8549 p = "Draw claim: 3-fold repetition";
8550 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8551 p = "Draw claim: insufficient mating material";
8552 if( p != NULL && canAdjudicate) {
8553 if(engineOpponent) {
8554 SendToProgram("force\n", engineOpponent); // suppress reply
8555 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8557 GameEnds( GameIsDrawn, p, GE_XBOARD );
8562 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8563 if(engineOpponent) {
8564 SendToProgram("force\n", engineOpponent); // suppress reply
8565 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8567 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8573 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8574 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8575 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8580 int pieces[10], squares[10], cnt=0, r, f, res;
8582 static PPROBE_EGBB probeBB;
8583 if(!appData.testLegality) return 10;
8584 if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8585 if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8586 if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8587 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8588 ChessSquare piece = boards[forwardMostMove][r][f];
8589 int black = (piece >= BlackPawn);
8590 int type = piece - black*BlackPawn;
8591 if(piece == EmptySquare) continue;
8592 if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8593 if(type == WhiteKing) type = WhiteQueen + 1;
8594 type = egbbCode[type];
8595 squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8596 pieces[cnt] = type + black*6;
8597 if(++cnt > 5) return 11;
8599 pieces[cnt] = squares[cnt] = 0;
8601 if(loaded == 2) return 13; // loading failed before
8603 char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8606 loaded = 2; // prepare for failure
8607 if(!path) return 13; // no egbb installed
8608 strncpy(buf, path + 8, MSG_SIZ);
8609 if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8610 snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8611 lib = LoadLibrary(buf);
8612 if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8613 loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8614 probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8615 if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8616 p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8617 loaded = 1; // success!
8619 res = probeBB(forwardMostMove & 1, pieces, squares);
8620 return res > 0 ? 1 : res < 0 ? -1 : 0;
8624 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8625 { // [HGM] book: this routine intercepts moves to simulate book replies
8626 char *bookHit = NULL;
8628 if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8630 snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8631 SendToProgram(buf, cps);
8633 //first determine if the incoming move brings opponent into his book
8634 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8635 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8636 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8637 if(bookHit != NULL && !cps->bookSuspend) {
8638 // make sure opponent is not going to reply after receiving move to book position
8639 SendToProgram("force\n", cps);
8640 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8642 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8643 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8644 // now arrange restart after book miss
8646 // after a book hit we never send 'go', and the code after the call to this routine
8647 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8648 char buf[MSG_SIZ], *move = bookHit;
8650 int fromX, fromY, toX, toY;
8654 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8655 &fromX, &fromY, &toX, &toY, &promoChar)) {
8656 (void) CoordsToAlgebraic(boards[forwardMostMove],
8657 PosFlags(forwardMostMove),
8658 fromY, fromX, toY, toX, promoChar, move);
8660 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8664 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8665 SendToProgram(buf, cps);
8666 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8667 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8668 SendToProgram("go\n", cps);
8669 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8670 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8671 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8672 SendToProgram("go\n", cps);
8673 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8675 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8679 LoadError (char *errmess, ChessProgramState *cps)
8680 { // unloads engine and switches back to -ncp mode if it was first
8681 if(cps->initDone) return FALSE;
8682 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8683 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8686 appData.noChessProgram = TRUE;
8687 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8688 gameMode = BeginningOfGame; ModeHighlight();
8691 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8692 DisplayMessage("", ""); // erase waiting message
8693 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8698 ChessProgramState *savedState;
8700 DeferredBookMove (void)
8702 if(savedState->lastPing != savedState->lastPong)
8703 ScheduleDelayedEvent(DeferredBookMove, 10);
8705 HandleMachineMove(savedMessage, savedState);
8708 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8709 static ChessProgramState *stalledEngine;
8710 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8713 HandleMachineMove (char *message, ChessProgramState *cps)
8715 static char firstLeg[20], legs;
8716 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8717 char realname[MSG_SIZ];
8718 int fromX, fromY, toX, toY;
8720 char promoChar, roar;
8725 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8726 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8727 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8728 DisplayError(_("Invalid pairing from pairing engine"), 0);
8731 pairingReceived = 1;
8733 return; // Skim the pairing messages here.
8736 oldError = cps->userError; cps->userError = 0;
8738 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8740 * Kludge to ignore BEL characters
8742 while (*message == '\007') message++;
8745 * [HGM] engine debug message: ignore lines starting with '#' character
8747 if(cps->debug && *message == '#') return;
8750 * Look for book output
8752 if (cps == &first && bookRequested) {
8753 if (message[0] == '\t' || message[0] == ' ') {
8754 /* Part of the book output is here; append it */
8755 strcat(bookOutput, message);
8756 strcat(bookOutput, " \n");
8758 } else if (bookOutput[0] != NULLCHAR) {
8759 /* All of book output has arrived; display it */
8760 char *p = bookOutput;
8761 while (*p != NULLCHAR) {
8762 if (*p == '\t') *p = ' ';
8765 DisplayInformation(bookOutput);
8766 bookRequested = FALSE;
8767 /* Fall through to parse the current output */
8772 * Look for machine move.
8774 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8775 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8777 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8778 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8779 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8780 stalledEngine = cps;
8781 if(appData.ponderNextMove) { // bring opponent out of ponder
8782 if(gameMode == TwoMachinesPlay) {
8783 if(cps->other->pause)
8784 PauseEngine(cps->other);
8786 SendToProgram("easy\n", cps->other);
8795 /* This method is only useful on engines that support ping */
8796 if(abortEngineThink) {
8797 if (appData.debugMode) {
8798 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8800 SendToProgram("undo\n", cps);
8804 if (cps->lastPing != cps->lastPong) {
8805 /* Extra move from before last new; ignore */
8806 if (appData.debugMode) {
8807 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8814 int machineWhite = FALSE;
8817 case BeginningOfGame:
8818 /* Extra move from before last reset; ignore */
8819 if (appData.debugMode) {
8820 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8827 /* Extra move after we tried to stop. The mode test is
8828 not a reliable way of detecting this problem, but it's
8829 the best we can do on engines that don't support ping.
8831 if (appData.debugMode) {
8832 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8833 cps->which, gameMode);
8835 SendToProgram("undo\n", cps);
8838 case MachinePlaysWhite:
8839 case IcsPlayingWhite:
8840 machineWhite = TRUE;
8843 case MachinePlaysBlack:
8844 case IcsPlayingBlack:
8845 machineWhite = FALSE;
8848 case TwoMachinesPlay:
8849 machineWhite = (cps->twoMachinesColor[0] == 'w');
8852 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8853 if (appData.debugMode) {
8855 "Ignoring move out of turn by %s, gameMode %d"
8856 ", forwardMost %d\n",
8857 cps->which, gameMode, forwardMostMove);
8863 if(cps->alphaRank) AlphaRank(machineMove, 4);
8865 // [HGM] lion: (some very limited) support for Alien protocol
8866 killX = killY = kill2X = kill2Y = -1;
8867 if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8868 if(legs++) return; // middle leg contains only redundant info, ignore (but count it)
8869 safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8872 if(p = strchr(machineMove, ',')) { // we got both legs in one (happens on book move)
8873 char *q = strchr(p+1, ','); // second comma?
8874 safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8875 if(q) legs = 2, p = q; else legs = 1; // with 3-leg move we clipof first two legs!
8876 safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8878 if(firstLeg[0]) { // there was a previous leg;
8879 // only support case where same piece makes two step
8880 char buf[20], *p = machineMove+1, *q = buf+1, f;
8881 safeStrCpy(buf, machineMove, 20);
8882 while(isdigit(*q)) q++; // find start of to-square
8883 safeStrCpy(machineMove, firstLeg, 20);
8884 while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8885 if(legs == 2) sscanf(p, "%c%d", &f, &kill2Y), kill2X = f - AAA, kill2Y -= ONE - '0'; // in 3-leg move 2nd kill is to-sqr of 1st leg
8886 else if(*p == *buf) // if first-leg to not equal to second-leg from first leg says unmodified (assume it is King move of castling)
8887 safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8888 sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8889 firstLeg[0] = NULLCHAR; legs = 0;
8892 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8893 &fromX, &fromY, &toX, &toY, &promoChar)) {
8894 /* Machine move could not be parsed; ignore it. */
8895 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8896 machineMove, _(cps->which));
8897 DisplayMoveError(buf1);
8898 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8899 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8900 if (gameMode == TwoMachinesPlay) {
8901 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8907 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8908 /* So we have to redo legality test with true e.p. status here, */
8909 /* to make sure an illegal e.p. capture does not slip through, */
8910 /* to cause a forfeit on a justified illegal-move complaint */
8911 /* of the opponent. */
8912 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8914 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8915 fromY, fromX, toY, toX, promoChar);
8916 if(moveType == IllegalMove) {
8917 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8918 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8919 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8922 } else if(!appData.fischerCastling)
8923 /* [HGM] Kludge to handle engines that send FRC-style castling
8924 when they shouldn't (like TSCP-Gothic) */
8926 case WhiteASideCastleFR:
8927 case BlackASideCastleFR:
8929 currentMoveString[2]++;
8931 case WhiteHSideCastleFR:
8932 case BlackHSideCastleFR:
8934 currentMoveString[2]--;
8936 default: ; // nothing to do, but suppresses warning of pedantic compilers
8939 hintRequested = FALSE;
8940 lastHint[0] = NULLCHAR;
8941 bookRequested = FALSE;
8942 /* Program may be pondering now */
8943 cps->maybeThinking = TRUE;
8944 if (cps->sendTime == 2) cps->sendTime = 1;
8945 if (cps->offeredDraw) cps->offeredDraw--;
8947 /* [AS] Save move info*/
8948 pvInfoList[ forwardMostMove ].score = programStats.score;
8949 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8950 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8952 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8954 /* Test suites abort the 'game' after one move */
8955 if(*appData.finger) {
8957 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8958 if(!f) f = fopen(appData.finger, "w");
8959 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8960 else { DisplayFatalError("Bad output file", errno, 0); return; }
8962 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8965 if(solvingTime >= 0) {
8966 snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
8967 totalTime += solvingTime; first.matchWins++; solvingTime = -1;
8969 snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
8970 if(solvingTime == -2) second.matchWins++;
8972 OutputKibitz(2, buf1);
8973 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8976 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8977 if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8980 while( count < adjudicateLossPlies ) {
8981 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8984 score = -score; /* Flip score for winning side */
8987 if( score > appData.adjudicateLossThreshold ) {
8994 if( count >= adjudicateLossPlies ) {
8995 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8997 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8998 "Xboard adjudication",
9005 if(Adjudicate(cps)) {
9006 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9007 return; // [HGM] adjudicate: for all automatic game ends
9011 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9013 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9014 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9016 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9018 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9020 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9021 char buf[3*MSG_SIZ];
9023 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9024 programStats.score / 100.,
9026 programStats.time / 100.,
9027 (unsigned int)programStats.nodes,
9028 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9029 programStats.movelist);
9035 /* [AS] Clear stats for next move */
9036 ClearProgramStats();
9037 thinkOutput[0] = NULLCHAR;
9038 hiddenThinkOutputState = 0;
9041 if (gameMode == TwoMachinesPlay) {
9042 /* [HGM] relaying draw offers moved to after reception of move */
9043 /* and interpreting offer as claim if it brings draw condition */
9044 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9045 SendToProgram("draw\n", cps->other);
9047 if (cps->other->sendTime) {
9048 SendTimeRemaining(cps->other,
9049 cps->other->twoMachinesColor[0] == 'w');
9051 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9052 if (firstMove && !bookHit) {
9054 if (cps->other->useColors) {
9055 SendToProgram(cps->other->twoMachinesColor, cps->other);
9057 SendToProgram("go\n", cps->other);
9059 cps->other->maybeThinking = TRUE;
9062 roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9064 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9066 if (!pausing && appData.ringBellAfterMoves) {
9067 if(!roar) RingBell();
9071 * Reenable menu items that were disabled while
9072 * machine was thinking
9074 if (gameMode != TwoMachinesPlay)
9075 SetUserThinkingEnables();
9077 // [HGM] book: after book hit opponent has received move and is now in force mode
9078 // force the book reply into it, and then fake that it outputted this move by jumping
9079 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9081 static char bookMove[MSG_SIZ]; // a bit generous?
9083 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9084 strcat(bookMove, bookHit);
9087 programStats.nodes = programStats.depth = programStats.time =
9088 programStats.score = programStats.got_only_move = 0;
9089 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9091 if(cps->lastPing != cps->lastPong) {
9092 savedMessage = message; // args for deferred call
9094 ScheduleDelayedEvent(DeferredBookMove, 10);
9103 /* Set special modes for chess engines. Later something general
9104 * could be added here; for now there is just one kludge feature,
9105 * needed because Crafty 15.10 and earlier don't ignore SIGINT
9106 * when "xboard" is given as an interactive command.
9108 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9109 cps->useSigint = FALSE;
9110 cps->useSigterm = FALSE;
9112 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9113 ParseFeatures(message+8, cps);
9114 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9117 if (!strncmp(message, "setup ", 6) &&
9118 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9119 NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9120 ) { // [HGM] allow first engine to define opening position
9121 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9122 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9124 if(sscanf(message, "setup (%s", buf) == 1) {
9125 s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9126 ASSIGN(appData.pieceToCharTable, buf);
9128 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9130 while(message[s] && message[s++] != ' ');
9131 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9132 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9133 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9134 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
9135 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9136 if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9137 startedFromSetupPosition = FALSE;
9140 if(startedFromSetupPosition) return;
9141 ParseFEN(boards[0], &dummy, message+s, FALSE);
9142 DrawPosition(TRUE, boards[0]);
9143 CopyBoard(initialPosition, boards[0]);
9144 startedFromSetupPosition = TRUE;
9147 if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9148 ChessSquare piece = WhitePawn;
9149 char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0;
9150 if(*p == '+') promoted++, ID = *++p;
9151 if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9152 piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece);
9153 if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9154 /* always accept definition of */ && piece != WhiteFalcon && piece != BlackFalcon
9155 /* wild-card pieces. */ && piece != WhiteCobra && piece != BlackCobra
9156 /* For variants we don't have */ && gameInfo.variant != VariantBerolina
9157 /* correct rules for, we cannot */ && gameInfo.variant != VariantCylinder
9158 /* enforce legality on our own! */ && gameInfo.variant != VariantUnknown
9159 && gameInfo.variant != VariantGreat
9160 && gameInfo.variant != VariantFairy ) return;
9161 if(piece < EmptySquare) {
9163 ASSIGN(pieceDesc[piece], buf1);
9164 if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9168 if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9169 promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9173 /* [HGM] Allow engine to set up a position. Don't ask me why one would
9174 * want this, I was asked to put it in, and obliged.
9176 if (!strncmp(message, "setboard ", 9)) {
9177 Board initial_position;
9179 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9181 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9182 DisplayError(_("Bad FEN received from engine"), 0);
9186 CopyBoard(boards[0], initial_position);
9187 initialRulePlies = FENrulePlies;
9188 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9189 else gameMode = MachinePlaysBlack;
9190 DrawPosition(FALSE, boards[currentMove]);
9196 * Look for communication commands
9198 if (!strncmp(message, "telluser ", 9)) {
9199 if(message[9] == '\\' && message[10] == '\\')
9200 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9202 DisplayNote(message + 9);
9205 if (!strncmp(message, "tellusererror ", 14)) {
9207 if(message[14] == '\\' && message[15] == '\\')
9208 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9210 DisplayError(message + 14, 0);
9213 if (!strncmp(message, "tellopponent ", 13)) {
9214 if (appData.icsActive) {
9216 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9220 DisplayNote(message + 13);
9224 if (!strncmp(message, "tellothers ", 11)) {
9225 if (appData.icsActive) {
9227 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9230 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9233 if (!strncmp(message, "tellall ", 8)) {
9234 if (appData.icsActive) {
9236 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9240 DisplayNote(message + 8);
9244 if (strncmp(message, "warning", 7) == 0) {
9245 /* Undocumented feature, use tellusererror in new code */
9246 DisplayError(message, 0);
9249 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9250 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9251 strcat(realname, " query");
9252 AskQuestion(realname, buf2, buf1, cps->pr);
9255 /* Commands from the engine directly to ICS. We don't allow these to be
9256 * sent until we are logged on. Crafty kibitzes have been known to
9257 * interfere with the login process.
9260 if (!strncmp(message, "tellics ", 8)) {
9261 SendToICS(message + 8);
9265 if (!strncmp(message, "tellicsnoalias ", 15)) {
9266 SendToICS(ics_prefix);
9267 SendToICS(message + 15);
9271 /* The following are for backward compatibility only */
9272 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9273 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9274 SendToICS(ics_prefix);
9280 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9281 if(initPing == cps->lastPong) {
9282 if(gameInfo.variant == VariantUnknown) {
9283 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9284 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9285 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9289 if(cps->lastPing == cps->lastPong && abortEngineThink) {
9290 abortEngineThink = FALSE;
9291 DisplayMessage("", "");
9296 if(!strncmp(message, "highlight ", 10)) {
9297 if(appData.testLegality && !*engineVariant && appData.markers) return;
9298 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9301 if(!strncmp(message, "click ", 6)) {
9302 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9303 if(appData.testLegality || !appData.oneClick) return;
9304 sscanf(message+6, "%c%d%c", &f, &y, &c);
9305 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9306 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9307 x = x*squareSize + (x+1)*lineGap + squareSize/2;
9308 y = y*squareSize + (y+1)*lineGap + squareSize/2;
9309 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9310 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9311 LeftClick(Release, lastLeftX, lastLeftY);
9312 controlKey = (c == ',');
9313 LeftClick(Press, x, y);
9314 LeftClick(Release, x, y);
9315 first.highlight = f;
9319 * If the move is illegal, cancel it and redraw the board.
9320 * Also deal with other error cases. Matching is rather loose
9321 * here to accommodate engines written before the spec.
9323 if (strncmp(message + 1, "llegal move", 11) == 0 ||
9324 strncmp(message, "Error", 5) == 0) {
9325 if (StrStr(message, "name") ||
9326 StrStr(message, "rating") || StrStr(message, "?") ||
9327 StrStr(message, "result") || StrStr(message, "board") ||
9328 StrStr(message, "bk") || StrStr(message, "computer") ||
9329 StrStr(message, "variant") || StrStr(message, "hint") ||
9330 StrStr(message, "random") || StrStr(message, "depth") ||
9331 StrStr(message, "accepted")) {
9334 if (StrStr(message, "protover")) {
9335 /* Program is responding to input, so it's apparently done
9336 initializing, and this error message indicates it is
9337 protocol version 1. So we don't need to wait any longer
9338 for it to initialize and send feature commands. */
9339 FeatureDone(cps, 1);
9340 cps->protocolVersion = 1;
9343 cps->maybeThinking = FALSE;
9345 if (StrStr(message, "draw")) {
9346 /* Program doesn't have "draw" command */
9347 cps->sendDrawOffers = 0;
9350 if (cps->sendTime != 1 &&
9351 (StrStr(message, "time") || StrStr(message, "otim"))) {
9352 /* Program apparently doesn't have "time" or "otim" command */
9356 if (StrStr(message, "analyze")) {
9357 cps->analysisSupport = FALSE;
9358 cps->analyzing = FALSE;
9359 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9360 EditGameEvent(); // [HGM] try to preserve loaded game
9361 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9362 DisplayError(buf2, 0);
9365 if (StrStr(message, "(no matching move)st")) {
9366 /* Special kludge for GNU Chess 4 only */
9367 cps->stKludge = TRUE;
9368 SendTimeControl(cps, movesPerSession, timeControl,
9369 timeIncrement, appData.searchDepth,
9373 if (StrStr(message, "(no matching move)sd")) {
9374 /* Special kludge for GNU Chess 4 only */
9375 cps->sdKludge = TRUE;
9376 SendTimeControl(cps, movesPerSession, timeControl,
9377 timeIncrement, appData.searchDepth,
9381 if (!StrStr(message, "llegal")) {
9384 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9385 gameMode == IcsIdle) return;
9386 if (forwardMostMove <= backwardMostMove) return;
9387 if (pausing) PauseEvent();
9388 if(appData.forceIllegal) {
9389 // [HGM] illegal: machine refused move; force position after move into it
9390 SendToProgram("force\n", cps);
9391 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9392 // we have a real problem now, as SendBoard will use the a2a3 kludge
9393 // when black is to move, while there might be nothing on a2 or black
9394 // might already have the move. So send the board as if white has the move.
9395 // But first we must change the stm of the engine, as it refused the last move
9396 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9397 if(WhiteOnMove(forwardMostMove)) {
9398 SendToProgram("a7a6\n", cps); // for the engine black still had the move
9399 SendBoard(cps, forwardMostMove); // kludgeless board
9401 SendToProgram("a2a3\n", cps); // for the engine white still had the move
9402 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9403 SendBoard(cps, forwardMostMove+1); // kludgeless board
9405 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9406 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9407 gameMode == TwoMachinesPlay)
9408 SendToProgram("go\n", cps);
9411 if (gameMode == PlayFromGameFile) {
9412 /* Stop reading this game file */
9413 gameMode = EditGame;
9416 /* [HGM] illegal-move claim should forfeit game when Xboard */
9417 /* only passes fully legal moves */
9418 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9419 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9420 "False illegal-move claim", GE_XBOARD );
9421 return; // do not take back move we tested as valid
9423 currentMove = forwardMostMove-1;
9424 DisplayMove(currentMove-1); /* before DisplayMoveError */
9425 SwitchClocks(forwardMostMove-1); // [HGM] race
9426 DisplayBothClocks();
9427 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9428 parseList[currentMove], _(cps->which));
9429 DisplayMoveError(buf1);
9430 DrawPosition(FALSE, boards[currentMove]);
9432 SetUserThinkingEnables();
9435 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9436 /* Program has a broken "time" command that
9437 outputs a string not ending in newline.
9441 if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9442 if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9443 sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1 ) return;
9447 * If chess program startup fails, exit with an error message.
9448 * Attempts to recover here are futile. [HGM] Well, we try anyway
9450 if ((StrStr(message, "unknown host") != NULL)
9451 || (StrStr(message, "No remote directory") != NULL)
9452 || (StrStr(message, "not found") != NULL)
9453 || (StrStr(message, "No such file") != NULL)
9454 || (StrStr(message, "can't alloc") != NULL)
9455 || (StrStr(message, "Permission denied") != NULL)) {
9457 cps->maybeThinking = FALSE;
9458 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9459 _(cps->which), cps->program, cps->host, message);
9460 RemoveInputSource(cps->isr);
9461 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9462 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9463 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9469 * Look for hint output
9471 if (sscanf(message, "Hint: %s", buf1) == 1) {
9472 if (cps == &first && hintRequested) {
9473 hintRequested = FALSE;
9474 if (ParseOneMove(buf1, forwardMostMove, &moveType,
9475 &fromX, &fromY, &toX, &toY, &promoChar)) {
9476 (void) CoordsToAlgebraic(boards[forwardMostMove],
9477 PosFlags(forwardMostMove),
9478 fromY, fromX, toY, toX, promoChar, buf1);
9479 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9480 DisplayInformation(buf2);
9482 /* Hint move could not be parsed!? */
9483 snprintf(buf2, sizeof(buf2),
9484 _("Illegal hint move \"%s\"\nfrom %s chess program"),
9485 buf1, _(cps->which));
9486 DisplayError(buf2, 0);
9489 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9495 * Ignore other messages if game is not in progress
9497 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9498 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9501 * look for win, lose, draw, or draw offer
9503 if (strncmp(message, "1-0", 3) == 0) {
9504 char *p, *q, *r = "";
9505 p = strchr(message, '{');
9513 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9515 } else if (strncmp(message, "0-1", 3) == 0) {
9516 char *p, *q, *r = "";
9517 p = strchr(message, '{');
9525 /* Kludge for Arasan 4.1 bug */
9526 if (strcmp(r, "Black resigns") == 0) {
9527 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9530 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9532 } else if (strncmp(message, "1/2", 3) == 0) {
9533 char *p, *q, *r = "";
9534 p = strchr(message, '{');
9543 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9546 } else if (strncmp(message, "White resign", 12) == 0) {
9547 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9549 } else if (strncmp(message, "Black resign", 12) == 0) {
9550 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9552 } else if (strncmp(message, "White matches", 13) == 0 ||
9553 strncmp(message, "Black matches", 13) == 0 ) {
9554 /* [HGM] ignore GNUShogi noises */
9556 } else if (strncmp(message, "White", 5) == 0 &&
9557 message[5] != '(' &&
9558 StrStr(message, "Black") == NULL) {
9559 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9561 } else if (strncmp(message, "Black", 5) == 0 &&
9562 message[5] != '(') {
9563 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9565 } else if (strcmp(message, "resign") == 0 ||
9566 strcmp(message, "computer resigns") == 0) {
9568 case MachinePlaysBlack:
9569 case IcsPlayingBlack:
9570 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9572 case MachinePlaysWhite:
9573 case IcsPlayingWhite:
9574 GameEnds(BlackWins, "White resigns", GE_ENGINE);
9576 case TwoMachinesPlay:
9577 if (cps->twoMachinesColor[0] == 'w')
9578 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9580 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9587 } else if (strncmp(message, "opponent mates", 14) == 0) {
9589 case MachinePlaysBlack:
9590 case IcsPlayingBlack:
9591 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9593 case MachinePlaysWhite:
9594 case IcsPlayingWhite:
9595 GameEnds(BlackWins, "Black mates", GE_ENGINE);
9597 case TwoMachinesPlay:
9598 if (cps->twoMachinesColor[0] == 'w')
9599 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9601 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9608 } else if (strncmp(message, "computer mates", 14) == 0) {
9610 case MachinePlaysBlack:
9611 case IcsPlayingBlack:
9612 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9614 case MachinePlaysWhite:
9615 case IcsPlayingWhite:
9616 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9618 case TwoMachinesPlay:
9619 if (cps->twoMachinesColor[0] == 'w')
9620 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9622 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9629 } else if (strncmp(message, "checkmate", 9) == 0) {
9630 if (WhiteOnMove(forwardMostMove)) {
9631 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9633 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9636 } else if (strstr(message, "Draw") != NULL ||
9637 strstr(message, "game is a draw") != NULL) {
9638 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9640 } else if (strstr(message, "offer") != NULL &&
9641 strstr(message, "draw") != NULL) {
9643 if (appData.zippyPlay && first.initDone) {
9644 /* Relay offer to ICS */
9645 SendToICS(ics_prefix);
9646 SendToICS("draw\n");
9649 cps->offeredDraw = 2; /* valid until this engine moves twice */
9650 if (gameMode == TwoMachinesPlay) {
9651 if (cps->other->offeredDraw) {
9652 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9653 /* [HGM] in two-machine mode we delay relaying draw offer */
9654 /* until after we also have move, to see if it is really claim */
9656 } else if (gameMode == MachinePlaysWhite ||
9657 gameMode == MachinePlaysBlack) {
9658 if (userOfferedDraw) {
9659 DisplayInformation(_("Machine accepts your draw offer"));
9660 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9662 DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9669 * Look for thinking output
9671 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9672 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9674 int plylev, mvleft, mvtot, curscore, time;
9675 char mvname[MOVE_LEN];
9679 int prefixHint = FALSE;
9680 mvname[0] = NULLCHAR;
9683 case MachinePlaysBlack:
9684 case IcsPlayingBlack:
9685 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9687 case MachinePlaysWhite:
9688 case IcsPlayingWhite:
9689 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9694 case IcsObserving: /* [DM] icsEngineAnalyze */
9695 if (!appData.icsEngineAnalyze) ignore = TRUE;
9697 case TwoMachinesPlay:
9698 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9708 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9711 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9712 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9713 char score_buf[MSG_SIZ];
9715 if(nodes>>32 == u64Const(0xFFFFFFFF)) // [HGM] negative node count read
9716 nodes += u64Const(0x100000000);
9718 if (plyext != ' ' && plyext != '\t') {
9722 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9723 if( cps->scoreIsAbsolute &&
9724 ( gameMode == MachinePlaysBlack ||
9725 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9726 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9727 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9728 !WhiteOnMove(currentMove)
9731 curscore = -curscore;
9734 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9736 if(*bestMove) { // rememer time best EPD move was first found
9737 int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9738 ChessMove mt; char *p = bestMove;
9739 int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9741 while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9742 if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9743 solvingTime = (solvingTime < 0 ? time : solvingTime);
9747 while(*p && *p != ' ') p++;
9748 while(*p == ' ') p++;
9750 if(!solved) solvingTime = -1;
9752 if(*avoidMove && !solved) {
9753 int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9754 ChessMove mt; char *p = avoidMove, solved = 1;
9755 int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9756 while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9757 if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9758 solved = 0; solvingTime = -2;
9761 while(*p && *p != ' ') p++;
9762 while(*p == ' ') p++;
9764 if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9767 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9770 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9771 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9772 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9773 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9774 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9775 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9779 /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9780 DisplayError(_("failed writing PV"), 0);
9783 tempStats.depth = plylev;
9784 tempStats.nodes = nodes;
9785 tempStats.time = time;
9786 tempStats.score = curscore;
9787 tempStats.got_only_move = 0;
9789 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9792 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9793 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9794 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9795 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9796 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9797 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9798 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9799 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9802 /* Buffer overflow protection */
9803 if (pv[0] != NULLCHAR) {
9804 if (strlen(pv) >= sizeof(tempStats.movelist)
9805 && appData.debugMode) {
9807 "PV is too long; using the first %u bytes.\n",
9808 (unsigned) sizeof(tempStats.movelist) - 1);
9811 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9813 sprintf(tempStats.movelist, " no PV\n");
9816 if (tempStats.seen_stat) {
9817 tempStats.ok_to_send = 1;
9820 if (strchr(tempStats.movelist, '(') != NULL) {
9821 tempStats.line_is_book = 1;
9822 tempStats.nr_moves = 0;
9823 tempStats.moves_left = 0;
9825 tempStats.line_is_book = 0;
9828 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9829 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9831 SendProgramStatsToFrontend( cps, &tempStats );
9834 [AS] Protect the thinkOutput buffer from overflow... this
9835 is only useful if buf1 hasn't overflowed first!
9837 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9838 if(curscore >= MATE_SCORE)
9839 snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9840 else if(curscore <= -MATE_SCORE)
9841 snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9843 snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9844 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9846 (gameMode == TwoMachinesPlay ?
9847 ToUpper(cps->twoMachinesColor[0]) : ' '),
9849 prefixHint ? lastHint : "",
9850 prefixHint ? " " : "" );
9852 if( buf1[0] != NULLCHAR ) {
9853 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9855 if( strlen(pv) > max_len ) {
9856 if( appData.debugMode) {
9857 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9859 pv[max_len+1] = '\0';
9862 strcat( thinkOutput, pv);
9865 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9866 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9867 DisplayMove(currentMove - 1);
9871 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9872 /* crafty (9.25+) says "(only move) <move>"
9873 * if there is only 1 legal move
9875 sscanf(p, "(only move) %s", buf1);
9876 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9877 sprintf(programStats.movelist, "%s (only move)", buf1);
9878 programStats.depth = 1;
9879 programStats.nr_moves = 1;
9880 programStats.moves_left = 1;
9881 programStats.nodes = 1;
9882 programStats.time = 1;
9883 programStats.got_only_move = 1;
9885 /* Not really, but we also use this member to
9886 mean "line isn't going to change" (Crafty
9887 isn't searching, so stats won't change) */
9888 programStats.line_is_book = 1;
9890 SendProgramStatsToFrontend( cps, &programStats );
9892 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9893 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9894 DisplayMove(currentMove - 1);
9897 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9898 &time, &nodes, &plylev, &mvleft,
9899 &mvtot, mvname) >= 5) {
9900 /* The stat01: line is from Crafty (9.29+) in response
9901 to the "." command */
9902 programStats.seen_stat = 1;
9903 cps->maybeThinking = TRUE;
9905 if (programStats.got_only_move || !appData.periodicUpdates)
9908 programStats.depth = plylev;
9909 programStats.time = time;
9910 programStats.nodes = nodes;
9911 programStats.moves_left = mvleft;
9912 programStats.nr_moves = mvtot;
9913 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9914 programStats.ok_to_send = 1;
9915 programStats.movelist[0] = '\0';
9917 SendProgramStatsToFrontend( cps, &programStats );
9921 } else if (strncmp(message,"++",2) == 0) {
9922 /* Crafty 9.29+ outputs this */
9923 programStats.got_fail = 2;
9926 } else if (strncmp(message,"--",2) == 0) {
9927 /* Crafty 9.29+ outputs this */
9928 programStats.got_fail = 1;
9931 } else if (thinkOutput[0] != NULLCHAR &&
9932 strncmp(message, " ", 4) == 0) {
9933 unsigned message_len;
9936 while (*p && *p == ' ') p++;
9938 message_len = strlen( p );
9940 /* [AS] Avoid buffer overflow */
9941 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9942 strcat(thinkOutput, " ");
9943 strcat(thinkOutput, p);
9946 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9947 strcat(programStats.movelist, " ");
9948 strcat(programStats.movelist, p);
9951 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9952 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9953 DisplayMove(currentMove - 1);
9961 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9962 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9964 ChessProgramStats cpstats;
9966 if (plyext != ' ' && plyext != '\t') {
9970 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9971 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9972 curscore = -curscore;
9975 cpstats.depth = plylev;
9976 cpstats.nodes = nodes;
9977 cpstats.time = time;
9978 cpstats.score = curscore;
9979 cpstats.got_only_move = 0;
9980 cpstats.movelist[0] = '\0';
9982 if (buf1[0] != NULLCHAR) {
9983 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9986 cpstats.ok_to_send = 0;
9987 cpstats.line_is_book = 0;
9988 cpstats.nr_moves = 0;
9989 cpstats.moves_left = 0;
9991 SendProgramStatsToFrontend( cps, &cpstats );
9998 /* Parse a game score from the character string "game", and
9999 record it as the history of the current game. The game
10000 score is NOT assumed to start from the standard position.
10001 The display is not updated in any way.
10004 ParseGameHistory (char *game)
10006 ChessMove moveType;
10007 int fromX, fromY, toX, toY, boardIndex;
10012 if (appData.debugMode)
10013 fprintf(debugFP, "Parsing game history: %s\n", game);
10015 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10016 gameInfo.site = StrSave(appData.icsHost);
10017 gameInfo.date = PGNDate();
10018 gameInfo.round = StrSave("-");
10020 /* Parse out names of players */
10021 while (*game == ' ') game++;
10023 while (*game != ' ') *p++ = *game++;
10025 gameInfo.white = StrSave(buf);
10026 while (*game == ' ') game++;
10028 while (*game != ' ' && *game != '\n') *p++ = *game++;
10030 gameInfo.black = StrSave(buf);
10033 boardIndex = blackPlaysFirst ? 1 : 0;
10036 yyboardindex = boardIndex;
10037 moveType = (ChessMove) Myylex();
10038 switch (moveType) {
10039 case IllegalMove: /* maybe suicide chess, etc. */
10040 if (appData.debugMode) {
10041 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10042 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10043 setbuf(debugFP, NULL);
10045 case WhitePromotion:
10046 case BlackPromotion:
10047 case WhiteNonPromotion:
10048 case BlackNonPromotion:
10051 case WhiteCapturesEnPassant:
10052 case BlackCapturesEnPassant:
10053 case WhiteKingSideCastle:
10054 case WhiteQueenSideCastle:
10055 case BlackKingSideCastle:
10056 case BlackQueenSideCastle:
10057 case WhiteKingSideCastleWild:
10058 case WhiteQueenSideCastleWild:
10059 case BlackKingSideCastleWild:
10060 case BlackQueenSideCastleWild:
10062 case WhiteHSideCastleFR:
10063 case WhiteASideCastleFR:
10064 case BlackHSideCastleFR:
10065 case BlackASideCastleFR:
10067 fromX = currentMoveString[0] - AAA;
10068 fromY = currentMoveString[1] - ONE;
10069 toX = currentMoveString[2] - AAA;
10070 toY = currentMoveString[3] - ONE;
10071 promoChar = currentMoveString[4];
10075 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10076 fromX = moveType == WhiteDrop ?
10077 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10078 (int) CharToPiece(ToLower(currentMoveString[0]));
10080 toX = currentMoveString[2] - AAA;
10081 toY = currentMoveString[3] - ONE;
10082 promoChar = NULLCHAR;
10084 case AmbiguousMove:
10086 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10087 if (appData.debugMode) {
10088 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10089 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10090 setbuf(debugFP, NULL);
10092 DisplayError(buf, 0);
10094 case ImpossibleMove:
10096 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10097 if (appData.debugMode) {
10098 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10099 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10100 setbuf(debugFP, NULL);
10102 DisplayError(buf, 0);
10105 if (boardIndex < backwardMostMove) {
10106 /* Oops, gap. How did that happen? */
10107 DisplayError(_("Gap in move list"), 0);
10110 backwardMostMove = blackPlaysFirst ? 1 : 0;
10111 if (boardIndex > forwardMostMove) {
10112 forwardMostMove = boardIndex;
10116 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10117 strcat(parseList[boardIndex-1], " ");
10118 strcat(parseList[boardIndex-1], yy_text);
10130 case GameUnfinished:
10131 if (gameMode == IcsExamining) {
10132 if (boardIndex < backwardMostMove) {
10133 /* Oops, gap. How did that happen? */
10136 backwardMostMove = blackPlaysFirst ? 1 : 0;
10139 gameInfo.result = moveType;
10140 p = strchr(yy_text, '{');
10141 if (p == NULL) p = strchr(yy_text, '(');
10144 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10146 q = strchr(p, *p == '{' ? '}' : ')');
10147 if (q != NULL) *q = NULLCHAR;
10150 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10151 gameInfo.resultDetails = StrSave(p);
10154 if (boardIndex >= forwardMostMove &&
10155 !(gameMode == IcsObserving && ics_gamenum == -1)) {
10156 backwardMostMove = blackPlaysFirst ? 1 : 0;
10159 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10160 fromY, fromX, toY, toX, promoChar,
10161 parseList[boardIndex]);
10162 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10163 /* currentMoveString is set as a side-effect of yylex */
10164 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10165 strcat(moveList[boardIndex], "\n");
10167 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10168 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10174 if(!IS_SHOGI(gameInfo.variant))
10175 strcat(parseList[boardIndex - 1], "+");
10179 strcat(parseList[boardIndex - 1], "#");
10186 /* Apply a move to the given board */
10188 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10190 ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10191 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10193 /* [HGM] compute & store e.p. status and castling rights for new position */
10194 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10196 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10197 oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10198 board[EP_STATUS] = EP_NONE;
10199 board[EP_FILE] = board[EP_RANK] = 100;
10201 if (fromY == DROP_RANK) {
10202 /* must be first */
10203 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10204 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10207 piece = board[toY][toX] = (ChessSquare) fromX;
10209 // ChessSquare victim;
10212 if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10213 // victim = board[killY][killX],
10214 killed = board[killY][killX],
10215 board[killY][killX] = EmptySquare,
10216 board[EP_STATUS] = EP_CAPTURE;
10217 if( kill2X >= 0 && kill2Y >= 0)
10218 killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10221 if( board[toY][toX] != EmptySquare ) {
10222 board[EP_STATUS] = EP_CAPTURE;
10223 if( (fromX != toX || fromY != toY) && // not igui!
10224 (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10225 captured == BlackLion && board[fromY][fromX] != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules
10226 board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10230 pawn = board[fromY][fromX];
10231 if( pawn == WhiteLance || pawn == BlackLance ) {
10232 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10233 if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10234 else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10237 if( pawn == WhitePawn ) {
10238 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10239 board[EP_STATUS] = EP_PAWN_MOVE;
10240 if( toY-fromY>=2) {
10241 board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10242 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
10243 gameInfo.variant != VariantBerolina || toX < fromX)
10244 board[EP_STATUS] = toX | berolina;
10245 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10246 gameInfo.variant != VariantBerolina || toX > fromX)
10247 board[EP_STATUS] = toX;
10250 if( pawn == BlackPawn ) {
10251 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10252 board[EP_STATUS] = EP_PAWN_MOVE;
10253 if( toY-fromY<= -2) {
10254 board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10255 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
10256 gameInfo.variant != VariantBerolina || toX < fromX)
10257 board[EP_STATUS] = toX | berolina;
10258 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10259 gameInfo.variant != VariantBerolina || toX > fromX)
10260 board[EP_STATUS] = toX;
10264 if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10265 if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10266 if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10267 if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10269 for(i=0; i<nrCastlingRights; i++) {
10270 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10271 board[CASTLING][i] == toX && castlingRank[i] == toY
10272 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10275 if(gameInfo.variant == VariantSChess) { // update virginity
10276 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10277 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10278 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
10279 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
10282 if (fromX == toX && fromY == toY && killX < 0) return;
10284 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10285 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10286 if(gameInfo.variant == VariantKnightmate)
10287 king += (int) WhiteUnicorn - (int) WhiteKing;
10289 if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10290 && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) { // and tramples own
10291 board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10292 board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10293 board[EP_STATUS] = EP_NONE; // capture was fake!
10295 if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10296 board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10297 board[toY][toX] = piece;
10298 board[EP_STATUS] = EP_NONE; // capture was fake!
10300 /* Code added by Tord: */
10301 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10302 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10303 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10304 board[EP_STATUS] = EP_NONE; // capture was fake!
10305 board[fromY][fromX] = EmptySquare;
10306 board[toY][toX] = EmptySquare;
10307 if((toX > fromX) != (piece == WhiteRook)) {
10308 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10310 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10312 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10313 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10314 board[EP_STATUS] = EP_NONE;
10315 board[fromY][fromX] = EmptySquare;
10316 board[toY][toX] = EmptySquare;
10317 if((toX > fromX) != (piece == BlackRook)) {
10318 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10320 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10322 /* End of code added by Tord */
10324 } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10325 board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10326 board[toY][toX] = piece;
10327 } else if (board[fromY][fromX] == king
10328 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10329 && toY == fromY && toX > fromX+1) {
10330 for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10331 ; // castle with nearest piece
10332 board[fromY][toX-1] = board[fromY][rookX];
10333 board[fromY][rookX] = EmptySquare;
10334 board[fromY][fromX] = EmptySquare;
10335 board[toY][toX] = king;
10336 } else if (board[fromY][fromX] == king
10337 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10338 && toY == fromY && toX < fromX-1) {
10339 for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10340 ; // castle with nearest piece
10341 board[fromY][toX+1] = board[fromY][rookX];
10342 board[fromY][rookX] = EmptySquare;
10343 board[fromY][fromX] = EmptySquare;
10344 board[toY][toX] = king;
10345 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10346 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10347 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10349 /* white pawn promotion */
10350 board[toY][toX] = CharToPiece(ToUpper(promoChar));
10351 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10352 board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10353 board[fromY][fromX] = EmptySquare;
10354 } else if ((fromY >= BOARD_HEIGHT>>1)
10355 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10357 && gameInfo.variant != VariantXiangqi
10358 && gameInfo.variant != VariantBerolina
10359 && (pawn == WhitePawn)
10360 && (board[toY][toX] == EmptySquare)) {
10361 board[fromY][fromX] = EmptySquare;
10362 board[toY][toX] = piece;
10363 if(toY == epRank - 128 + 1)
10364 captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10366 captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10367 } else if ((fromY == BOARD_HEIGHT-4)
10369 && gameInfo.variant == VariantBerolina
10370 && (board[fromY][fromX] == WhitePawn)
10371 && (board[toY][toX] == EmptySquare)) {
10372 board[fromY][fromX] = EmptySquare;
10373 board[toY][toX] = WhitePawn;
10374 if(oldEP & EP_BEROLIN_A) {
10375 captured = board[fromY][fromX-1];
10376 board[fromY][fromX-1] = EmptySquare;
10377 }else{ captured = board[fromY][fromX+1];
10378 board[fromY][fromX+1] = EmptySquare;
10380 } else if (board[fromY][fromX] == king
10381 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10382 && toY == fromY && toX > fromX+1) {
10383 for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10385 board[fromY][toX-1] = board[fromY][rookX];
10386 board[fromY][rookX] = EmptySquare;
10387 board[fromY][fromX] = EmptySquare;
10388 board[toY][toX] = king;
10389 } else if (board[fromY][fromX] == king
10390 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10391 && toY == fromY && toX < fromX-1) {
10392 for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10394 board[fromY][toX+1] = board[fromY][rookX];
10395 board[fromY][rookX] = EmptySquare;
10396 board[fromY][fromX] = EmptySquare;
10397 board[toY][toX] = king;
10398 } else if (fromY == 7 && fromX == 3
10399 && board[fromY][fromX] == BlackKing
10400 && toY == 7 && toX == 5) {
10401 board[fromY][fromX] = EmptySquare;
10402 board[toY][toX] = BlackKing;
10403 board[fromY][7] = EmptySquare;
10404 board[toY][4] = BlackRook;
10405 } else if (fromY == 7 && fromX == 3
10406 && board[fromY][fromX] == BlackKing
10407 && toY == 7 && toX == 1) {
10408 board[fromY][fromX] = EmptySquare;
10409 board[toY][toX] = BlackKing;
10410 board[fromY][0] = EmptySquare;
10411 board[toY][2] = BlackRook;
10412 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10413 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10414 && toY < promoRank && promoChar
10416 /* black pawn promotion */
10417 board[toY][toX] = CharToPiece(ToLower(promoChar));
10418 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10419 board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10420 board[fromY][fromX] = EmptySquare;
10421 } else if ((fromY < BOARD_HEIGHT>>1)
10422 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10424 && gameInfo.variant != VariantXiangqi
10425 && gameInfo.variant != VariantBerolina
10426 && (pawn == BlackPawn)
10427 && (board[toY][toX] == EmptySquare)) {
10428 board[fromY][fromX] = EmptySquare;
10429 board[toY][toX] = piece;
10430 if(toY == epRank - 128 - 1)
10431 captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10433 captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10434 } else if ((fromY == 3)
10436 && gameInfo.variant == VariantBerolina
10437 && (board[fromY][fromX] == BlackPawn)
10438 && (board[toY][toX] == EmptySquare)) {
10439 board[fromY][fromX] = EmptySquare;
10440 board[toY][toX] = BlackPawn;
10441 if(oldEP & EP_BEROLIN_A) {
10442 captured = board[fromY][fromX-1];
10443 board[fromY][fromX-1] = EmptySquare;
10444 }else{ captured = board[fromY][fromX+1];
10445 board[fromY][fromX+1] = EmptySquare;
10448 ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10449 board[fromY][fromX] = EmptySquare;
10450 board[toY][toX] = piece;
10454 if (gameInfo.holdingsWidth != 0) {
10456 /* !!A lot more code needs to be written to support holdings */
10457 /* [HGM] OK, so I have written it. Holdings are stored in the */
10458 /* penultimate board files, so they are automaticlly stored */
10459 /* in the game history. */
10460 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10461 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10462 /* Delete from holdings, by decreasing count */
10463 /* and erasing image if necessary */
10464 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10465 if(p < (int) BlackPawn) { /* white drop */
10466 p -= (int)WhitePawn;
10467 p = PieceToNumber((ChessSquare)p);
10468 if(p >= gameInfo.holdingsSize) p = 0;
10469 if(--board[p][BOARD_WIDTH-2] <= 0)
10470 board[p][BOARD_WIDTH-1] = EmptySquare;
10471 if((int)board[p][BOARD_WIDTH-2] < 0)
10472 board[p][BOARD_WIDTH-2] = 0;
10473 } else { /* black drop */
10474 p -= (int)BlackPawn;
10475 p = PieceToNumber((ChessSquare)p);
10476 if(p >= gameInfo.holdingsSize) p = 0;
10477 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10478 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10479 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10480 board[BOARD_HEIGHT-1-p][1] = 0;
10483 if (captured != EmptySquare && gameInfo.holdingsSize > 0
10484 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
10485 /* [HGM] holdings: Add to holdings, if holdings exist */
10486 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10487 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10488 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10490 p = (int) captured;
10491 if (p >= (int) BlackPawn) {
10492 p -= (int)BlackPawn;
10493 if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10494 /* Restore shogi-promoted piece to its original first */
10495 captured = (ChessSquare) (DEMOTED(captured));
10498 p = PieceToNumber((ChessSquare)p);
10499 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10500 board[p][BOARD_WIDTH-2]++;
10501 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10503 p -= (int)WhitePawn;
10504 if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10505 captured = (ChessSquare) (DEMOTED(captured));
10508 p = PieceToNumber((ChessSquare)p);
10509 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10510 board[BOARD_HEIGHT-1-p][1]++;
10511 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10514 } else if (gameInfo.variant == VariantAtomic) {
10515 if (captured != EmptySquare) {
10517 for (y = toY-1; y <= toY+1; y++) {
10518 for (x = toX-1; x <= toX+1; x++) {
10519 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10520 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10521 board[y][x] = EmptySquare;
10525 board[toY][toX] = EmptySquare;
10529 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10530 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10532 if(promoChar == '+') {
10533 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10534 board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10535 if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10536 board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10537 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10538 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10539 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10540 && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10541 board[toY][toX] = newPiece;
10543 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10544 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10545 // [HGM] superchess: take promotion piece out of holdings
10546 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10547 if((int)piece < (int)BlackPawn) { // determine stm from piece color
10548 if(!--board[k][BOARD_WIDTH-2])
10549 board[k][BOARD_WIDTH-1] = EmptySquare;
10551 if(!--board[BOARD_HEIGHT-1-k][1])
10552 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10557 /* Updates forwardMostMove */
10559 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10561 int x = toX, y = toY;
10562 char *s = parseList[forwardMostMove];
10563 ChessSquare p = boards[forwardMostMove][toY][toX];
10564 // forwardMostMove++; // [HGM] bare: moved downstream
10566 if(kill2X >= 0) x = kill2X, y = kill2Y; else
10567 if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10568 (void) CoordsToAlgebraic(boards[forwardMostMove],
10569 PosFlags(forwardMostMove),
10570 fromY, fromX, y, x, (killX < 0)*promoChar,
10572 if(kill2X >= 0 && kill2Y >= 0)
10573 sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10574 if(killX >= 0 && killY >= 0)
10575 sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10576 toX + AAA, toY + ONE - '0', promoChar);
10578 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10579 int timeLeft; static int lastLoadFlag=0; int king, piece;
10580 piece = boards[forwardMostMove][fromY][fromX];
10581 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10582 if(gameInfo.variant == VariantKnightmate)
10583 king += (int) WhiteUnicorn - (int) WhiteKing;
10584 if(forwardMostMove == 0) {
10585 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10586 fprintf(serverMoves, "%s;", UserName());
10587 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10588 fprintf(serverMoves, "%s;", second.tidy);
10589 fprintf(serverMoves, "%s;", first.tidy);
10590 if(gameMode == MachinePlaysWhite)
10591 fprintf(serverMoves, "%s;", UserName());
10592 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10593 fprintf(serverMoves, "%s;", second.tidy);
10594 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10595 lastLoadFlag = loadFlag;
10597 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10598 // print castling suffix
10599 if( toY == fromY && piece == king ) {
10601 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10603 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10606 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10607 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
10608 boards[forwardMostMove][toY][toX] == EmptySquare
10609 && fromX != toX && fromY != toY)
10610 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10611 // promotion suffix
10612 if(promoChar != NULLCHAR) {
10613 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10614 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10615 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10616 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10619 char buf[MOVE_LEN*2], *p; int len;
10620 fprintf(serverMoves, "/%d/%d",
10621 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10622 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10623 else timeLeft = blackTimeRemaining/1000;
10624 fprintf(serverMoves, "/%d", timeLeft);
10625 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10626 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10627 if(p = strchr(buf, '=')) *p = NULLCHAR;
10628 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10629 fprintf(serverMoves, "/%s", buf);
10631 fflush(serverMoves);
10634 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10635 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10638 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10639 if (commentList[forwardMostMove+1] != NULL) {
10640 free(commentList[forwardMostMove+1]);
10641 commentList[forwardMostMove+1] = NULL;
10643 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10644 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10645 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10646 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10647 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10648 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10649 adjustedClock = FALSE;
10650 gameInfo.result = GameUnfinished;
10651 if (gameInfo.resultDetails != NULL) {
10652 free(gameInfo.resultDetails);
10653 gameInfo.resultDetails = NULL;
10655 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10656 moveList[forwardMostMove - 1]);
10657 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10663 if(!IS_SHOGI(gameInfo.variant))
10664 strcat(parseList[forwardMostMove - 1], "+");
10668 strcat(parseList[forwardMostMove - 1], "#");
10673 /* Updates currentMove if not pausing */
10675 ShowMove (int fromX, int fromY, int toX, int toY)
10677 int instant = (gameMode == PlayFromGameFile) ?
10678 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10679 if(appData.noGUI) return;
10680 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10682 if (forwardMostMove == currentMove + 1) {
10683 AnimateMove(boards[forwardMostMove - 1],
10684 fromX, fromY, toX, toY);
10687 currentMove = forwardMostMove;
10690 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10692 if (instant) return;
10694 DisplayMove(currentMove - 1);
10695 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10696 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10697 SetHighlights(fromX, fromY, toX, toY);
10700 DrawPosition(FALSE, boards[currentMove]);
10701 DisplayBothClocks();
10702 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10706 SendEgtPath (ChessProgramState *cps)
10707 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10708 char buf[MSG_SIZ], name[MSG_SIZ], *p;
10710 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10713 char c, *q = name+1, *r, *s;
10715 name[0] = ','; // extract next format name from feature and copy with prefixed ','
10716 while(*p && *p != ',') *q++ = *p++;
10717 *q++ = ':'; *q = 0;
10718 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10719 strcmp(name, ",nalimov:") == 0 ) {
10720 // take nalimov path from the menu-changeable option first, if it is defined
10721 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10722 SendToProgram(buf,cps); // send egtbpath command for nalimov
10724 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10725 (s = StrStr(appData.egtFormats, name)) != NULL) {
10726 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10727 s = r = StrStr(s, ":") + 1; // beginning of path info
10728 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10729 c = *r; *r = 0; // temporarily null-terminate path info
10730 *--q = 0; // strip of trailig ':' from name
10731 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10733 SendToProgram(buf,cps); // send egtbpath command for this format
10735 if(*p == ',') p++; // read away comma to position for next format name
10740 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10742 int width = 8, height = 8, holdings = 0; // most common sizes
10743 if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10744 // correct the deviations default for each variant
10745 if( v == VariantXiangqi ) width = 9, height = 10;
10746 if( v == VariantShogi ) width = 9, height = 9, holdings = 7;
10747 if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10748 if( v == VariantCapablanca || v == VariantCapaRandom ||
10749 v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10751 if( v == VariantCourier ) width = 12;
10752 if( v == VariantSuper ) holdings = 8;
10753 if( v == VariantGreat ) width = 10, holdings = 8;
10754 if( v == VariantSChess ) holdings = 7;
10755 if( v == VariantGrand ) width = 10, height = 10, holdings = 7;
10756 if( v == VariantChuChess) width = 10, height = 10;
10757 if( v == VariantChu ) width = 12, height = 12;
10758 return boardWidth >= 0 && boardWidth != width || // -1 is default,
10759 boardHeight >= 0 && boardHeight != height || // and thus by definition OK
10760 holdingsSize >= 0 && holdingsSize != holdings;
10763 char variantError[MSG_SIZ];
10766 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10767 { // returns error message (recognizable by upper-case) if engine does not support the variant
10768 char *p, *variant = VariantName(v);
10769 static char b[MSG_SIZ];
10770 if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10771 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10772 holdingsSize, variant); // cook up sized variant name
10773 /* [HGM] varsize: try first if this deviant size variant is specifically known */
10774 if(StrStr(list, b) == NULL) {
10775 // specific sized variant not known, check if general sizing allowed
10776 if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10777 snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10778 boardWidth, boardHeight, holdingsSize, engine);
10781 /* [HGM] here we really should compare with the maximum supported board size */
10783 } else snprintf(b, MSG_SIZ,"%s", variant);
10784 if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10785 p = StrStr(list, b);
10786 while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10788 // occurs not at all in list, or only as sub-string
10789 snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10790 if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10791 int l = strlen(variantError);
10793 while(p != list && p[-1] != ',') p--;
10794 q = strchr(p, ',');
10795 if(q) *q = NULLCHAR;
10796 snprintf(variantError + l, MSG_SIZ - l, _(", but %s is"), p);
10805 InitChessProgram (ChessProgramState *cps, int setup)
10806 /* setup needed to setup FRC opening position */
10808 char buf[MSG_SIZ], *b;
10809 if (appData.noChessProgram) return;
10810 hintRequested = FALSE;
10811 bookRequested = FALSE;
10813 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10814 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10815 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10816 if(cps->memSize) { /* [HGM] memory */
10817 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10818 SendToProgram(buf, cps);
10820 SendEgtPath(cps); /* [HGM] EGT */
10821 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10822 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10823 SendToProgram(buf, cps);
10826 setboardSpoiledMachineBlack = FALSE;
10827 SendToProgram(cps->initString, cps);
10828 if (gameInfo.variant != VariantNormal &&
10829 gameInfo.variant != VariantLoadable
10830 /* [HGM] also send variant if board size non-standard */
10831 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10833 b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10834 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10838 char c, *q = cps->variants, *p = strchr(q, ',');
10839 if(p) *p = NULLCHAR;
10840 v = StringToVariant(q);
10841 DisplayError(variantError, 0);
10842 if(v != VariantUnknown && cps == &first) {
10844 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10845 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10846 ASSIGN(appData.variant, q);
10847 Reset(TRUE, FALSE);
10853 snprintf(buf, MSG_SIZ, "variant %s\n", b);
10854 SendToProgram(buf, cps);
10856 currentlyInitializedVariant = gameInfo.variant;
10858 /* [HGM] send opening position in FRC to first engine */
10860 SendToProgram("force\n", cps);
10862 /* engine is now in force mode! Set flag to wake it up after first move. */
10863 setboardSpoiledMachineBlack = 1;
10866 if (cps->sendICS) {
10867 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10868 SendToProgram(buf, cps);
10870 cps->maybeThinking = FALSE;
10871 cps->offeredDraw = 0;
10872 if (!appData.icsActive) {
10873 SendTimeControl(cps, movesPerSession, timeControl,
10874 timeIncrement, appData.searchDepth,
10877 if (appData.showThinking
10878 // [HGM] thinking: four options require thinking output to be sent
10879 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10881 SendToProgram("post\n", cps);
10883 SendToProgram("hard\n", cps);
10884 if (!appData.ponderNextMove) {
10885 /* Warning: "easy" is a toggle in GNU Chess, so don't send
10886 it without being sure what state we are in first. "hard"
10887 is not a toggle, so that one is OK.
10889 SendToProgram("easy\n", cps);
10891 if (cps->usePing) {
10892 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10893 SendToProgram(buf, cps);
10895 cps->initDone = TRUE;
10896 ClearEngineOutputPane(cps == &second);
10901 ResendOptions (ChessProgramState *cps)
10902 { // send the stored value of the options
10905 Option *opt = cps->option;
10906 for(i=0; i<cps->nrOptions; i++, opt++) {
10907 switch(opt->type) {
10911 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10914 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10917 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10923 SendToProgram(buf, cps);
10928 StartChessProgram (ChessProgramState *cps)
10933 if (appData.noChessProgram) return;
10934 cps->initDone = FALSE;
10936 if (strcmp(cps->host, "localhost") == 0) {
10937 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10938 } else if (*appData.remoteShell == NULLCHAR) {
10939 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10941 if (*appData.remoteUser == NULLCHAR) {
10942 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10945 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10946 cps->host, appData.remoteUser, cps->program);
10948 err = StartChildProcess(buf, "", &cps->pr);
10952 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10953 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10954 if(cps != &first) return;
10955 appData.noChessProgram = TRUE;
10958 // DisplayFatalError(buf, err, 1);
10959 // cps->pr = NoProc;
10960 // cps->isr = NULL;
10964 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10965 if (cps->protocolVersion > 1) {
10966 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10967 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10968 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10969 cps->comboCnt = 0; // and values of combo boxes
10971 SendToProgram(buf, cps);
10972 if(cps->reload) ResendOptions(cps);
10974 SendToProgram("xboard\n", cps);
10979 TwoMachinesEventIfReady P((void))
10981 static int curMess = 0;
10982 if (first.lastPing != first.lastPong) {
10983 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10984 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10987 if (second.lastPing != second.lastPong) {
10988 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10989 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10992 DisplayMessage("", ""); curMess = 0;
10993 TwoMachinesEvent();
10997 MakeName (char *template)
11001 static char buf[MSG_SIZ];
11005 clock = time((time_t *)NULL);
11006 tm = localtime(&clock);
11008 while(*p++ = *template++) if(p[-1] == '%') {
11009 switch(*template++) {
11010 case 0: *p = 0; return buf;
11011 case 'Y': i = tm->tm_year+1900; break;
11012 case 'y': i = tm->tm_year-100; break;
11013 case 'M': i = tm->tm_mon+1; break;
11014 case 'd': i = tm->tm_mday; break;
11015 case 'h': i = tm->tm_hour; break;
11016 case 'm': i = tm->tm_min; break;
11017 case 's': i = tm->tm_sec; break;
11020 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11026 CountPlayers (char *p)
11029 while(p = strchr(p, '\n')) p++, n++; // count participants
11034 WriteTourneyFile (char *results, FILE *f)
11035 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11036 if(f == NULL) f = fopen(appData.tourneyFile, "w");
11037 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11038 // create a file with tournament description
11039 fprintf(f, "-participants {%s}\n", appData.participants);
11040 fprintf(f, "-seedBase %d\n", appData.seedBase);
11041 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11042 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11043 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11044 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11045 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11046 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11047 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11048 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11049 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11050 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11051 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11052 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11053 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11054 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11055 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11056 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11057 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11058 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11059 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11060 fprintf(f, "-smpCores %d\n", appData.smpCores);
11062 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11064 fprintf(f, "-mps %d\n", appData.movesPerSession);
11065 fprintf(f, "-tc %s\n", appData.timeControl);
11066 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11068 fprintf(f, "-results \"%s\"\n", results);
11073 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11076 Substitute (char *participants, int expunge)
11078 int i, changed, changes=0, nPlayers=0;
11079 char *p, *q, *r, buf[MSG_SIZ];
11080 if(participants == NULL) return;
11081 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11082 r = p = participants; q = appData.participants;
11083 while(*p && *p == *q) {
11084 if(*p == '\n') r = p+1, nPlayers++;
11087 if(*p) { // difference
11088 while(*p && *p++ != '\n')
11090 while(*q && *q++ != '\n')
11092 changed = nPlayers;
11093 changes = 1 + (strcmp(p, q) != 0);
11095 if(changes == 1) { // a single engine mnemonic was changed
11096 q = r; while(*q) nPlayers += (*q++ == '\n');
11097 p = buf; while(*r && (*p = *r++) != '\n') p++;
11099 NamesToList(firstChessProgramNames, command, mnemonic, "all");
11100 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11101 if(mnemonic[i]) { // The substitute is valid
11103 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11104 flock(fileno(f), LOCK_EX);
11105 ParseArgsFromFile(f);
11106 fseek(f, 0, SEEK_SET);
11107 FREE(appData.participants); appData.participants = participants;
11108 if(expunge) { // erase results of replaced engine
11109 int len = strlen(appData.results), w, b, dummy;
11110 for(i=0; i<len; i++) {
11111 Pairing(i, nPlayers, &w, &b, &dummy);
11112 if((w == changed || b == changed) && appData.results[i] == '*') {
11113 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11118 for(i=0; i<len; i++) {
11119 Pairing(i, nPlayers, &w, &b, &dummy);
11120 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11123 WriteTourneyFile(appData.results, f);
11124 fclose(f); // release lock
11127 } else DisplayError(_("No engine with the name you gave is installed"), 0);
11129 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11130 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
11131 free(participants);
11136 CheckPlayers (char *participants)
11139 char buf[MSG_SIZ], *p;
11140 NamesToList(firstChessProgramNames, command, mnemonic, "all");
11141 while(p = strchr(participants, '\n')) {
11143 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11145 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11147 DisplayError(buf, 0);
11151 participants = p + 1;
11157 CreateTourney (char *name)
11160 if(matchMode && strcmp(name, appData.tourneyFile)) {
11161 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11163 if(name[0] == NULLCHAR) {
11164 if(appData.participants[0])
11165 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11168 f = fopen(name, "r");
11169 if(f) { // file exists
11170 ASSIGN(appData.tourneyFile, name);
11171 ParseArgsFromFile(f); // parse it
11173 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11174 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11175 DisplayError(_("Not enough participants"), 0);
11178 if(CheckPlayers(appData.participants)) return 0;
11179 ASSIGN(appData.tourneyFile, name);
11180 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11181 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11184 appData.noChessProgram = FALSE;
11185 appData.clockMode = TRUE;
11191 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11193 char buf[MSG_SIZ], *p, *q;
11194 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11195 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11196 skip = !all && group[0]; // if group requested, we start in skip mode
11197 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11198 p = names; q = buf; header = 0;
11199 while(*p && *p != '\n') *q++ = *p++;
11201 if(*p == '\n') p++;
11202 if(buf[0] == '#') {
11203 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11204 depth++; // we must be entering a new group
11205 if(all) continue; // suppress printing group headers when complete list requested
11207 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11209 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11210 if(engineList[i]) free(engineList[i]);
11211 engineList[i] = strdup(buf);
11212 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11213 if(engineMnemonic[i]) free(engineMnemonic[i]);
11214 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11216 sscanf(q + 8, "%s", buf + strlen(buf));
11219 engineMnemonic[i] = strdup(buf);
11222 engineList[i] = engineMnemonic[i] = NULL;
11226 // following implemented as macro to avoid type limitations
11227 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11230 SwapEngines (int n)
11231 { // swap settings for first engine and other engine (so far only some selected options)
11236 SWAP(chessProgram, p)
11238 SWAP(hasOwnBookUCI, h)
11239 SWAP(protocolVersion, h)
11241 SWAP(scoreIsAbsolute, h)
11246 SWAP(engOptions, p)
11247 SWAP(engInitString, p)
11248 SWAP(computerString, p)
11250 SWAP(fenOverride, p)
11252 SWAP(accumulateTC, h)
11259 GetEngineLine (char *s, int n)
11263 extern char *icsNames;
11264 if(!s || !*s) return 0;
11265 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11266 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11267 if(!mnemonic[i]) return 0;
11268 if(n == 11) return 1; // just testing if there was a match
11269 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11270 if(n == 1) SwapEngines(n);
11271 ParseArgsFromString(buf);
11272 if(n == 1) SwapEngines(n);
11273 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11274 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11275 ParseArgsFromString(buf);
11281 SetPlayer (int player, char *p)
11282 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
11284 char buf[MSG_SIZ], *engineName;
11285 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11286 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11287 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11289 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11290 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11291 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11292 ParseArgsFromString(buf);
11293 } else { // no engine with this nickname is installed!
11294 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11295 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11296 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11298 DisplayError(buf, 0);
11305 char *recentEngines;
11308 RecentEngineEvent (int nr)
11311 // SwapEngines(1); // bump first to second
11312 // ReplaceEngine(&second, 1); // and load it there
11313 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11314 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11315 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11316 ReplaceEngine(&first, 0);
11317 FloatToFront(&appData.recentEngineList, command[n]);
11322 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11323 { // determine players from game number
11324 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11326 if(appData.tourneyType == 0) {
11327 roundsPerCycle = (nPlayers - 1) | 1;
11328 pairingsPerRound = nPlayers / 2;
11329 } else if(appData.tourneyType > 0) {
11330 roundsPerCycle = nPlayers - appData.tourneyType;
11331 pairingsPerRound = appData.tourneyType;
11333 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11334 gamesPerCycle = gamesPerRound * roundsPerCycle;
11335 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11336 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11337 curRound = nr / gamesPerRound; nr %= gamesPerRound;
11338 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11339 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11340 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11342 if(appData.cycleSync) *syncInterval = gamesPerCycle;
11343 if(appData.roundSync) *syncInterval = gamesPerRound;
11345 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11347 if(appData.tourneyType == 0) {
11348 if(curPairing == (nPlayers-1)/2 ) {
11349 *whitePlayer = curRound;
11350 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11352 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11353 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11354 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11355 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11357 } else if(appData.tourneyType > 1) {
11358 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11359 *whitePlayer = curRound + appData.tourneyType;
11360 } else if(appData.tourneyType > 0) {
11361 *whitePlayer = curPairing;
11362 *blackPlayer = curRound + appData.tourneyType;
11365 // take care of white/black alternation per round.
11366 // For cycles and games this is already taken care of by default, derived from matchGame!
11367 return curRound & 1;
11371 NextTourneyGame (int nr, int *swapColors)
11372 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11374 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11376 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11377 tf = fopen(appData.tourneyFile, "r");
11378 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11379 ParseArgsFromFile(tf); fclose(tf);
11380 InitTimeControls(); // TC might be altered from tourney file
11382 nPlayers = CountPlayers(appData.participants); // count participants
11383 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11384 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11387 p = q = appData.results;
11388 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11389 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11390 DisplayMessage(_("Waiting for other game(s)"),"");
11391 waitingForGame = TRUE;
11392 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11395 waitingForGame = FALSE;
11398 if(appData.tourneyType < 0) {
11399 if(nr>=0 && !pairingReceived) {
11401 if(pairing.pr == NoProc) {
11402 if(!appData.pairingEngine[0]) {
11403 DisplayFatalError(_("No pairing engine specified"), 0, 1);
11406 StartChessProgram(&pairing); // starts the pairing engine
11408 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11409 SendToProgram(buf, &pairing);
11410 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11411 SendToProgram(buf, &pairing);
11412 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11414 pairingReceived = 0; // ... so we continue here
11416 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11417 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11418 matchGame = 1; roundNr = nr / syncInterval + 1;
11421 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11423 // redefine engines, engine dir, etc.
11424 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11425 if(first.pr == NoProc) {
11426 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11427 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
11429 if(second.pr == NoProc) {
11431 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11432 SwapEngines(1); // and make that valid for second engine by swapping
11433 InitEngine(&second, 1);
11435 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
11436 UpdateLogos(FALSE); // leave display to ModeHiglight()
11442 { // performs game initialization that does not invoke engines, and then tries to start the game
11443 int res, firstWhite, swapColors = 0;
11444 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11445 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
11447 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11448 if(strcmp(buf, currentDebugFile)) { // name has changed
11449 FILE *f = fopen(buf, "w");
11450 if(f) { // if opening the new file failed, just keep using the old one
11451 ASSIGN(currentDebugFile, buf);
11455 if(appData.serverFileName) {
11456 if(serverFP) fclose(serverFP);
11457 serverFP = fopen(appData.serverFileName, "w");
11458 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11459 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11463 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11464 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11465 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
11466 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11467 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11468 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11469 Reset(FALSE, first.pr != NoProc);
11470 res = LoadGameOrPosition(matchGame); // setup game
11471 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11472 if(!res) return; // abort when bad game/pos file
11473 if(appData.epd) {// in EPD mode we make sure first engine is to move
11474 firstWhite = !(forwardMostMove & 1);
11475 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
11476 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11478 TwoMachinesEvent();
11482 UserAdjudicationEvent (int result)
11484 ChessMove gameResult = GameIsDrawn;
11487 gameResult = WhiteWins;
11489 else if( result < 0 ) {
11490 gameResult = BlackWins;
11493 if( gameMode == TwoMachinesPlay ) {
11494 GameEnds( gameResult, "User adjudication", GE_XBOARD );
11499 // [HGM] save: calculate checksum of game to make games easily identifiable
11501 StringCheckSum (char *s)
11504 if(s==NULL) return 0;
11505 while(*s) i = i*259 + *s++;
11513 for(i=backwardMostMove; i<forwardMostMove; i++) {
11514 sum += pvInfoList[i].depth;
11515 sum += StringCheckSum(parseList[i]);
11516 sum += StringCheckSum(commentList[i]);
11519 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11520 return sum + StringCheckSum(commentList[i]);
11521 } // end of save patch
11524 GameEnds (ChessMove result, char *resultDetails, int whosays)
11526 GameMode nextGameMode;
11528 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11530 if(endingGame) return; /* [HGM] crash: forbid recursion */
11532 if(twoBoards) { // [HGM] dual: switch back to one board
11533 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11534 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11536 if (appData.debugMode) {
11537 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11538 result, resultDetails ? resultDetails : "(null)", whosays);
11541 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11543 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11545 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11546 /* If we are playing on ICS, the server decides when the
11547 game is over, but the engine can offer to draw, claim
11551 if (appData.zippyPlay && first.initDone) {
11552 if (result == GameIsDrawn) {
11553 /* In case draw still needs to be claimed */
11554 SendToICS(ics_prefix);
11555 SendToICS("draw\n");
11556 } else if (StrCaseStr(resultDetails, "resign")) {
11557 SendToICS(ics_prefix);
11558 SendToICS("resign\n");
11562 endingGame = 0; /* [HGM] crash */
11566 /* If we're loading the game from a file, stop */
11567 if (whosays == GE_FILE) {
11568 (void) StopLoadGameTimer();
11572 /* Cancel draw offers */
11573 first.offeredDraw = second.offeredDraw = 0;
11575 /* If this is an ICS game, only ICS can really say it's done;
11576 if not, anyone can. */
11577 isIcsGame = (gameMode == IcsPlayingWhite ||
11578 gameMode == IcsPlayingBlack ||
11579 gameMode == IcsObserving ||
11580 gameMode == IcsExamining);
11582 if (!isIcsGame || whosays == GE_ICS) {
11583 /* OK -- not an ICS game, or ICS said it was done */
11585 if (!isIcsGame && !appData.noChessProgram)
11586 SetUserThinkingEnables();
11588 /* [HGM] if a machine claims the game end we verify this claim */
11589 if(gameMode == TwoMachinesPlay && appData.testClaims) {
11590 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11592 ChessMove trueResult = (ChessMove) -1;
11594 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
11595 first.twoMachinesColor[0] :
11596 second.twoMachinesColor[0] ;
11598 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11599 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11600 /* [HGM] verify: engine mate claims accepted if they were flagged */
11601 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11603 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11604 /* [HGM] verify: engine mate claims accepted if they were flagged */
11605 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11607 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11608 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11611 // now verify win claims, but not in drop games, as we don't understand those yet
11612 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11613 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11614 (result == WhiteWins && claimer == 'w' ||
11615 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
11616 if (appData.debugMode) {
11617 fprintf(debugFP, "result=%d sp=%d move=%d\n",
11618 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11620 if(result != trueResult) {
11621 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11622 result = claimer == 'w' ? BlackWins : WhiteWins;
11623 resultDetails = buf;
11626 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11627 && (forwardMostMove <= backwardMostMove ||
11628 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11629 (claimer=='b')==(forwardMostMove&1))
11631 /* [HGM] verify: draws that were not flagged are false claims */
11632 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11633 result = claimer == 'w' ? BlackWins : WhiteWins;
11634 resultDetails = buf;
11636 /* (Claiming a loss is accepted no questions asked!) */
11637 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11638 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11639 result = GameUnfinished;
11640 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11642 /* [HGM] bare: don't allow bare King to win */
11643 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11644 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11645 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11646 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11647 && result != GameIsDrawn)
11648 { int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11649 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11650 int p = (signed char)boards[forwardMostMove][i][j] - color;
11651 if(p >= 0 && p <= (int)WhiteKing) k++;
11652 oppoKings += (p + color == WhiteKing + BlackPawn - color);
11654 if (appData.debugMode) {
11655 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11656 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11658 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11659 result = GameIsDrawn;
11660 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11661 resultDetails = buf;
11667 if(serverMoves != NULL && !loadFlag) { char c = '=';
11668 if(result==WhiteWins) c = '+';
11669 if(result==BlackWins) c = '-';
11670 if(resultDetails != NULL)
11671 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11673 if (resultDetails != NULL) {
11674 gameInfo.result = result;
11675 gameInfo.resultDetails = StrSave(resultDetails);
11677 /* display last move only if game was not loaded from file */
11678 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11679 DisplayMove(currentMove - 1);
11681 if (forwardMostMove != 0) {
11682 if (gameMode != PlayFromGameFile && gameMode != EditGame
11683 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11685 if (*appData.saveGameFile != NULLCHAR) {
11686 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11687 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11689 SaveGameToFile(appData.saveGameFile, TRUE);
11690 } else if (appData.autoSaveGames) {
11691 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11693 if (*appData.savePositionFile != NULLCHAR) {
11694 SavePositionToFile(appData.savePositionFile);
11696 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11700 /* Tell program how game ended in case it is learning */
11701 /* [HGM] Moved this to after saving the PGN, just in case */
11702 /* engine died and we got here through time loss. In that */
11703 /* case we will get a fatal error writing the pipe, which */
11704 /* would otherwise lose us the PGN. */
11705 /* [HGM] crash: not needed anymore, but doesn't hurt; */
11706 /* output during GameEnds should never be fatal anymore */
11707 if (gameMode == MachinePlaysWhite ||
11708 gameMode == MachinePlaysBlack ||
11709 gameMode == TwoMachinesPlay ||
11710 gameMode == IcsPlayingWhite ||
11711 gameMode == IcsPlayingBlack ||
11712 gameMode == BeginningOfGame) {
11714 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11716 if (first.pr != NoProc) {
11717 SendToProgram(buf, &first);
11719 if (second.pr != NoProc &&
11720 gameMode == TwoMachinesPlay) {
11721 SendToProgram(buf, &second);
11726 if (appData.icsActive) {
11727 if (appData.quietPlay &&
11728 (gameMode == IcsPlayingWhite ||
11729 gameMode == IcsPlayingBlack)) {
11730 SendToICS(ics_prefix);
11731 SendToICS("set shout 1\n");
11733 nextGameMode = IcsIdle;
11734 ics_user_moved = FALSE;
11735 /* clean up premove. It's ugly when the game has ended and the
11736 * premove highlights are still on the board.
11739 gotPremove = FALSE;
11740 ClearPremoveHighlights();
11741 DrawPosition(FALSE, boards[currentMove]);
11743 if (whosays == GE_ICS) {
11746 if (gameMode == IcsPlayingWhite)
11748 else if(gameMode == IcsPlayingBlack)
11749 PlayIcsLossSound();
11752 if (gameMode == IcsPlayingBlack)
11754 else if(gameMode == IcsPlayingWhite)
11755 PlayIcsLossSound();
11758 PlayIcsDrawSound();
11761 PlayIcsUnfinishedSound();
11764 if(appData.quitNext) { ExitEvent(0); return; }
11765 } else if (gameMode == EditGame ||
11766 gameMode == PlayFromGameFile ||
11767 gameMode == AnalyzeMode ||
11768 gameMode == AnalyzeFile) {
11769 nextGameMode = gameMode;
11771 nextGameMode = EndOfGame;
11776 nextGameMode = gameMode;
11779 if (appData.noChessProgram) {
11780 gameMode = nextGameMode;
11782 endingGame = 0; /* [HGM] crash */
11787 /* Put first chess program into idle state */
11788 if (first.pr != NoProc &&
11789 (gameMode == MachinePlaysWhite ||
11790 gameMode == MachinePlaysBlack ||
11791 gameMode == TwoMachinesPlay ||
11792 gameMode == IcsPlayingWhite ||
11793 gameMode == IcsPlayingBlack ||
11794 gameMode == BeginningOfGame)) {
11795 SendToProgram("force\n", &first);
11796 if (first.usePing) {
11798 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11799 SendToProgram(buf, &first);
11802 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11803 /* Kill off first chess program */
11804 if (first.isr != NULL)
11805 RemoveInputSource(first.isr);
11808 if (first.pr != NoProc) {
11810 DoSleep( appData.delayBeforeQuit );
11811 SendToProgram("quit\n", &first);
11812 DestroyChildProcess(first.pr, 4 + first.useSigterm);
11813 first.reload = TRUE;
11817 if (second.reuse) {
11818 /* Put second chess program into idle state */
11819 if (second.pr != NoProc &&
11820 gameMode == TwoMachinesPlay) {
11821 SendToProgram("force\n", &second);
11822 if (second.usePing) {
11824 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11825 SendToProgram(buf, &second);
11828 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11829 /* Kill off second chess program */
11830 if (second.isr != NULL)
11831 RemoveInputSource(second.isr);
11834 if (second.pr != NoProc) {
11835 DoSleep( appData.delayBeforeQuit );
11836 SendToProgram("quit\n", &second);
11837 DestroyChildProcess(second.pr, 4 + second.useSigterm);
11838 second.reload = TRUE;
11840 second.pr = NoProc;
11843 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11844 char resChar = '=';
11848 if (first.twoMachinesColor[0] == 'w') {
11851 second.matchWins++;
11856 if (first.twoMachinesColor[0] == 'b') {
11859 second.matchWins++;
11862 case GameUnfinished:
11868 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11869 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11870 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11871 ReserveGame(nextGame, resChar); // sets nextGame
11872 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11873 else ranking = strdup("busy"); //suppress popup when aborted but not finished
11874 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11876 if (nextGame <= appData.matchGames && !abortMatch) {
11877 gameMode = nextGameMode;
11878 matchGame = nextGame; // this will be overruled in tourney mode!
11879 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11880 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11881 endingGame = 0; /* [HGM] crash */
11884 gameMode = nextGameMode;
11886 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
11887 OutputKibitz(2, buf);
11888 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
11889 OutputKibitz(2, buf);
11890 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
11891 if(second.matchWins) OutputKibitz(2, buf);
11892 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
11893 OutputKibitz(2, buf);
11895 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11896 first.tidy, second.tidy,
11897 first.matchWins, second.matchWins,
11898 appData.matchGames - (first.matchWins + second.matchWins));
11899 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11900 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11901 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11902 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11903 first.twoMachinesColor = "black\n";
11904 second.twoMachinesColor = "white\n";
11906 first.twoMachinesColor = "white\n";
11907 second.twoMachinesColor = "black\n";
11911 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11912 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11914 gameMode = nextGameMode;
11916 endingGame = 0; /* [HGM] crash */
11917 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11918 if(matchMode == TRUE) { // match through command line: exit with or without popup
11920 ToNrEvent(forwardMostMove);
11921 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11923 } else DisplayFatalError(buf, 0, 0);
11924 } else { // match through menu; just stop, with or without popup
11925 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11928 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11929 } else DisplayNote(buf);
11931 if(ranking) free(ranking);
11935 /* Assumes program was just initialized (initString sent).
11936 Leaves program in force mode. */
11938 FeedMovesToProgram (ChessProgramState *cps, int upto)
11942 if (appData.debugMode)
11943 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11944 startedFromSetupPosition ? "position and " : "",
11945 backwardMostMove, upto, cps->which);
11946 if(currentlyInitializedVariant != gameInfo.variant) {
11948 // [HGM] variantswitch: make engine aware of new variant
11949 if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11950 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11951 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11952 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11953 SendToProgram(buf, cps);
11954 currentlyInitializedVariant = gameInfo.variant;
11956 SendToProgram("force\n", cps);
11957 if (startedFromSetupPosition) {
11958 SendBoard(cps, backwardMostMove);
11959 if (appData.debugMode) {
11960 fprintf(debugFP, "feedMoves\n");
11963 for (i = backwardMostMove; i < upto; i++) {
11964 SendMoveToProgram(i, cps);
11970 ResurrectChessProgram ()
11972 /* The chess program may have exited.
11973 If so, restart it and feed it all the moves made so far. */
11974 static int doInit = 0;
11976 if (appData.noChessProgram) return 1;
11978 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11979 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11980 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11981 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11983 if (first.pr != NoProc) return 1;
11984 StartChessProgram(&first);
11986 InitChessProgram(&first, FALSE);
11987 FeedMovesToProgram(&first, currentMove);
11989 if (!first.sendTime) {
11990 /* can't tell gnuchess what its clock should read,
11991 so we bow to its notion. */
11993 timeRemaining[0][currentMove] = whiteTimeRemaining;
11994 timeRemaining[1][currentMove] = blackTimeRemaining;
11997 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11998 appData.icsEngineAnalyze) && first.analysisSupport) {
11999 SendToProgram("analyze\n", &first);
12000 first.analyzing = TRUE;
12006 * Button procedures
12009 Reset (int redraw, int init)
12013 if (appData.debugMode) {
12014 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12015 redraw, init, gameMode);
12017 pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12018 for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12019 CleanupTail(); // [HGM] vari: delete any stored variations
12020 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12021 pausing = pauseExamInvalid = FALSE;
12022 startedFromSetupPosition = blackPlaysFirst = FALSE;
12024 whiteFlag = blackFlag = FALSE;
12025 userOfferedDraw = FALSE;
12026 hintRequested = bookRequested = FALSE;
12027 first.maybeThinking = FALSE;
12028 second.maybeThinking = FALSE;
12029 first.bookSuspend = FALSE; // [HGM] book
12030 second.bookSuspend = FALSE;
12031 thinkOutput[0] = NULLCHAR;
12032 lastHint[0] = NULLCHAR;
12033 ClearGameInfo(&gameInfo);
12034 gameInfo.variant = StringToVariant(appData.variant);
12035 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12036 gameInfo.variant = VariantUnknown;
12037 strncpy(engineVariant, appData.variant, MSG_SIZ);
12039 ics_user_moved = ics_clock_paused = FALSE;
12040 ics_getting_history = H_FALSE;
12042 white_holding[0] = black_holding[0] = NULLCHAR;
12043 ClearProgramStats();
12044 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12048 flipView = appData.flipView;
12049 ClearPremoveHighlights();
12050 gotPremove = FALSE;
12051 alarmSounded = FALSE;
12052 killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12054 GameEnds(EndOfFile, NULL, GE_PLAYER);
12055 if(appData.serverMovesName != NULL) {
12056 /* [HGM] prepare to make moves file for broadcasting */
12057 clock_t t = clock();
12058 if(serverMoves != NULL) fclose(serverMoves);
12059 serverMoves = fopen(appData.serverMovesName, "r");
12060 if(serverMoves != NULL) {
12061 fclose(serverMoves);
12062 /* delay 15 sec before overwriting, so all clients can see end */
12063 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12065 serverMoves = fopen(appData.serverMovesName, "w");
12069 gameMode = BeginningOfGame;
12071 if(appData.icsActive) gameInfo.variant = VariantNormal;
12072 currentMove = forwardMostMove = backwardMostMove = 0;
12073 MarkTargetSquares(1);
12074 InitPosition(redraw);
12075 for (i = 0; i < MAX_MOVES; i++) {
12076 if (commentList[i] != NULL) {
12077 free(commentList[i]);
12078 commentList[i] = NULL;
12082 timeRemaining[0][0] = whiteTimeRemaining;
12083 timeRemaining[1][0] = blackTimeRemaining;
12085 if (first.pr == NoProc) {
12086 StartChessProgram(&first);
12089 InitChessProgram(&first, startedFromSetupPosition);
12092 DisplayMessage("", "");
12093 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12094 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12095 ClearMap(); // [HGM] exclude: invalidate map
12099 AutoPlayGameLoop ()
12102 if (!AutoPlayOneMove())
12104 if (matchMode || appData.timeDelay == 0)
12106 if (appData.timeDelay < 0)
12108 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12116 ReloadGame(1); // next game
12122 int fromX, fromY, toX, toY;
12124 if (appData.debugMode) {
12125 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12128 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12131 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12132 pvInfoList[currentMove].depth = programStats.depth;
12133 pvInfoList[currentMove].score = programStats.score;
12134 pvInfoList[currentMove].time = 0;
12135 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12136 else { // append analysis of final position as comment
12138 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12139 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12141 programStats.depth = 0;
12144 if (currentMove >= forwardMostMove) {
12145 if(gameMode == AnalyzeFile) {
12146 if(appData.loadGameIndex == -1) {
12147 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12148 ScheduleDelayedEvent(AnalyzeNextGame, 10);
12150 ExitAnalyzeMode(); SendToProgram("force\n", &first);
12153 // gameMode = EndOfGame;
12154 // ModeHighlight();
12156 /* [AS] Clear current move marker at the end of a game */
12157 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12162 toX = moveList[currentMove][2] - AAA;
12163 toY = moveList[currentMove][3] - ONE;
12165 if (moveList[currentMove][1] == '@') {
12166 if (appData.highlightLastMove) {
12167 SetHighlights(-1, -1, toX, toY);
12170 fromX = moveList[currentMove][0] - AAA;
12171 fromY = moveList[currentMove][1] - ONE;
12173 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12175 if(moveList[currentMove][4] == ';') { // multi-leg
12176 killX = moveList[currentMove][5] - AAA;
12177 killY = moveList[currentMove][6] - ONE;
12179 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12180 killX = killY = -1;
12182 if (appData.highlightLastMove) {
12183 SetHighlights(fromX, fromY, toX, toY);
12186 DisplayMove(currentMove);
12187 SendMoveToProgram(currentMove++, &first);
12188 DisplayBothClocks();
12189 DrawPosition(FALSE, boards[currentMove]);
12190 // [HGM] PV info: always display, routine tests if empty
12191 DisplayComment(currentMove - 1, commentList[currentMove]);
12197 LoadGameOneMove (ChessMove readAhead)
12199 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12200 char promoChar = NULLCHAR;
12201 ChessMove moveType;
12202 char move[MSG_SIZ];
12205 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12206 gameMode != AnalyzeMode && gameMode != Training) {
12211 yyboardindex = forwardMostMove;
12212 if (readAhead != EndOfFile) {
12213 moveType = readAhead;
12215 if (gameFileFP == NULL)
12217 moveType = (ChessMove) Myylex();
12221 switch (moveType) {
12223 if (appData.debugMode)
12224 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12227 /* append the comment but don't display it */
12228 AppendComment(currentMove, p, FALSE);
12231 case WhiteCapturesEnPassant:
12232 case BlackCapturesEnPassant:
12233 case WhitePromotion:
12234 case BlackPromotion:
12235 case WhiteNonPromotion:
12236 case BlackNonPromotion:
12239 case WhiteKingSideCastle:
12240 case WhiteQueenSideCastle:
12241 case BlackKingSideCastle:
12242 case BlackQueenSideCastle:
12243 case WhiteKingSideCastleWild:
12244 case WhiteQueenSideCastleWild:
12245 case BlackKingSideCastleWild:
12246 case BlackQueenSideCastleWild:
12248 case WhiteHSideCastleFR:
12249 case WhiteASideCastleFR:
12250 case BlackHSideCastleFR:
12251 case BlackASideCastleFR:
12253 if (appData.debugMode)
12254 fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12255 fromX = currentMoveString[0] - AAA;
12256 fromY = currentMoveString[1] - ONE;
12257 toX = currentMoveString[2] - AAA;
12258 toY = currentMoveString[3] - ONE;
12259 promoChar = currentMoveString[4];
12260 if(promoChar == ';') promoChar = currentMoveString[7];
12265 if (appData.debugMode)
12266 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12267 fromX = moveType == WhiteDrop ?
12268 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12269 (int) CharToPiece(ToLower(currentMoveString[0]));
12271 toX = currentMoveString[2] - AAA;
12272 toY = currentMoveString[3] - ONE;
12278 case GameUnfinished:
12279 if (appData.debugMode)
12280 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12281 p = strchr(yy_text, '{');
12282 if (p == NULL) p = strchr(yy_text, '(');
12285 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12287 q = strchr(p, *p == '{' ? '}' : ')');
12288 if (q != NULL) *q = NULLCHAR;
12291 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12292 GameEnds(moveType, p, GE_FILE);
12294 if (cmailMsgLoaded) {
12296 flipView = WhiteOnMove(currentMove);
12297 if (moveType == GameUnfinished) flipView = !flipView;
12298 if (appData.debugMode)
12299 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12304 if (appData.debugMode)
12305 fprintf(debugFP, "Parser hit end of file\n");
12306 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12312 if (WhiteOnMove(currentMove)) {
12313 GameEnds(BlackWins, "Black mates", GE_FILE);
12315 GameEnds(WhiteWins, "White mates", GE_FILE);
12319 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12325 case MoveNumberOne:
12326 if (lastLoadGameStart == GNUChessGame) {
12327 /* GNUChessGames have numbers, but they aren't move numbers */
12328 if (appData.debugMode)
12329 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12330 yy_text, (int) moveType);
12331 return LoadGameOneMove(EndOfFile); /* tail recursion */
12333 /* else fall thru */
12338 /* Reached start of next game in file */
12339 if (appData.debugMode)
12340 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12341 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12347 if (WhiteOnMove(currentMove)) {
12348 GameEnds(BlackWins, "Black mates", GE_FILE);
12350 GameEnds(WhiteWins, "White mates", GE_FILE);
12354 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12360 case PositionDiagram: /* should not happen; ignore */
12361 case ElapsedTime: /* ignore */
12362 case NAG: /* ignore */
12363 if (appData.debugMode)
12364 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12365 yy_text, (int) moveType);
12366 return LoadGameOneMove(EndOfFile); /* tail recursion */
12369 if (appData.testLegality) {
12370 if (appData.debugMode)
12371 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12372 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12373 (forwardMostMove / 2) + 1,
12374 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12375 DisplayError(move, 0);
12378 if (appData.debugMode)
12379 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12380 yy_text, currentMoveString);
12381 if(currentMoveString[1] == '@') {
12382 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12385 fromX = currentMoveString[0] - AAA;
12386 fromY = currentMoveString[1] - ONE;
12388 toX = currentMoveString[2] - AAA;
12389 toY = currentMoveString[3] - ONE;
12390 promoChar = currentMoveString[4];
12394 case AmbiguousMove:
12395 if (appData.debugMode)
12396 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12397 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12398 (forwardMostMove / 2) + 1,
12399 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12400 DisplayError(move, 0);
12405 case ImpossibleMove:
12406 if (appData.debugMode)
12407 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12408 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12409 (forwardMostMove / 2) + 1,
12410 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12411 DisplayError(move, 0);
12417 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12418 DrawPosition(FALSE, boards[currentMove]);
12419 DisplayBothClocks();
12420 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12421 DisplayComment(currentMove - 1, commentList[currentMove]);
12423 (void) StopLoadGameTimer();
12425 cmailOldMove = forwardMostMove;
12428 /* currentMoveString is set as a side-effect of yylex */
12430 thinkOutput[0] = NULLCHAR;
12431 MakeMove(fromX, fromY, toX, toY, promoChar);
12432 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12433 currentMove = forwardMostMove;
12438 /* Load the nth game from the given file */
12440 LoadGameFromFile (char *filename, int n, char *title, int useList)
12445 if (strcmp(filename, "-") == 0) {
12449 f = fopen(filename, "rb");
12451 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12452 DisplayError(buf, errno);
12456 if (fseek(f, 0, 0) == -1) {
12457 /* f is not seekable; probably a pipe */
12460 if (useList && n == 0) {
12461 int error = GameListBuild(f);
12463 DisplayError(_("Cannot build game list"), error);
12464 } else if (!ListEmpty(&gameList) &&
12465 ((ListGame *) gameList.tailPred)->number > 1) {
12466 GameListPopUp(f, title);
12473 return LoadGame(f, n, title, FALSE);
12478 MakeRegisteredMove ()
12480 int fromX, fromY, toX, toY;
12482 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12483 switch (cmailMoveType[lastLoadGameNumber - 1]) {
12486 if (appData.debugMode)
12487 fprintf(debugFP, "Restoring %s for game %d\n",
12488 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12490 thinkOutput[0] = NULLCHAR;
12491 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12492 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12493 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12494 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12495 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12496 promoChar = cmailMove[lastLoadGameNumber - 1][4];
12497 MakeMove(fromX, fromY, toX, toY, promoChar);
12498 ShowMove(fromX, fromY, toX, toY);
12500 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12507 if (WhiteOnMove(currentMove)) {
12508 GameEnds(BlackWins, "Black mates", GE_PLAYER);
12510 GameEnds(WhiteWins, "White mates", GE_PLAYER);
12515 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12522 if (WhiteOnMove(currentMove)) {
12523 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12525 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12530 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12541 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12543 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12547 if (gameNumber > nCmailGames) {
12548 DisplayError(_("No more games in this message"), 0);
12551 if (f == lastLoadGameFP) {
12552 int offset = gameNumber - lastLoadGameNumber;
12554 cmailMsg[0] = NULLCHAR;
12555 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12556 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12557 nCmailMovesRegistered--;
12559 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12560 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12561 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12564 if (! RegisterMove()) return FALSE;
12568 retVal = LoadGame(f, gameNumber, title, useList);
12570 /* Make move registered during previous look at this game, if any */
12571 MakeRegisteredMove();
12573 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12574 commentList[currentMove]
12575 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12576 DisplayComment(currentMove - 1, commentList[currentMove]);
12582 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12584 ReloadGame (int offset)
12586 int gameNumber = lastLoadGameNumber + offset;
12587 if (lastLoadGameFP == NULL) {
12588 DisplayError(_("No game has been loaded yet"), 0);
12591 if (gameNumber <= 0) {
12592 DisplayError(_("Can't back up any further"), 0);
12595 if (cmailMsgLoaded) {
12596 return CmailLoadGame(lastLoadGameFP, gameNumber,
12597 lastLoadGameTitle, lastLoadGameUseList);
12599 return LoadGame(lastLoadGameFP, gameNumber,
12600 lastLoadGameTitle, lastLoadGameUseList);
12604 int keys[EmptySquare+1];
12607 PositionMatches (Board b1, Board b2)
12610 switch(appData.searchMode) {
12611 case 1: return CompareWithRights(b1, b2);
12613 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12614 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12618 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12619 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12620 sum += keys[b1[r][f]] - keys[b2[r][f]];
12624 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12625 sum += keys[b1[r][f]] - keys[b2[r][f]];
12637 int pieceList[256], quickBoard[256];
12638 ChessSquare pieceType[256] = { EmptySquare };
12639 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12640 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12641 int soughtTotal, turn;
12642 Boolean epOK, flipSearch;
12645 unsigned char piece, to;
12648 #define DSIZE (250000)
12650 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12651 Move *moveDatabase = initialSpace;
12652 unsigned int movePtr, dataSize = DSIZE;
12655 MakePieceList (Board board, int *counts)
12657 int r, f, n=Q_PROMO, total=0;
12658 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12659 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12660 int sq = f + (r<<4);
12661 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12662 quickBoard[sq] = ++n;
12664 pieceType[n] = board[r][f];
12665 counts[board[r][f]]++;
12666 if(board[r][f] == WhiteKing) pieceList[1] = n; else
12667 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12671 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12676 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12678 int sq = fromX + (fromY<<4);
12679 int piece = quickBoard[sq], rook;
12680 quickBoard[sq] = 0;
12681 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12682 if(piece == pieceList[1] && fromY == toY) {
12683 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12684 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12685 moveDatabase[movePtr++].piece = Q_WCASTL;
12686 quickBoard[sq] = piece;
12687 piece = quickBoard[from]; quickBoard[from] = 0;
12688 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12689 } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12690 quickBoard[sq] = 0; // remove Rook
12691 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12692 moveDatabase[movePtr++].piece = Q_WCASTL;
12693 quickBoard[sq] = pieceList[1]; // put King
12695 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12698 if(piece == pieceList[2] && fromY == toY) {
12699 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12700 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12701 moveDatabase[movePtr++].piece = Q_BCASTL;
12702 quickBoard[sq] = piece;
12703 piece = quickBoard[from]; quickBoard[from] = 0;
12704 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12705 } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12706 quickBoard[sq] = 0; // remove Rook
12707 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12708 moveDatabase[movePtr++].piece = Q_BCASTL;
12709 quickBoard[sq] = pieceList[2]; // put King
12711 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12714 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12715 quickBoard[(fromY<<4)+toX] = 0;
12716 moveDatabase[movePtr].piece = Q_EP;
12717 moveDatabase[movePtr++].to = (fromY<<4)+toX;
12718 moveDatabase[movePtr].to = sq;
12720 if(promoPiece != pieceType[piece]) {
12721 moveDatabase[movePtr++].piece = Q_PROMO;
12722 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12724 moveDatabase[movePtr].piece = piece;
12725 quickBoard[sq] = piece;
12730 PackGame (Board board)
12732 Move *newSpace = NULL;
12733 moveDatabase[movePtr].piece = 0; // terminate previous game
12734 if(movePtr > dataSize) {
12735 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12736 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12737 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12740 Move *p = moveDatabase, *q = newSpace;
12741 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
12742 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12743 moveDatabase = newSpace;
12744 } else { // calloc failed, we must be out of memory. Too bad...
12745 dataSize = 0; // prevent calloc events for all subsequent games
12746 return 0; // and signal this one isn't cached
12750 MakePieceList(board, counts);
12755 QuickCompare (Board board, int *minCounts, int *maxCounts)
12756 { // compare according to search mode
12758 switch(appData.searchMode)
12760 case 1: // exact position match
12761 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12762 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12763 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12766 case 2: // can have extra material on empty squares
12767 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12768 if(board[r][f] == EmptySquare) continue;
12769 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12772 case 3: // material with exact Pawn structure
12773 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12774 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12775 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12776 } // fall through to material comparison
12777 case 4: // exact material
12778 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12780 case 6: // material range with given imbalance
12781 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12782 // fall through to range comparison
12783 case 5: // material range
12784 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12790 QuickScan (Board board, Move *move)
12791 { // reconstruct game,and compare all positions in it
12792 int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12794 int piece = move->piece;
12795 int to = move->to, from = pieceList[piece];
12796 if(found < 0) { // if already found just scan to game end for final piece count
12797 if(QuickCompare(soughtBoard, minSought, maxSought) ||
12798 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12799 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12800 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12802 static int lastCounts[EmptySquare+1];
12804 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12805 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12806 } else stretch = 0;
12807 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12808 if(found >= 0 && !appData.minPieces) return found;
12810 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12811 if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12812 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12813 piece = (++move)->piece;
12814 from = pieceList[piece];
12815 counts[pieceType[piece]]--;
12816 pieceType[piece] = (ChessSquare) move->to;
12817 counts[move->to]++;
12818 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12819 counts[pieceType[quickBoard[to]]]--;
12820 quickBoard[to] = 0; total--;
12823 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12824 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12825 from = pieceList[piece]; // so this must be King
12826 quickBoard[from] = 0;
12827 pieceList[piece] = to;
12828 from = pieceList[(++move)->piece]; // for FRC this has to be done here
12829 quickBoard[from] = 0; // rook
12830 quickBoard[to] = piece;
12831 to = move->to; piece = move->piece;
12835 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12836 if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12837 quickBoard[from] = 0;
12839 quickBoard[to] = piece;
12840 pieceList[piece] = to;
12850 flipSearch = FALSE;
12851 CopyBoard(soughtBoard, boards[currentMove]);
12852 soughtTotal = MakePieceList(soughtBoard, maxSought);
12853 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12854 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12855 CopyBoard(reverseBoard, boards[currentMove]);
12856 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12857 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12858 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12859 reverseBoard[r][f] = piece;
12861 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12862 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12863 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12864 || (boards[currentMove][CASTLING][2] == NoRights ||
12865 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12866 && (boards[currentMove][CASTLING][5] == NoRights ||
12867 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12870 CopyBoard(flipBoard, soughtBoard);
12871 CopyBoard(rotateBoard, reverseBoard);
12872 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12873 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
12874 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12877 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12878 if(appData.searchMode >= 5) {
12879 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12880 MakePieceList(soughtBoard, minSought);
12881 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12883 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12884 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12887 GameInfo dummyInfo;
12888 static int creatingBook;
12891 GameContainsPosition (FILE *f, ListGame *lg)
12893 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12894 int fromX, fromY, toX, toY;
12896 static int initDone=FALSE;
12898 // weed out games based on numerical tag comparison
12899 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12900 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12901 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12902 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12904 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12907 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12908 else CopyBoard(boards[scratch], initialPosition); // default start position
12911 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12912 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12915 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12916 fseek(f, lg->offset, 0);
12919 yyboardindex = scratch;
12920 quickFlag = plyNr+1;
12925 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12931 if(plyNr) return -1; // after we have seen moves, this is for new game
12934 case AmbiguousMove: // we cannot reconstruct the game beyond these two
12935 case ImpossibleMove:
12936 case WhiteWins: // game ends here with these four
12939 case GameUnfinished:
12943 if(appData.testLegality) return -1;
12944 case WhiteCapturesEnPassant:
12945 case BlackCapturesEnPassant:
12946 case WhitePromotion:
12947 case BlackPromotion:
12948 case WhiteNonPromotion:
12949 case BlackNonPromotion:
12952 case WhiteKingSideCastle:
12953 case WhiteQueenSideCastle:
12954 case BlackKingSideCastle:
12955 case BlackQueenSideCastle:
12956 case WhiteKingSideCastleWild:
12957 case WhiteQueenSideCastleWild:
12958 case BlackKingSideCastleWild:
12959 case BlackQueenSideCastleWild:
12960 case WhiteHSideCastleFR:
12961 case WhiteASideCastleFR:
12962 case BlackHSideCastleFR:
12963 case BlackASideCastleFR:
12964 fromX = currentMoveString[0] - AAA;
12965 fromY = currentMoveString[1] - ONE;
12966 toX = currentMoveString[2] - AAA;
12967 toY = currentMoveString[3] - ONE;
12968 promoChar = currentMoveString[4];
12972 fromX = next == WhiteDrop ?
12973 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12974 (int) CharToPiece(ToLower(currentMoveString[0]));
12976 toX = currentMoveString[2] - AAA;
12977 toY = currentMoveString[3] - ONE;
12981 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12983 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12984 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12985 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12986 if(appData.findMirror) {
12987 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12988 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12993 /* Load the nth game from open file f */
12995 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12999 int gn = gameNumber;
13000 ListGame *lg = NULL;
13001 int numPGNTags = 0, i;
13003 GameMode oldGameMode;
13004 VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13005 char oldName[MSG_SIZ];
13007 safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13009 if (appData.debugMode)
13010 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13012 if (gameMode == Training )
13013 SetTrainingModeOff();
13015 oldGameMode = gameMode;
13016 if (gameMode != BeginningOfGame) {
13017 Reset(FALSE, TRUE);
13019 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13022 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13023 fclose(lastLoadGameFP);
13027 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13030 fseek(f, lg->offset, 0);
13031 GameListHighlight(gameNumber);
13032 pos = lg->position;
13036 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13037 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13039 DisplayError(_("Game number out of range"), 0);
13044 if (fseek(f, 0, 0) == -1) {
13045 if (f == lastLoadGameFP ?
13046 gameNumber == lastLoadGameNumber + 1 :
13050 DisplayError(_("Can't seek on game file"), 0);
13055 lastLoadGameFP = f;
13056 lastLoadGameNumber = gameNumber;
13057 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13058 lastLoadGameUseList = useList;
13062 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13063 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13064 lg->gameInfo.black);
13066 } else if (*title != NULLCHAR) {
13067 if (gameNumber > 1) {
13068 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13071 DisplayTitle(title);
13075 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13076 gameMode = PlayFromGameFile;
13080 currentMove = forwardMostMove = backwardMostMove = 0;
13081 CopyBoard(boards[0], initialPosition);
13085 * Skip the first gn-1 games in the file.
13086 * Also skip over anything that precedes an identifiable
13087 * start of game marker, to avoid being confused by
13088 * garbage at the start of the file. Currently
13089 * recognized start of game markers are the move number "1",
13090 * the pattern "gnuchess .* game", the pattern
13091 * "^[#;%] [^ ]* game file", and a PGN tag block.
13092 * A game that starts with one of the latter two patterns
13093 * will also have a move number 1, possibly
13094 * following a position diagram.
13095 * 5-4-02: Let's try being more lenient and allowing a game to
13096 * start with an unnumbered move. Does that break anything?
13098 cm = lastLoadGameStart = EndOfFile;
13100 yyboardindex = forwardMostMove;
13101 cm = (ChessMove) Myylex();
13104 if (cmailMsgLoaded) {
13105 nCmailGames = CMAIL_MAX_GAMES - gn;
13108 DisplayError(_("Game not found in file"), 0);
13115 lastLoadGameStart = cm;
13118 case MoveNumberOne:
13119 switch (lastLoadGameStart) {
13124 case MoveNumberOne:
13126 gn--; /* count this game */
13127 lastLoadGameStart = cm;
13136 switch (lastLoadGameStart) {
13139 case MoveNumberOne:
13141 gn--; /* count this game */
13142 lastLoadGameStart = cm;
13145 lastLoadGameStart = cm; /* game counted already */
13153 yyboardindex = forwardMostMove;
13154 cm = (ChessMove) Myylex();
13155 } while (cm == PGNTag || cm == Comment);
13162 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13163 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
13164 != CMAIL_OLD_RESULT) {
13166 cmailResult[ CMAIL_MAX_GAMES
13167 - gn - 1] = CMAIL_OLD_RESULT;
13174 /* Only a NormalMove can be at the start of a game
13175 * without a position diagram. */
13176 if (lastLoadGameStart == EndOfFile ) {
13178 lastLoadGameStart = MoveNumberOne;
13187 if (appData.debugMode)
13188 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13190 for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13192 if (cm == XBoardGame) {
13193 /* Skip any header junk before position diagram and/or move 1 */
13195 yyboardindex = forwardMostMove;
13196 cm = (ChessMove) Myylex();
13198 if (cm == EndOfFile ||
13199 cm == GNUChessGame || cm == XBoardGame) {
13200 /* Empty game; pretend end-of-file and handle later */
13205 if (cm == MoveNumberOne || cm == PositionDiagram ||
13206 cm == PGNTag || cm == Comment)
13209 } else if (cm == GNUChessGame) {
13210 if (gameInfo.event != NULL) {
13211 free(gameInfo.event);
13213 gameInfo.event = StrSave(yy_text);
13216 startedFromSetupPosition = startedFromPositionFile; // [HGM]
13217 while (cm == PGNTag) {
13218 if (appData.debugMode)
13219 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13220 err = ParsePGNTag(yy_text, &gameInfo);
13221 if (!err) numPGNTags++;
13223 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13224 if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13225 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13226 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13227 InitPosition(TRUE);
13228 oldVariant = gameInfo.variant;
13229 if (appData.debugMode)
13230 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13234 if (gameInfo.fen != NULL) {
13235 Board initial_position;
13236 startedFromSetupPosition = TRUE;
13237 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13239 DisplayError(_("Bad FEN position in file"), 0);
13242 CopyBoard(boards[0], initial_position);
13243 if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13244 CopyBoard(initialPosition, initial_position);
13245 if (blackPlaysFirst) {
13246 currentMove = forwardMostMove = backwardMostMove = 1;
13247 CopyBoard(boards[1], initial_position);
13248 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13249 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13250 timeRemaining[0][1] = whiteTimeRemaining;
13251 timeRemaining[1][1] = blackTimeRemaining;
13252 if (commentList[0] != NULL) {
13253 commentList[1] = commentList[0];
13254 commentList[0] = NULL;
13257 currentMove = forwardMostMove = backwardMostMove = 0;
13259 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13261 initialRulePlies = FENrulePlies;
13262 for( i=0; i< nrCastlingRights; i++ )
13263 initialRights[i] = initial_position[CASTLING][i];
13265 yyboardindex = forwardMostMove;
13266 free(gameInfo.fen);
13267 gameInfo.fen = NULL;
13270 yyboardindex = forwardMostMove;
13271 cm = (ChessMove) Myylex();
13273 /* Handle comments interspersed among the tags */
13274 while (cm == Comment) {
13276 if (appData.debugMode)
13277 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13279 AppendComment(currentMove, p, FALSE);
13280 yyboardindex = forwardMostMove;
13281 cm = (ChessMove) Myylex();
13285 /* don't rely on existence of Event tag since if game was
13286 * pasted from clipboard the Event tag may not exist
13288 if (numPGNTags > 0){
13290 if (gameInfo.variant == VariantNormal) {
13291 VariantClass v = StringToVariant(gameInfo.event);
13292 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13293 if(v < VariantShogi) gameInfo.variant = v;
13296 if( appData.autoDisplayTags ) {
13297 tags = PGNTags(&gameInfo);
13298 TagsPopUp(tags, CmailMsg());
13303 /* Make something up, but don't display it now */
13308 if (cm == PositionDiagram) {
13311 Board initial_position;
13313 if (appData.debugMode)
13314 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13316 if (!startedFromSetupPosition) {
13318 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13319 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13330 initial_position[i][j++] = CharToPiece(*p);
13333 while (*p == ' ' || *p == '\t' ||
13334 *p == '\n' || *p == '\r') p++;
13336 if (strncmp(p, "black", strlen("black"))==0)
13337 blackPlaysFirst = TRUE;
13339 blackPlaysFirst = FALSE;
13340 startedFromSetupPosition = TRUE;
13342 CopyBoard(boards[0], initial_position);
13343 if (blackPlaysFirst) {
13344 currentMove = forwardMostMove = backwardMostMove = 1;
13345 CopyBoard(boards[1], initial_position);
13346 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13347 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13348 timeRemaining[0][1] = whiteTimeRemaining;
13349 timeRemaining[1][1] = blackTimeRemaining;
13350 if (commentList[0] != NULL) {
13351 commentList[1] = commentList[0];
13352 commentList[0] = NULL;
13355 currentMove = forwardMostMove = backwardMostMove = 0;
13358 yyboardindex = forwardMostMove;
13359 cm = (ChessMove) Myylex();
13362 if(!creatingBook) {
13363 if (first.pr == NoProc) {
13364 StartChessProgram(&first);
13366 InitChessProgram(&first, FALSE);
13367 if(gameInfo.variant == VariantUnknown && *oldName) {
13368 safeStrCpy(engineVariant, oldName, MSG_SIZ);
13369 gameInfo.variant = v;
13371 SendToProgram("force\n", &first);
13372 if (startedFromSetupPosition) {
13373 SendBoard(&first, forwardMostMove);
13374 if (appData.debugMode) {
13375 fprintf(debugFP, "Load Game\n");
13377 DisplayBothClocks();
13381 /* [HGM] server: flag to write setup moves in broadcast file as one */
13382 loadFlag = appData.suppressLoadMoves;
13384 while (cm == Comment) {
13386 if (appData.debugMode)
13387 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13389 AppendComment(currentMove, p, FALSE);
13390 yyboardindex = forwardMostMove;
13391 cm = (ChessMove) Myylex();
13394 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13395 cm == WhiteWins || cm == BlackWins ||
13396 cm == GameIsDrawn || cm == GameUnfinished) {
13397 DisplayMessage("", _("No moves in game"));
13398 if (cmailMsgLoaded) {
13399 if (appData.debugMode)
13400 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13404 DrawPosition(FALSE, boards[currentMove]);
13405 DisplayBothClocks();
13406 gameMode = EditGame;
13413 // [HGM] PV info: routine tests if comment empty
13414 if (!matchMode && (pausing || appData.timeDelay != 0)) {
13415 DisplayComment(currentMove - 1, commentList[currentMove]);
13417 if (!matchMode && appData.timeDelay != 0)
13418 DrawPosition(FALSE, boards[currentMove]);
13420 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13421 programStats.ok_to_send = 1;
13424 /* if the first token after the PGN tags is a move
13425 * and not move number 1, retrieve it from the parser
13427 if (cm != MoveNumberOne)
13428 LoadGameOneMove(cm);
13430 /* load the remaining moves from the file */
13431 while (LoadGameOneMove(EndOfFile)) {
13432 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13433 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13436 /* rewind to the start of the game */
13437 currentMove = backwardMostMove;
13439 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13441 if (oldGameMode == AnalyzeFile) {
13442 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13443 AnalyzeFileEvent();
13445 if (oldGameMode == AnalyzeMode) {
13446 AnalyzeFileEvent();
13449 if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13450 long int w, b; // [HGM] adjourn: restore saved clock times
13451 char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13452 if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13453 timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13454 timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13458 if(creatingBook) return TRUE;
13459 if (!matchMode && pos > 0) {
13460 ToNrEvent(pos); // [HGM] no autoplay if selected on position
13462 if (matchMode || appData.timeDelay == 0) {
13464 } else if (appData.timeDelay > 0) {
13465 AutoPlayGameLoop();
13468 if (appData.debugMode)
13469 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13471 loadFlag = 0; /* [HGM] true game starts */
13475 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13477 ReloadPosition (int offset)
13479 int positionNumber = lastLoadPositionNumber + offset;
13480 if (lastLoadPositionFP == NULL) {
13481 DisplayError(_("No position has been loaded yet"), 0);
13484 if (positionNumber <= 0) {
13485 DisplayError(_("Can't back up any further"), 0);
13488 return LoadPosition(lastLoadPositionFP, positionNumber,
13489 lastLoadPositionTitle);
13492 /* Load the nth position from the given file */
13494 LoadPositionFromFile (char *filename, int n, char *title)
13499 if (strcmp(filename, "-") == 0) {
13500 return LoadPosition(stdin, n, "stdin");
13502 f = fopen(filename, "rb");
13504 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13505 DisplayError(buf, errno);
13508 return LoadPosition(f, n, title);
13513 /* Load the nth position from the given open file, and close it */
13515 LoadPosition (FILE *f, int positionNumber, char *title)
13517 char *p, line[MSG_SIZ];
13518 Board initial_position;
13519 int i, j, fenMode, pn;
13521 if (gameMode == Training )
13522 SetTrainingModeOff();
13524 if (gameMode != BeginningOfGame) {
13525 Reset(FALSE, TRUE);
13527 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13528 fclose(lastLoadPositionFP);
13530 if (positionNumber == 0) positionNumber = 1;
13531 lastLoadPositionFP = f;
13532 lastLoadPositionNumber = positionNumber;
13533 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13534 if (first.pr == NoProc && !appData.noChessProgram) {
13535 StartChessProgram(&first);
13536 InitChessProgram(&first, FALSE);
13538 pn = positionNumber;
13539 if (positionNumber < 0) {
13540 /* Negative position number means to seek to that byte offset */
13541 if (fseek(f, -positionNumber, 0) == -1) {
13542 DisplayError(_("Can't seek on position file"), 0);
13547 if (fseek(f, 0, 0) == -1) {
13548 if (f == lastLoadPositionFP ?
13549 positionNumber == lastLoadPositionNumber + 1 :
13550 positionNumber == 1) {
13553 DisplayError(_("Can't seek on position file"), 0);
13558 /* See if this file is FEN or old-style xboard */
13559 if (fgets(line, MSG_SIZ, f) == NULL) {
13560 DisplayError(_("Position not found in file"), 0);
13563 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13564 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13567 if (fenMode || line[0] == '#') pn--;
13569 /* skip positions before number pn */
13570 if (fgets(line, MSG_SIZ, f) == NULL) {
13572 DisplayError(_("Position not found in file"), 0);
13575 if (fenMode || line[0] == '#') pn--;
13581 if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13582 DisplayError(_("Bad FEN position in file"), 0);
13585 if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13586 sscanf(p+4, "%[^;]", bestMove);
13587 } else *bestMove = NULLCHAR;
13588 if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13589 sscanf(p+4, "%[^;]", avoidMove);
13590 } else *avoidMove = NULLCHAR;
13592 (void) fgets(line, MSG_SIZ, f);
13593 (void) fgets(line, MSG_SIZ, f);
13595 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13596 (void) fgets(line, MSG_SIZ, f);
13597 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13600 initial_position[i][j++] = CharToPiece(*p);
13604 blackPlaysFirst = FALSE;
13606 (void) fgets(line, MSG_SIZ, f);
13607 if (strncmp(line, "black", strlen("black"))==0)
13608 blackPlaysFirst = TRUE;
13611 startedFromSetupPosition = TRUE;
13613 CopyBoard(boards[0], initial_position);
13614 if (blackPlaysFirst) {
13615 currentMove = forwardMostMove = backwardMostMove = 1;
13616 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13617 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13618 CopyBoard(boards[1], initial_position);
13619 DisplayMessage("", _("Black to play"));
13621 currentMove = forwardMostMove = backwardMostMove = 0;
13622 DisplayMessage("", _("White to play"));
13624 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13625 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13626 SendToProgram("force\n", &first);
13627 SendBoard(&first, forwardMostMove);
13629 if (appData.debugMode) {
13631 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13632 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13633 fprintf(debugFP, "Load Position\n");
13636 if (positionNumber > 1) {
13637 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13638 DisplayTitle(line);
13640 DisplayTitle(title);
13642 gameMode = EditGame;
13645 timeRemaining[0][1] = whiteTimeRemaining;
13646 timeRemaining[1][1] = blackTimeRemaining;
13647 DrawPosition(FALSE, boards[currentMove]);
13654 CopyPlayerNameIntoFileName (char **dest, char *src)
13656 while (*src != NULLCHAR && *src != ',') {
13661 *(*dest)++ = *src++;
13667 DefaultFileName (char *ext)
13669 static char def[MSG_SIZ];
13672 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13674 CopyPlayerNameIntoFileName(&p, gameInfo.white);
13676 CopyPlayerNameIntoFileName(&p, gameInfo.black);
13678 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13685 /* Save the current game to the given file */
13687 SaveGameToFile (char *filename, int append)
13691 int result, i, t,tot=0;
13693 if (strcmp(filename, "-") == 0) {
13694 return SaveGame(stdout, 0, NULL);
13696 for(i=0; i<10; i++) { // upto 10 tries
13697 f = fopen(filename, append ? "a" : "w");
13698 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13699 if(f || errno != 13) break;
13700 DoSleep(t = 5 + random()%11); // wait 5-15 msec
13704 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13705 DisplayError(buf, errno);
13708 safeStrCpy(buf, lastMsg, MSG_SIZ);
13709 DisplayMessage(_("Waiting for access to save file"), "");
13710 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13711 DisplayMessage(_("Saving game"), "");
13712 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
13713 result = SaveGame(f, 0, NULL);
13714 DisplayMessage(buf, "");
13721 SavePart (char *str)
13723 static char buf[MSG_SIZ];
13726 p = strchr(str, ' ');
13727 if (p == NULL) return str;
13728 strncpy(buf, str, p - str);
13729 buf[p - str] = NULLCHAR;
13733 #define PGN_MAX_LINE 75
13735 #define PGN_SIDE_WHITE 0
13736 #define PGN_SIDE_BLACK 1
13739 FindFirstMoveOutOfBook (int side)
13743 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13744 int index = backwardMostMove;
13745 int has_book_hit = 0;
13747 if( (index % 2) != side ) {
13751 while( index < forwardMostMove ) {
13752 /* Check to see if engine is in book */
13753 int depth = pvInfoList[index].depth;
13754 int score = pvInfoList[index].score;
13760 else if( score == 0 && depth == 63 ) {
13761 in_book = 1; /* Zappa */
13763 else if( score == 2 && depth == 99 ) {
13764 in_book = 1; /* Abrok */
13767 has_book_hit += in_book;
13783 GetOutOfBookInfo (char * buf)
13787 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13789 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13790 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13794 if( oob[0] >= 0 || oob[1] >= 0 ) {
13795 for( i=0; i<2; i++ ) {
13799 if( i > 0 && oob[0] >= 0 ) {
13800 strcat( buf, " " );
13803 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13804 sprintf( buf+strlen(buf), "%s%.2f",
13805 pvInfoList[idx].score >= 0 ? "+" : "",
13806 pvInfoList[idx].score / 100.0 );
13812 /* Save game in PGN style */
13814 SaveGamePGN2 (FILE *f)
13816 int i, offset, linelen, newblock;
13819 int movelen, numlen, blank;
13820 char move_buffer[100]; /* [AS] Buffer for move+PV info */
13822 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13824 PrintPGNTags(f, &gameInfo);
13826 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13828 if (backwardMostMove > 0 || startedFromSetupPosition) {
13829 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13830 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13831 fprintf(f, "\n{--------------\n");
13832 PrintPosition(f, backwardMostMove);
13833 fprintf(f, "--------------}\n");
13837 /* [AS] Out of book annotation */
13838 if( appData.saveOutOfBookInfo ) {
13841 GetOutOfBookInfo( buf );
13843 if( buf[0] != '\0' ) {
13844 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13851 i = backwardMostMove;
13855 while (i < forwardMostMove) {
13856 /* Print comments preceding this move */
13857 if (commentList[i] != NULL) {
13858 if (linelen > 0) fprintf(f, "\n");
13859 fprintf(f, "%s", commentList[i]);
13864 /* Format move number */
13866 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13869 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13871 numtext[0] = NULLCHAR;
13873 numlen = strlen(numtext);
13876 /* Print move number */
13877 blank = linelen > 0 && numlen > 0;
13878 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13887 fprintf(f, "%s", numtext);
13891 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13892 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13895 blank = linelen > 0 && movelen > 0;
13896 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13905 fprintf(f, "%s", move_buffer);
13906 linelen += movelen;
13908 /* [AS] Add PV info if present */
13909 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13910 /* [HGM] add time */
13911 char buf[MSG_SIZ]; int seconds;
13913 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13919 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13922 seconds = (seconds + 4)/10; // round to full seconds
13924 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13926 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13929 if(appData.cumulativeTimePGN) {
13930 snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
13933 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13934 pvInfoList[i].score >= 0 ? "+" : "",
13935 pvInfoList[i].score / 100.0,
13936 pvInfoList[i].depth,
13939 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13941 /* Print score/depth */
13942 blank = linelen > 0 && movelen > 0;
13943 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13952 fprintf(f, "%s", move_buffer);
13953 linelen += movelen;
13959 /* Start a new line */
13960 if (linelen > 0) fprintf(f, "\n");
13962 /* Print comments after last move */
13963 if (commentList[i] != NULL) {
13964 fprintf(f, "%s\n", commentList[i]);
13968 if (gameInfo.resultDetails != NULL &&
13969 gameInfo.resultDetails[0] != NULLCHAR) {
13970 char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13971 if(gameInfo.result == GameUnfinished && appData.clockMode &&
13972 (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13973 snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13974 fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13976 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13980 /* Save game in PGN style and close the file */
13982 SaveGamePGN (FILE *f)
13986 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13990 /* Save game in old style and close the file */
13992 SaveGameOldStyle (FILE *f)
13997 tm = time((time_t *) NULL);
13999 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
14002 if (backwardMostMove > 0 || startedFromSetupPosition) {
14003 fprintf(f, "\n[--------------\n");
14004 PrintPosition(f, backwardMostMove);
14005 fprintf(f, "--------------]\n");
14010 i = backwardMostMove;
14011 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14013 while (i < forwardMostMove) {
14014 if (commentList[i] != NULL) {
14015 fprintf(f, "[%s]\n", commentList[i]);
14018 if ((i % 2) == 1) {
14019 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
14022 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
14024 if (commentList[i] != NULL) {
14028 if (i >= forwardMostMove) {
14032 fprintf(f, "%s\n", parseList[i]);
14037 if (commentList[i] != NULL) {
14038 fprintf(f, "[%s]\n", commentList[i]);
14041 /* This isn't really the old style, but it's close enough */
14042 if (gameInfo.resultDetails != NULL &&
14043 gameInfo.resultDetails[0] != NULLCHAR) {
14044 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14045 gameInfo.resultDetails);
14047 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14054 /* Save the current game to open file f and close the file */
14056 SaveGame (FILE *f, int dummy, char *dummy2)
14058 if (gameMode == EditPosition) EditPositionDone(TRUE);
14059 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14060 if (appData.oldSaveStyle)
14061 return SaveGameOldStyle(f);
14063 return SaveGamePGN(f);
14066 /* Save the current position to the given file */
14068 SavePositionToFile (char *filename)
14073 if (strcmp(filename, "-") == 0) {
14074 return SavePosition(stdout, 0, NULL);
14076 f = fopen(filename, "a");
14078 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14079 DisplayError(buf, errno);
14082 safeStrCpy(buf, lastMsg, MSG_SIZ);
14083 DisplayMessage(_("Waiting for access to save file"), "");
14084 flock(fileno(f), LOCK_EX); // [HGM] lock
14085 DisplayMessage(_("Saving position"), "");
14086 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
14087 SavePosition(f, 0, NULL);
14088 DisplayMessage(buf, "");
14094 /* Save the current position to the given open file and close the file */
14096 SavePosition (FILE *f, int dummy, char *dummy2)
14101 if (gameMode == EditPosition) EditPositionDone(TRUE);
14102 if (appData.oldSaveStyle) {
14103 tm = time((time_t *) NULL);
14105 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14107 fprintf(f, "[--------------\n");
14108 PrintPosition(f, currentMove);
14109 fprintf(f, "--------------]\n");
14111 fen = PositionToFEN(currentMove, NULL, 1);
14112 fprintf(f, "%s\n", fen);
14120 ReloadCmailMsgEvent (int unregister)
14123 static char *inFilename = NULL;
14124 static char *outFilename;
14126 struct stat inbuf, outbuf;
14129 /* Any registered moves are unregistered if unregister is set, */
14130 /* i.e. invoked by the signal handler */
14132 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14133 cmailMoveRegistered[i] = FALSE;
14134 if (cmailCommentList[i] != NULL) {
14135 free(cmailCommentList[i]);
14136 cmailCommentList[i] = NULL;
14139 nCmailMovesRegistered = 0;
14142 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14143 cmailResult[i] = CMAIL_NOT_RESULT;
14147 if (inFilename == NULL) {
14148 /* Because the filenames are static they only get malloced once */
14149 /* and they never get freed */
14150 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14151 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14153 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14154 sprintf(outFilename, "%s.out", appData.cmailGameName);
14157 status = stat(outFilename, &outbuf);
14159 cmailMailedMove = FALSE;
14161 status = stat(inFilename, &inbuf);
14162 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14165 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14166 counts the games, notes how each one terminated, etc.
14168 It would be nice to remove this kludge and instead gather all
14169 the information while building the game list. (And to keep it
14170 in the game list nodes instead of having a bunch of fixed-size
14171 parallel arrays.) Note this will require getting each game's
14172 termination from the PGN tags, as the game list builder does
14173 not process the game moves. --mann
14175 cmailMsgLoaded = TRUE;
14176 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14178 /* Load first game in the file or popup game menu */
14179 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14181 #endif /* !WIN32 */
14189 char string[MSG_SIZ];
14191 if ( cmailMailedMove
14192 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14193 return TRUE; /* Allow free viewing */
14196 /* Unregister move to ensure that we don't leave RegisterMove */
14197 /* with the move registered when the conditions for registering no */
14199 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14200 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14201 nCmailMovesRegistered --;
14203 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14205 free(cmailCommentList[lastLoadGameNumber - 1]);
14206 cmailCommentList[lastLoadGameNumber - 1] = NULL;
14210 if (cmailOldMove == -1) {
14211 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14215 if (currentMove > cmailOldMove + 1) {
14216 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14220 if (currentMove < cmailOldMove) {
14221 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14225 if (forwardMostMove > currentMove) {
14226 /* Silently truncate extra moves */
14230 if ( (currentMove == cmailOldMove + 1)
14231 || ( (currentMove == cmailOldMove)
14232 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14233 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14234 if (gameInfo.result != GameUnfinished) {
14235 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14238 if (commentList[currentMove] != NULL) {
14239 cmailCommentList[lastLoadGameNumber - 1]
14240 = StrSave(commentList[currentMove]);
14242 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14244 if (appData.debugMode)
14245 fprintf(debugFP, "Saving %s for game %d\n",
14246 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14248 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14250 f = fopen(string, "w");
14251 if (appData.oldSaveStyle) {
14252 SaveGameOldStyle(f); /* also closes the file */
14254 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14255 f = fopen(string, "w");
14256 SavePosition(f, 0, NULL); /* also closes the file */
14258 fprintf(f, "{--------------\n");
14259 PrintPosition(f, currentMove);
14260 fprintf(f, "--------------}\n\n");
14262 SaveGame(f, 0, NULL); /* also closes the file*/
14265 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14266 nCmailMovesRegistered ++;
14267 } else if (nCmailGames == 1) {
14268 DisplayError(_("You have not made a move yet"), 0);
14279 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14280 FILE *commandOutput;
14281 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14282 int nBytes = 0; /* Suppress warnings on uninitialized variables */
14288 if (! cmailMsgLoaded) {
14289 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14293 if (nCmailGames == nCmailResults) {
14294 DisplayError(_("No unfinished games"), 0);
14298 #if CMAIL_PROHIBIT_REMAIL
14299 if (cmailMailedMove) {
14300 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);
14301 DisplayError(msg, 0);
14306 if (! (cmailMailedMove || RegisterMove())) return;
14308 if ( cmailMailedMove
14309 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14310 snprintf(string, MSG_SIZ, partCommandString,
14311 appData.debugMode ? " -v" : "", appData.cmailGameName);
14312 commandOutput = popen(string, "r");
14314 if (commandOutput == NULL) {
14315 DisplayError(_("Failed to invoke cmail"), 0);
14317 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14318 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14320 if (nBuffers > 1) {
14321 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14322 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14323 nBytes = MSG_SIZ - 1;
14325 (void) memcpy(msg, buffer, nBytes);
14327 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14329 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14330 cmailMailedMove = TRUE; /* Prevent >1 moves */
14333 for (i = 0; i < nCmailGames; i ++) {
14334 if (cmailResult[i] == CMAIL_NOT_RESULT) {
14339 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14341 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14343 appData.cmailGameName,
14345 LoadGameFromFile(buffer, 1, buffer, FALSE);
14346 cmailMsgLoaded = FALSE;
14350 DisplayInformation(msg);
14351 pclose(commandOutput);
14354 if ((*cmailMsg) != '\0') {
14355 DisplayInformation(cmailMsg);
14360 #endif /* !WIN32 */
14369 int prependComma = 0;
14371 char string[MSG_SIZ]; /* Space for game-list */
14374 if (!cmailMsgLoaded) return "";
14376 if (cmailMailedMove) {
14377 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14379 /* Create a list of games left */
14380 snprintf(string, MSG_SIZ, "[");
14381 for (i = 0; i < nCmailGames; i ++) {
14382 if (! ( cmailMoveRegistered[i]
14383 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14384 if (prependComma) {
14385 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14387 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14391 strcat(string, number);
14394 strcat(string, "]");
14396 if (nCmailMovesRegistered + nCmailResults == 0) {
14397 switch (nCmailGames) {
14399 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14403 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14407 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14412 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14414 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14419 if (nCmailResults == nCmailGames) {
14420 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14422 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14427 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14439 if (gameMode == Training)
14440 SetTrainingModeOff();
14443 cmailMsgLoaded = FALSE;
14444 if (appData.icsActive) {
14445 SendToICS(ics_prefix);
14446 SendToICS("refresh\n");
14451 ExitEvent (int status)
14455 /* Give up on clean exit */
14459 /* Keep trying for clean exit */
14463 if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14464 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14466 if (telnetISR != NULL) {
14467 RemoveInputSource(telnetISR);
14469 if (icsPR != NoProc) {
14470 DestroyChildProcess(icsPR, TRUE);
14473 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14474 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14476 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14477 /* make sure this other one finishes before killing it! */
14478 if(endingGame) { int count = 0;
14479 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14480 while(endingGame && count++ < 10) DoSleep(1);
14481 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14484 /* Kill off chess programs */
14485 if (first.pr != NoProc) {
14488 DoSleep( appData.delayBeforeQuit );
14489 SendToProgram("quit\n", &first);
14490 DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14492 if (second.pr != NoProc) {
14493 DoSleep( appData.delayBeforeQuit );
14494 SendToProgram("quit\n", &second);
14495 DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14497 if (first.isr != NULL) {
14498 RemoveInputSource(first.isr);
14500 if (second.isr != NULL) {
14501 RemoveInputSource(second.isr);
14504 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14505 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14507 ShutDownFrontEnd();
14512 PauseEngine (ChessProgramState *cps)
14514 SendToProgram("pause\n", cps);
14519 UnPauseEngine (ChessProgramState *cps)
14521 SendToProgram("resume\n", cps);
14528 if (appData.debugMode)
14529 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14533 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14535 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14536 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14537 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14539 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14540 HandleMachineMove(stashedInputMove, stalledEngine);
14541 stalledEngine = NULL;
14544 if (gameMode == MachinePlaysWhite ||
14545 gameMode == TwoMachinesPlay ||
14546 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14547 if(first.pause) UnPauseEngine(&first);
14548 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14549 if(second.pause) UnPauseEngine(&second);
14550 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14553 DisplayBothClocks();
14555 if (gameMode == PlayFromGameFile) {
14556 if (appData.timeDelay >= 0)
14557 AutoPlayGameLoop();
14558 } else if (gameMode == IcsExamining && pauseExamInvalid) {
14559 Reset(FALSE, TRUE);
14560 SendToICS(ics_prefix);
14561 SendToICS("refresh\n");
14562 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14563 ForwardInner(forwardMostMove);
14565 pauseExamInvalid = FALSE;
14567 switch (gameMode) {
14571 pauseExamForwardMostMove = forwardMostMove;
14572 pauseExamInvalid = FALSE;
14575 case IcsPlayingWhite:
14576 case IcsPlayingBlack:
14580 case PlayFromGameFile:
14581 (void) StopLoadGameTimer();
14585 case BeginningOfGame:
14586 if (appData.icsActive) return;
14587 /* else fall through */
14588 case MachinePlaysWhite:
14589 case MachinePlaysBlack:
14590 case TwoMachinesPlay:
14591 if (forwardMostMove == 0)
14592 return; /* don't pause if no one has moved */
14593 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14594 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14595 if(onMove->pause) { // thinking engine can be paused
14596 PauseEngine(onMove); // do it
14597 if(onMove->other->pause) // pondering opponent can always be paused immediately
14598 PauseEngine(onMove->other);
14600 SendToProgram("easy\n", onMove->other);
14602 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14603 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14605 PauseEngine(&first);
14607 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14608 } else { // human on move, pause pondering by either method
14610 PauseEngine(&first);
14611 else if(appData.ponderNextMove)
14612 SendToProgram("easy\n", &first);
14615 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14625 EditCommentEvent ()
14627 char title[MSG_SIZ];
14629 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14630 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14632 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14633 WhiteOnMove(currentMove - 1) ? " " : ".. ",
14634 parseList[currentMove - 1]);
14637 EditCommentPopUp(currentMove, title, commentList[currentMove]);
14644 char *tags = PGNTags(&gameInfo);
14646 EditTagsPopUp(tags, NULL);
14653 if(second.analyzing) {
14654 SendToProgram("exit\n", &second);
14655 second.analyzing = FALSE;
14657 if (second.pr == NoProc) StartChessProgram(&second);
14658 InitChessProgram(&second, FALSE);
14659 FeedMovesToProgram(&second, currentMove);
14661 SendToProgram("analyze\n", &second);
14662 second.analyzing = TRUE;
14666 /* Toggle ShowThinking */
14668 ToggleShowThinking()
14670 appData.showThinking = !appData.showThinking;
14671 ShowThinkingEvent();
14675 AnalyzeModeEvent ()
14679 if (!first.analysisSupport) {
14680 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14681 DisplayError(buf, 0);
14684 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14685 if (appData.icsActive) {
14686 if (gameMode != IcsObserving) {
14687 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14688 DisplayError(buf, 0);
14690 if (appData.icsEngineAnalyze) {
14691 if (appData.debugMode)
14692 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14698 /* if enable, user wants to disable icsEngineAnalyze */
14699 if (appData.icsEngineAnalyze) {
14704 appData.icsEngineAnalyze = TRUE;
14705 if (appData.debugMode)
14706 fprintf(debugFP, "ICS engine analyze starting... \n");
14709 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14710 if (appData.noChessProgram || gameMode == AnalyzeMode)
14713 if (gameMode != AnalyzeFile) {
14714 if (!appData.icsEngineAnalyze) {
14716 if (gameMode != EditGame) return 0;
14718 if (!appData.showThinking) ToggleShowThinking();
14719 ResurrectChessProgram();
14720 SendToProgram("analyze\n", &first);
14721 first.analyzing = TRUE;
14722 /*first.maybeThinking = TRUE;*/
14723 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14724 EngineOutputPopUp();
14726 if (!appData.icsEngineAnalyze) {
14727 gameMode = AnalyzeMode;
14728 ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14734 StartAnalysisClock();
14735 GetTimeMark(&lastNodeCountTime);
14741 AnalyzeFileEvent ()
14743 if (appData.noChessProgram || gameMode == AnalyzeFile)
14746 if (!first.analysisSupport) {
14748 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14749 DisplayError(buf, 0);
14753 if (gameMode != AnalyzeMode) {
14754 keepInfo = 1; // mere annotating should not alter PGN tags
14757 if (gameMode != EditGame) return;
14758 if (!appData.showThinking) ToggleShowThinking();
14759 ResurrectChessProgram();
14760 SendToProgram("analyze\n", &first);
14761 first.analyzing = TRUE;
14762 /*first.maybeThinking = TRUE;*/
14763 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14764 EngineOutputPopUp();
14766 gameMode = AnalyzeFile;
14770 StartAnalysisClock();
14771 GetTimeMark(&lastNodeCountTime);
14773 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14774 AnalysisPeriodicEvent(1);
14778 MachineWhiteEvent ()
14781 char *bookHit = NULL;
14783 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14787 if (gameMode == PlayFromGameFile ||
14788 gameMode == TwoMachinesPlay ||
14789 gameMode == Training ||
14790 gameMode == AnalyzeMode ||
14791 gameMode == EndOfGame)
14794 if (gameMode == EditPosition)
14795 EditPositionDone(TRUE);
14797 if (!WhiteOnMove(currentMove)) {
14798 DisplayError(_("It is not White's turn"), 0);
14802 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14805 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14806 gameMode == AnalyzeFile)
14809 ResurrectChessProgram(); /* in case it isn't running */
14810 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14811 gameMode = MachinePlaysWhite;
14814 gameMode = MachinePlaysWhite;
14818 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14820 if (first.sendName) {
14821 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14822 SendToProgram(buf, &first);
14824 if (first.sendTime) {
14825 if (first.useColors) {
14826 SendToProgram("black\n", &first); /*gnu kludge*/
14828 SendTimeRemaining(&first, TRUE);
14830 if (first.useColors) {
14831 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14833 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14834 SetMachineThinkingEnables();
14835 first.maybeThinking = TRUE;
14839 if (appData.autoFlipView && !flipView) {
14840 flipView = !flipView;
14841 DrawPosition(FALSE, NULL);
14842 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14845 if(bookHit) { // [HGM] book: simulate book reply
14846 static char bookMove[MSG_SIZ]; // a bit generous?
14848 programStats.nodes = programStats.depth = programStats.time =
14849 programStats.score = programStats.got_only_move = 0;
14850 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14852 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14853 strcat(bookMove, bookHit);
14854 HandleMachineMove(bookMove, &first);
14859 MachineBlackEvent ()
14862 char *bookHit = NULL;
14864 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14868 if (gameMode == PlayFromGameFile ||
14869 gameMode == TwoMachinesPlay ||
14870 gameMode == Training ||
14871 gameMode == AnalyzeMode ||
14872 gameMode == EndOfGame)
14875 if (gameMode == EditPosition)
14876 EditPositionDone(TRUE);
14878 if (WhiteOnMove(currentMove)) {
14879 DisplayError(_("It is not Black's turn"), 0);
14883 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14886 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14887 gameMode == AnalyzeFile)
14890 ResurrectChessProgram(); /* in case it isn't running */
14891 gameMode = MachinePlaysBlack;
14895 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14897 if (first.sendName) {
14898 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14899 SendToProgram(buf, &first);
14901 if (first.sendTime) {
14902 if (first.useColors) {
14903 SendToProgram("white\n", &first); /*gnu kludge*/
14905 SendTimeRemaining(&first, FALSE);
14907 if (first.useColors) {
14908 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14910 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14911 SetMachineThinkingEnables();
14912 first.maybeThinking = TRUE;
14915 if (appData.autoFlipView && flipView) {
14916 flipView = !flipView;
14917 DrawPosition(FALSE, NULL);
14918 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14920 if(bookHit) { // [HGM] book: simulate book reply
14921 static char bookMove[MSG_SIZ]; // a bit generous?
14923 programStats.nodes = programStats.depth = programStats.time =
14924 programStats.score = programStats.got_only_move = 0;
14925 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14927 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14928 strcat(bookMove, bookHit);
14929 HandleMachineMove(bookMove, &first);
14935 DisplayTwoMachinesTitle ()
14938 if (appData.matchGames > 0) {
14939 if(appData.tourneyFile[0]) {
14940 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14941 gameInfo.white, _("vs."), gameInfo.black,
14942 nextGame+1, appData.matchGames+1,
14943 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14945 if (first.twoMachinesColor[0] == 'w') {
14946 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14947 gameInfo.white, _("vs."), gameInfo.black,
14948 first.matchWins, second.matchWins,
14949 matchGame - 1 - (first.matchWins + second.matchWins));
14951 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14952 gameInfo.white, _("vs."), gameInfo.black,
14953 second.matchWins, first.matchWins,
14954 matchGame - 1 - (first.matchWins + second.matchWins));
14957 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14963 SettingsMenuIfReady ()
14965 if (second.lastPing != second.lastPong) {
14966 DisplayMessage("", _("Waiting for second chess program"));
14967 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14971 DisplayMessage("", "");
14972 SettingsPopUp(&second);
14976 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14979 if (cps->pr == NoProc) {
14980 StartChessProgram(cps);
14981 if (cps->protocolVersion == 1) {
14983 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14985 /* kludge: allow timeout for initial "feature" command */
14986 if(retry != TwoMachinesEventIfReady) FreezeUI();
14987 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14988 DisplayMessage("", buf);
14989 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14997 TwoMachinesEvent P((void))
15001 ChessProgramState *onmove;
15002 char *bookHit = NULL;
15003 static int stalling = 0;
15007 if (appData.noChessProgram) return;
15009 switch (gameMode) {
15010 case TwoMachinesPlay:
15012 case MachinePlaysWhite:
15013 case MachinePlaysBlack:
15014 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15015 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15019 case BeginningOfGame:
15020 case PlayFromGameFile:
15023 if (gameMode != EditGame) return;
15026 EditPositionDone(TRUE);
15037 // forwardMostMove = currentMove;
15038 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15039 startingEngine = TRUE;
15041 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15043 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15044 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15045 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15049 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15051 if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15052 gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15053 startingEngine = matchMode = FALSE;
15054 DisplayError("second engine does not play this", 0);
15055 gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15056 EditGameEvent(); // switch back to EditGame mode
15061 InitChessProgram(&second, FALSE); // unbalances ping of second engine
15062 SendToProgram("force\n", &second);
15064 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15068 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15069 if(appData.matchPause>10000 || appData.matchPause<10)
15070 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15071 wait = SubtractTimeMarks(&now, &pauseStart);
15072 if(wait < appData.matchPause) {
15073 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15076 // we are now committed to starting the game
15078 DisplayMessage("", "");
15080 if (startedFromSetupPosition) {
15081 SendBoard(&second, backwardMostMove);
15082 if (appData.debugMode) {
15083 fprintf(debugFP, "Two Machines\n");
15086 for (i = backwardMostMove; i < forwardMostMove; i++) {
15087 SendMoveToProgram(i, &second);
15091 gameMode = TwoMachinesPlay;
15092 pausing = startingEngine = FALSE;
15093 ModeHighlight(); // [HGM] logo: this triggers display update of logos
15095 DisplayTwoMachinesTitle();
15097 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15102 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15103 SendToProgram(first.computerString, &first);
15104 if (first.sendName) {
15105 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15106 SendToProgram(buf, &first);
15109 SendToProgram(second.computerString, &second);
15110 if (second.sendName) {
15111 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15112 SendToProgram(buf, &second);
15117 if (!first.sendTime || !second.sendTime) {
15118 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15119 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15121 if (onmove->sendTime) {
15122 if (onmove->useColors) {
15123 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15125 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15127 if (onmove->useColors) {
15128 SendToProgram(onmove->twoMachinesColor, onmove);
15130 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15131 // SendToProgram("go\n", onmove);
15132 onmove->maybeThinking = TRUE;
15133 SetMachineThinkingEnables();
15137 if(bookHit) { // [HGM] book: simulate book reply
15138 static char bookMove[MSG_SIZ]; // a bit generous?
15140 programStats.nodes = programStats.depth = programStats.time =
15141 programStats.score = programStats.got_only_move = 0;
15142 sprintf(programStats.movelist, "%s (xbook)", bookHit);
15144 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15145 strcat(bookMove, bookHit);
15146 savedMessage = bookMove; // args for deferred call
15147 savedState = onmove;
15148 ScheduleDelayedEvent(DeferredBookMove, 1);
15155 if (gameMode == Training) {
15156 SetTrainingModeOff();
15157 gameMode = PlayFromGameFile;
15158 DisplayMessage("", _("Training mode off"));
15160 gameMode = Training;
15161 animateTraining = appData.animate;
15163 /* make sure we are not already at the end of the game */
15164 if (currentMove < forwardMostMove) {
15165 SetTrainingModeOn();
15166 DisplayMessage("", _("Training mode on"));
15168 gameMode = PlayFromGameFile;
15169 DisplayError(_("Already at end of game"), 0);
15178 if (!appData.icsActive) return;
15179 switch (gameMode) {
15180 case IcsPlayingWhite:
15181 case IcsPlayingBlack:
15184 case BeginningOfGame:
15192 EditPositionDone(TRUE);
15205 gameMode = IcsIdle;
15215 switch (gameMode) {
15217 SetTrainingModeOff();
15219 case MachinePlaysWhite:
15220 case MachinePlaysBlack:
15221 case BeginningOfGame:
15222 SendToProgram("force\n", &first);
15223 if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15224 if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15226 abortEngineThink = TRUE;
15227 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15228 SendToProgram(buf, &first);
15229 DisplayMessage("Aborting engine think", "");
15233 SetUserThinkingEnables();
15235 case PlayFromGameFile:
15236 (void) StopLoadGameTimer();
15237 if (gameFileFP != NULL) {
15242 EditPositionDone(TRUE);
15247 SendToProgram("force\n", &first);
15249 case TwoMachinesPlay:
15250 GameEnds(EndOfFile, NULL, GE_PLAYER);
15251 ResurrectChessProgram();
15252 SetUserThinkingEnables();
15255 ResurrectChessProgram();
15257 case IcsPlayingBlack:
15258 case IcsPlayingWhite:
15259 DisplayError(_("Warning: You are still playing a game"), 0);
15262 DisplayError(_("Warning: You are still observing a game"), 0);
15265 DisplayError(_("Warning: You are still examining a game"), 0);
15276 first.offeredDraw = second.offeredDraw = 0;
15278 if (gameMode == PlayFromGameFile) {
15279 whiteTimeRemaining = timeRemaining[0][currentMove];
15280 blackTimeRemaining = timeRemaining[1][currentMove];
15284 if (gameMode == MachinePlaysWhite ||
15285 gameMode == MachinePlaysBlack ||
15286 gameMode == TwoMachinesPlay ||
15287 gameMode == EndOfGame) {
15288 i = forwardMostMove;
15289 while (i > currentMove) {
15290 SendToProgram("undo\n", &first);
15293 if(!adjustedClock) {
15294 whiteTimeRemaining = timeRemaining[0][currentMove];
15295 blackTimeRemaining = timeRemaining[1][currentMove];
15296 DisplayBothClocks();
15298 if (whiteFlag || blackFlag) {
15299 whiteFlag = blackFlag = 0;
15304 gameMode = EditGame;
15310 EditPositionEvent ()
15313 if (gameMode == EditPosition) {
15319 if (gameMode != EditGame) return;
15321 gameMode = EditPosition;
15324 CopyBoard(rightsBoard, nullBoard);
15325 if (currentMove > 0)
15326 CopyBoard(boards[0], boards[currentMove]);
15327 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15328 rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15330 blackPlaysFirst = !WhiteOnMove(currentMove);
15332 currentMove = forwardMostMove = backwardMostMove = 0;
15333 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15335 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15341 /* [DM] icsEngineAnalyze - possible call from other functions */
15342 if (appData.icsEngineAnalyze) {
15343 appData.icsEngineAnalyze = FALSE;
15345 DisplayMessage("",_("Close ICS engine analyze..."));
15347 if (first.analysisSupport && first.analyzing) {
15348 SendToBoth("exit\n");
15349 first.analyzing = second.analyzing = FALSE;
15351 thinkOutput[0] = NULLCHAR;
15355 EditPositionDone (Boolean fakeRights)
15357 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15359 startedFromSetupPosition = TRUE;
15360 InitChessProgram(&first, FALSE);
15361 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15363 boards[0][EP_STATUS] = EP_NONE;
15364 for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
15365 for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
15366 if(rightsBoard[r][f]) {
15367 ChessSquare p = boards[0][r][f];
15368 if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
15369 else if(p == king) boards[0][CASTLING][2] = f;
15370 else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
15371 else rightsBoard[r][f] = 2; // mark for second pass
15374 for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
15375 if(rightsBoard[r][f] == 2) {
15376 ChessSquare p = boards[0][r][f];
15377 if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
15378 if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
15382 SendToProgram("force\n", &first);
15383 if (blackPlaysFirst) {
15384 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15385 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15386 currentMove = forwardMostMove = backwardMostMove = 1;
15387 CopyBoard(boards[1], boards[0]);
15389 currentMove = forwardMostMove = backwardMostMove = 0;
15391 SendBoard(&first, forwardMostMove);
15392 if (appData.debugMode) {
15393 fprintf(debugFP, "EditPosDone\n");
15396 DisplayMessage("", "");
15397 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15398 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15399 gameMode = EditGame;
15401 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15402 ClearHighlights(); /* [AS] */
15405 /* Pause for `ms' milliseconds */
15406 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15408 TimeDelay (long ms)
15415 } while (SubtractTimeMarks(&m2, &m1) < ms);
15418 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15420 SendMultiLineToICS (char *buf)
15422 char temp[MSG_SIZ+1], *p;
15429 strncpy(temp, buf, len);
15434 if (*p == '\n' || *p == '\r')
15439 strcat(temp, "\n");
15441 SendToPlayer(temp, strlen(temp));
15445 SetWhiteToPlayEvent ()
15447 if (gameMode == EditPosition) {
15448 blackPlaysFirst = FALSE;
15449 DisplayBothClocks(); /* works because currentMove is 0 */
15450 } else if (gameMode == IcsExamining) {
15451 SendToICS(ics_prefix);
15452 SendToICS("tomove white\n");
15457 SetBlackToPlayEvent ()
15459 if (gameMode == EditPosition) {
15460 blackPlaysFirst = TRUE;
15461 currentMove = 1; /* kludge */
15462 DisplayBothClocks();
15464 } else if (gameMode == IcsExamining) {
15465 SendToICS(ics_prefix);
15466 SendToICS("tomove black\n");
15471 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15474 ChessSquare piece = boards[0][y][x];
15475 static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15476 static int lastVariant;
15477 int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15479 if (gameMode != EditPosition && gameMode != IcsExamining) return;
15481 switch (selection) {
15483 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15484 MarkTargetSquares(1);
15485 CopyBoard(currentBoard, boards[0]);
15486 CopyBoard(menuBoard, initialPosition);
15487 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15488 SendToICS(ics_prefix);
15489 SendToICS("bsetup clear\n");
15490 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15491 SendToICS(ics_prefix);
15492 SendToICS("clearboard\n");
15495 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15496 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15497 for (y = 0; y < BOARD_HEIGHT; y++) {
15498 if (gameMode == IcsExamining) {
15499 if (boards[currentMove][y][x] != EmptySquare) {
15500 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15504 } else if(boards[0][y][x] != DarkSquare) {
15505 if(boards[0][y][x] != p) nonEmpty++;
15506 boards[0][y][x] = p;
15510 CopyBoard(rightsBoard, nullBoard);
15511 if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15513 for(r = 0; r < BOARD_HEIGHT; r++) {
15514 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
15515 ChessSquare p = menuBoard[r][x];
15516 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15519 menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
15520 DisplayMessage("Clicking clock again restores position", "");
15521 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15522 if(!nonEmpty) { // asked to clear an empty board
15523 CopyBoard(boards[0], menuBoard);
15525 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15526 CopyBoard(boards[0], initialPosition);
15528 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15529 && !CompareBoards(nullBoard, erasedBoard)) {
15530 CopyBoard(boards[0], erasedBoard);
15532 CopyBoard(erasedBoard, currentBoard);
15534 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15535 rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15538 if (gameMode == EditPosition) {
15539 DrawPosition(FALSE, boards[0]);
15544 SetWhiteToPlayEvent();
15548 SetBlackToPlayEvent();
15552 if (gameMode == IcsExamining) {
15553 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15554 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15557 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15558 if(x == BOARD_LEFT-2) {
15559 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15560 boards[0][y][1] = 0;
15562 if(x == BOARD_RGHT+1) {
15563 if(y >= gameInfo.holdingsSize) break;
15564 boards[0][y][BOARD_WIDTH-2] = 0;
15567 boards[0][y][x] = EmptySquare;
15568 DrawPosition(FALSE, boards[0]);
15573 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15574 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
15575 selection = (ChessSquare) (PROMOTED(piece));
15576 } else if(piece == EmptySquare) selection = WhiteSilver;
15577 else selection = (ChessSquare)((int)piece - 1);
15581 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15582 piece > (int)BlackMan && piece <= (int)BlackKing ) {
15583 selection = (ChessSquare) (DEMOTED(piece));
15584 } else if(piece == EmptySquare) selection = BlackSilver;
15585 else selection = (ChessSquare)((int)piece + 1);
15590 if(gameInfo.variant == VariantShatranj ||
15591 gameInfo.variant == VariantXiangqi ||
15592 gameInfo.variant == VariantCourier ||
15593 gameInfo.variant == VariantASEAN ||
15594 gameInfo.variant == VariantMakruk )
15595 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15601 if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15602 if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15608 if(gameInfo.variant == VariantXiangqi)
15609 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15610 if(gameInfo.variant == VariantKnightmate)
15611 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15612 if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15615 if (gameMode == IcsExamining) {
15616 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15617 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15618 PieceToChar(selection), AAA + x, ONE + y);
15621 rightsBoard[y][x] = hasRights;
15622 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15624 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15625 n = PieceToNumber(selection - BlackPawn);
15626 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15627 boards[0][BOARD_HEIGHT-1-n][0] = selection;
15628 boards[0][BOARD_HEIGHT-1-n][1]++;
15630 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15631 n = PieceToNumber(selection);
15632 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15633 boards[0][n][BOARD_WIDTH-1] = selection;
15634 boards[0][n][BOARD_WIDTH-2]++;
15637 boards[0][y][x] = selection;
15638 DrawPosition(TRUE, boards[0]);
15640 fromX = fromY = -1;
15648 DropMenuEvent (ChessSquare selection, int x, int y)
15650 ChessMove moveType;
15652 switch (gameMode) {
15653 case IcsPlayingWhite:
15654 case MachinePlaysBlack:
15655 if (!WhiteOnMove(currentMove)) {
15656 DisplayMoveError(_("It is Black's turn"));
15659 moveType = WhiteDrop;
15661 case IcsPlayingBlack:
15662 case MachinePlaysWhite:
15663 if (WhiteOnMove(currentMove)) {
15664 DisplayMoveError(_("It is White's turn"));
15667 moveType = BlackDrop;
15670 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15676 if (moveType == BlackDrop && selection < BlackPawn) {
15677 selection = (ChessSquare) ((int) selection
15678 + (int) BlackPawn - (int) WhitePawn);
15680 if (boards[currentMove][y][x] != EmptySquare) {
15681 DisplayMoveError(_("That square is occupied"));
15685 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15691 /* Accept a pending offer of any kind from opponent */
15693 if (appData.icsActive) {
15694 SendToICS(ics_prefix);
15695 SendToICS("accept\n");
15696 } else if (cmailMsgLoaded) {
15697 if (currentMove == cmailOldMove &&
15698 commentList[cmailOldMove] != NULL &&
15699 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15700 "Black offers a draw" : "White offers a draw")) {
15702 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15703 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15705 DisplayError(_("There is no pending offer on this move"), 0);
15706 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15709 /* Not used for offers from chess program */
15716 /* Decline a pending offer of any kind from opponent */
15718 if (appData.icsActive) {
15719 SendToICS(ics_prefix);
15720 SendToICS("decline\n");
15721 } else if (cmailMsgLoaded) {
15722 if (currentMove == cmailOldMove &&
15723 commentList[cmailOldMove] != NULL &&
15724 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15725 "Black offers a draw" : "White offers a draw")) {
15727 AppendComment(cmailOldMove, "Draw declined", TRUE);
15728 DisplayComment(cmailOldMove - 1, "Draw declined");
15731 DisplayError(_("There is no pending offer on this move"), 0);
15734 /* Not used for offers from chess program */
15741 /* Issue ICS rematch command */
15742 if (appData.icsActive) {
15743 SendToICS(ics_prefix);
15744 SendToICS("rematch\n");
15751 /* Call your opponent's flag (claim a win on time) */
15752 if (appData.icsActive) {
15753 SendToICS(ics_prefix);
15754 SendToICS("flag\n");
15756 switch (gameMode) {
15759 case MachinePlaysWhite:
15762 GameEnds(GameIsDrawn, "Both players ran out of time",
15765 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15767 DisplayError(_("Your opponent is not out of time"), 0);
15770 case MachinePlaysBlack:
15773 GameEnds(GameIsDrawn, "Both players ran out of time",
15776 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15778 DisplayError(_("Your opponent is not out of time"), 0);
15786 ClockClick (int which)
15787 { // [HGM] code moved to back-end from winboard.c
15788 if(which) { // black clock
15789 if (gameMode == EditPosition || gameMode == IcsExamining) {
15790 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15791 SetBlackToPlayEvent();
15792 } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15793 gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15794 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15795 } else if (shiftKey) {
15796 AdjustClock(which, -1);
15797 } else if (gameMode == IcsPlayingWhite ||
15798 gameMode == MachinePlaysBlack) {
15801 } else { // white clock
15802 if (gameMode == EditPosition || gameMode == IcsExamining) {
15803 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15804 SetWhiteToPlayEvent();
15805 } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15806 gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15807 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15808 } else if (shiftKey) {
15809 AdjustClock(which, -1);
15810 } else if (gameMode == IcsPlayingBlack ||
15811 gameMode == MachinePlaysWhite) {
15820 /* Offer draw or accept pending draw offer from opponent */
15822 if (appData.icsActive) {
15823 /* Note: tournament rules require draw offers to be
15824 made after you make your move but before you punch
15825 your clock. Currently ICS doesn't let you do that;
15826 instead, you immediately punch your clock after making
15827 a move, but you can offer a draw at any time. */
15829 SendToICS(ics_prefix);
15830 SendToICS("draw\n");
15831 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15832 } else if (cmailMsgLoaded) {
15833 if (currentMove == cmailOldMove &&
15834 commentList[cmailOldMove] != NULL &&
15835 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15836 "Black offers a draw" : "White offers a draw")) {
15837 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15838 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15839 } else if (currentMove == cmailOldMove + 1) {
15840 char *offer = WhiteOnMove(cmailOldMove) ?
15841 "White offers a draw" : "Black offers a draw";
15842 AppendComment(currentMove, offer, TRUE);
15843 DisplayComment(currentMove - 1, offer);
15844 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15846 DisplayError(_("You must make your move before offering a draw"), 0);
15847 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15849 } else if (first.offeredDraw) {
15850 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15852 if (first.sendDrawOffers) {
15853 SendToProgram("draw\n", &first);
15854 userOfferedDraw = TRUE;
15862 /* Offer Adjourn or accept pending Adjourn offer from opponent */
15864 if (appData.icsActive) {
15865 SendToICS(ics_prefix);
15866 SendToICS("adjourn\n");
15868 /* Currently GNU Chess doesn't offer or accept Adjourns */
15876 /* Offer Abort or accept pending Abort offer from opponent */
15878 if (appData.icsActive) {
15879 SendToICS(ics_prefix);
15880 SendToICS("abort\n");
15882 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15889 /* Resign. You can do this even if it's not your turn. */
15891 if (appData.icsActive) {
15892 SendToICS(ics_prefix);
15893 SendToICS("resign\n");
15895 switch (gameMode) {
15896 case MachinePlaysWhite:
15897 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15899 case MachinePlaysBlack:
15900 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15903 if (cmailMsgLoaded) {
15905 if (WhiteOnMove(cmailOldMove)) {
15906 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15908 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15910 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15921 StopObservingEvent ()
15923 /* Stop observing current games */
15924 SendToICS(ics_prefix);
15925 SendToICS("unobserve\n");
15929 StopExaminingEvent ()
15931 /* Stop observing current game */
15932 SendToICS(ics_prefix);
15933 SendToICS("unexamine\n");
15937 ForwardInner (int target)
15939 int limit; int oldSeekGraphUp = seekGraphUp;
15941 if (appData.debugMode)
15942 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15943 target, currentMove, forwardMostMove);
15945 if (gameMode == EditPosition)
15948 seekGraphUp = FALSE;
15949 MarkTargetSquares(1);
15950 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15952 if (gameMode == PlayFromGameFile && !pausing)
15955 if (gameMode == IcsExamining && pausing)
15956 limit = pauseExamForwardMostMove;
15958 limit = forwardMostMove;
15960 if (target > limit) target = limit;
15962 if (target > 0 && moveList[target - 1][0]) {
15963 int fromX, fromY, toX, toY;
15964 toX = moveList[target - 1][2] - AAA;
15965 toY = moveList[target - 1][3] - ONE;
15966 if (moveList[target - 1][1] == '@') {
15967 if (appData.highlightLastMove) {
15968 SetHighlights(-1, -1, toX, toY);
15971 fromX = moveList[target - 1][0] - AAA;
15972 fromY = moveList[target - 1][1] - ONE;
15973 if (target == currentMove + 1) {
15974 if(moveList[target - 1][4] == ';') { // multi-leg
15975 killX = moveList[target - 1][5] - AAA;
15976 killY = moveList[target - 1][6] - ONE;
15978 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15979 killX = killY = -1;
15981 if (appData.highlightLastMove) {
15982 SetHighlights(fromX, fromY, toX, toY);
15986 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15987 gameMode == Training || gameMode == PlayFromGameFile ||
15988 gameMode == AnalyzeFile) {
15989 while (currentMove < target) {
15990 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15991 SendMoveToProgram(currentMove++, &first);
15994 currentMove = target;
15997 if (gameMode == EditGame || gameMode == EndOfGame) {
15998 whiteTimeRemaining = timeRemaining[0][currentMove];
15999 blackTimeRemaining = timeRemaining[1][currentMove];
16001 DisplayBothClocks();
16002 DisplayMove(currentMove - 1);
16003 DrawPosition(oldSeekGraphUp, boards[currentMove]);
16004 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16005 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
16006 DisplayComment(currentMove - 1, commentList[currentMove]);
16008 ClearMap(); // [HGM] exclude: invalidate map
16015 if (gameMode == IcsExamining && !pausing) {
16016 SendToICS(ics_prefix);
16017 SendToICS("forward\n");
16019 ForwardInner(currentMove + 1);
16026 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16027 /* to optimze, we temporarily turn off analysis mode while we feed
16028 * the remaining moves to the engine. Otherwise we get analysis output
16031 if (first.analysisSupport) {
16032 SendToProgram("exit\nforce\n", &first);
16033 first.analyzing = FALSE;
16037 if (gameMode == IcsExamining && !pausing) {
16038 SendToICS(ics_prefix);
16039 SendToICS("forward 999999\n");
16041 ForwardInner(forwardMostMove);
16044 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16045 /* we have fed all the moves, so reactivate analysis mode */
16046 SendToProgram("analyze\n", &first);
16047 first.analyzing = TRUE;
16048 /*first.maybeThinking = TRUE;*/
16049 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16054 BackwardInner (int target)
16056 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16058 if (appData.debugMode)
16059 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16060 target, currentMove, forwardMostMove);
16062 if (gameMode == EditPosition) return;
16063 seekGraphUp = FALSE;
16064 MarkTargetSquares(1);
16065 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16066 if (currentMove <= backwardMostMove) {
16068 DrawPosition(full_redraw, boards[currentMove]);
16071 if (gameMode == PlayFromGameFile && !pausing)
16074 if (moveList[target][0]) {
16075 int fromX, fromY, toX, toY;
16076 toX = moveList[target][2] - AAA;
16077 toY = moveList[target][3] - ONE;
16078 if (moveList[target][1] == '@') {
16079 if (appData.highlightLastMove) {
16080 SetHighlights(-1, -1, toX, toY);
16083 fromX = moveList[target][0] - AAA;
16084 fromY = moveList[target][1] - ONE;
16085 if (target == currentMove - 1) {
16086 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16088 if (appData.highlightLastMove) {
16089 SetHighlights(fromX, fromY, toX, toY);
16093 if (gameMode == EditGame || gameMode==AnalyzeMode ||
16094 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16095 while (currentMove > target) {
16096 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16097 // null move cannot be undone. Reload program with move history before it.
16099 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16100 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16102 SendBoard(&first, i);
16103 if(second.analyzing) SendBoard(&second, i);
16104 for(currentMove=i; currentMove<target; currentMove++) {
16105 SendMoveToProgram(currentMove, &first);
16106 if(second.analyzing) SendMoveToProgram(currentMove, &second);
16110 SendToBoth("undo\n");
16114 currentMove = target;
16117 if (gameMode == EditGame || gameMode == EndOfGame) {
16118 whiteTimeRemaining = timeRemaining[0][currentMove];
16119 blackTimeRemaining = timeRemaining[1][currentMove];
16121 DisplayBothClocks();
16122 DisplayMove(currentMove - 1);
16123 DrawPosition(full_redraw, boards[currentMove]);
16124 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16125 // [HGM] PV info: routine tests if comment empty
16126 DisplayComment(currentMove - 1, commentList[currentMove]);
16127 ClearMap(); // [HGM] exclude: invalidate map
16133 if (gameMode == IcsExamining && !pausing) {
16134 SendToICS(ics_prefix);
16135 SendToICS("backward\n");
16137 BackwardInner(currentMove - 1);
16144 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16145 /* to optimize, we temporarily turn off analysis mode while we undo
16146 * all the moves. Otherwise we get analysis output after each undo.
16148 if (first.analysisSupport) {
16149 SendToProgram("exit\nforce\n", &first);
16150 first.analyzing = FALSE;
16154 if (gameMode == IcsExamining && !pausing) {
16155 SendToICS(ics_prefix);
16156 SendToICS("backward 999999\n");
16158 BackwardInner(backwardMostMove);
16161 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16162 /* we have fed all the moves, so reactivate analysis mode */
16163 SendToProgram("analyze\n", &first);
16164 first.analyzing = TRUE;
16165 /*first.maybeThinking = TRUE;*/
16166 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16173 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16174 if (to >= forwardMostMove) to = forwardMostMove;
16175 if (to <= backwardMostMove) to = backwardMostMove;
16176 if (to < currentMove) {
16184 RevertEvent (Boolean annotate)
16186 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16189 if (gameMode != IcsExamining) {
16190 DisplayError(_("You are not examining a game"), 0);
16194 DisplayError(_("You can't revert while pausing"), 0);
16197 SendToICS(ics_prefix);
16198 SendToICS("revert\n");
16202 RetractMoveEvent ()
16204 switch (gameMode) {
16205 case MachinePlaysWhite:
16206 case MachinePlaysBlack:
16207 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16208 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16211 if (forwardMostMove < 2) return;
16212 currentMove = forwardMostMove = forwardMostMove - 2;
16213 whiteTimeRemaining = timeRemaining[0][currentMove];
16214 blackTimeRemaining = timeRemaining[1][currentMove];
16215 DisplayBothClocks();
16216 DisplayMove(currentMove - 1);
16217 ClearHighlights();/*!! could figure this out*/
16218 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16219 SendToProgram("remove\n", &first);
16220 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16223 case BeginningOfGame:
16227 case IcsPlayingWhite:
16228 case IcsPlayingBlack:
16229 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16230 SendToICS(ics_prefix);
16231 SendToICS("takeback 2\n");
16233 SendToICS(ics_prefix);
16234 SendToICS("takeback 1\n");
16243 ChessProgramState *cps;
16245 switch (gameMode) {
16246 case MachinePlaysWhite:
16247 if (!WhiteOnMove(forwardMostMove)) {
16248 DisplayError(_("It is your turn"), 0);
16253 case MachinePlaysBlack:
16254 if (WhiteOnMove(forwardMostMove)) {
16255 DisplayError(_("It is your turn"), 0);
16260 case TwoMachinesPlay:
16261 if (WhiteOnMove(forwardMostMove) ==
16262 (first.twoMachinesColor[0] == 'w')) {
16268 case BeginningOfGame:
16272 SendToProgram("?\n", cps);
16276 TruncateGameEvent ()
16279 if (gameMode != EditGame) return;
16286 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16287 if (forwardMostMove > currentMove) {
16288 if (gameInfo.resultDetails != NULL) {
16289 free(gameInfo.resultDetails);
16290 gameInfo.resultDetails = NULL;
16291 gameInfo.result = GameUnfinished;
16293 forwardMostMove = currentMove;
16294 HistorySet(parseList, backwardMostMove, forwardMostMove,
16302 if (appData.noChessProgram) return;
16303 switch (gameMode) {
16304 case MachinePlaysWhite:
16305 if (WhiteOnMove(forwardMostMove)) {
16306 DisplayError(_("Wait until your turn."), 0);
16310 case BeginningOfGame:
16311 case MachinePlaysBlack:
16312 if (!WhiteOnMove(forwardMostMove)) {
16313 DisplayError(_("Wait until your turn."), 0);
16318 DisplayError(_("No hint available"), 0);
16321 SendToProgram("hint\n", &first);
16322 hintRequested = TRUE;
16326 SaveSelected (FILE *g, int dummy, char *dummy2)
16328 ListGame * lg = (ListGame *) gameList.head;
16332 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16333 DisplayError(_("Game list not loaded or empty"), 0);
16337 creatingBook = TRUE; // suppresses stuff during load game
16339 /* Get list size */
16340 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16341 if(lg->position >= 0) { // selected?
16342 LoadGame(f, nItem, "", TRUE);
16343 SaveGamePGN2(g); // leaves g open
16346 lg = (ListGame *) lg->node.succ;
16350 creatingBook = FALSE;
16358 ListGame * lg = (ListGame *) gameList.head;
16361 static int secondTime = FALSE;
16363 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16364 DisplayError(_("Game list not loaded or empty"), 0);
16368 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16371 DisplayNote(_("Book file exists! Try again for overwrite."));
16375 creatingBook = TRUE;
16376 secondTime = FALSE;
16378 /* Get list size */
16379 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16380 if(lg->position >= 0) {
16381 LoadGame(f, nItem, "", TRUE);
16382 AddGameToBook(TRUE);
16385 lg = (ListGame *) lg->node.succ;
16388 creatingBook = FALSE;
16395 if (appData.noChessProgram) return;
16396 switch (gameMode) {
16397 case MachinePlaysWhite:
16398 if (WhiteOnMove(forwardMostMove)) {
16399 DisplayError(_("Wait until your turn."), 0);
16403 case BeginningOfGame:
16404 case MachinePlaysBlack:
16405 if (!WhiteOnMove(forwardMostMove)) {
16406 DisplayError(_("Wait until your turn."), 0);
16411 EditPositionDone(TRUE);
16413 case TwoMachinesPlay:
16418 SendToProgram("bk\n", &first);
16419 bookOutput[0] = NULLCHAR;
16420 bookRequested = TRUE;
16426 char *tags = PGNTags(&gameInfo);
16427 TagsPopUp(tags, CmailMsg());
16431 /* end button procedures */
16434 PrintPosition (FILE *fp, int move)
16438 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16439 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16440 char c = PieceToChar(boards[move][i][j]);
16441 fputc(c == '?' ? '.' : c, fp);
16442 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16445 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16446 fprintf(fp, "white to play\n");
16448 fprintf(fp, "black to play\n");
16452 PrintOpponents (FILE *fp)
16454 if (gameInfo.white != NULL) {
16455 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16461 /* Find last component of program's own name, using some heuristics */
16463 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16466 int local = (strcmp(host, "localhost") == 0);
16467 while (!local && (p = strchr(prog, ';')) != NULL) {
16469 while (*p == ' ') p++;
16472 if (*prog == '"' || *prog == '\'') {
16473 q = strchr(prog + 1, *prog);
16475 q = strchr(prog, ' ');
16477 if (q == NULL) q = prog + strlen(prog);
16479 while (p >= prog && *p != '/' && *p != '\\') p--;
16481 if(p == prog && *p == '"') p++;
16483 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16484 memcpy(buf, p, q - p);
16485 buf[q - p] = NULLCHAR;
16493 TimeControlTagValue ()
16496 if (!appData.clockMode) {
16497 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16498 } else if (movesPerSession > 0) {
16499 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16500 } else if (timeIncrement == 0) {
16501 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16503 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16505 return StrSave(buf);
16511 /* This routine is used only for certain modes */
16512 VariantClass v = gameInfo.variant;
16513 ChessMove r = GameUnfinished;
16516 if(keepInfo) return;
16518 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16519 r = gameInfo.result;
16520 p = gameInfo.resultDetails;
16521 gameInfo.resultDetails = NULL;
16523 ClearGameInfo(&gameInfo);
16524 gameInfo.variant = v;
16526 switch (gameMode) {
16527 case MachinePlaysWhite:
16528 gameInfo.event = StrSave( appData.pgnEventHeader );
16529 gameInfo.site = StrSave(HostName());
16530 gameInfo.date = PGNDate();
16531 gameInfo.round = StrSave("-");
16532 gameInfo.white = StrSave(first.tidy);
16533 gameInfo.black = StrSave(UserName());
16534 gameInfo.timeControl = TimeControlTagValue();
16537 case MachinePlaysBlack:
16538 gameInfo.event = StrSave( appData.pgnEventHeader );
16539 gameInfo.site = StrSave(HostName());
16540 gameInfo.date = PGNDate();
16541 gameInfo.round = StrSave("-");
16542 gameInfo.white = StrSave(UserName());
16543 gameInfo.black = StrSave(first.tidy);
16544 gameInfo.timeControl = TimeControlTagValue();
16547 case TwoMachinesPlay:
16548 gameInfo.event = StrSave( appData.pgnEventHeader );
16549 gameInfo.site = StrSave(HostName());
16550 gameInfo.date = PGNDate();
16553 snprintf(buf, MSG_SIZ, "%d", roundNr);
16554 gameInfo.round = StrSave(buf);
16556 gameInfo.round = StrSave("-");
16558 if (first.twoMachinesColor[0] == 'w') {
16559 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16560 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16562 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16563 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16565 gameInfo.timeControl = TimeControlTagValue();
16569 gameInfo.event = StrSave("Edited game");
16570 gameInfo.site = StrSave(HostName());
16571 gameInfo.date = PGNDate();
16572 gameInfo.round = StrSave("-");
16573 gameInfo.white = StrSave("-");
16574 gameInfo.black = StrSave("-");
16575 gameInfo.result = r;
16576 gameInfo.resultDetails = p;
16580 gameInfo.event = StrSave("Edited position");
16581 gameInfo.site = StrSave(HostName());
16582 gameInfo.date = PGNDate();
16583 gameInfo.round = StrSave("-");
16584 gameInfo.white = StrSave("-");
16585 gameInfo.black = StrSave("-");
16588 case IcsPlayingWhite:
16589 case IcsPlayingBlack:
16594 case PlayFromGameFile:
16595 gameInfo.event = StrSave("Game from non-PGN file");
16596 gameInfo.site = StrSave(HostName());
16597 gameInfo.date = PGNDate();
16598 gameInfo.round = StrSave("-");
16599 gameInfo.white = StrSave("?");
16600 gameInfo.black = StrSave("?");
16609 ReplaceComment (int index, char *text)
16615 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16616 pvInfoList[index-1].depth == len &&
16617 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16618 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16619 while (*text == '\n') text++;
16620 len = strlen(text);
16621 while (len > 0 && text[len - 1] == '\n') len--;
16623 if (commentList[index] != NULL)
16624 free(commentList[index]);
16627 commentList[index] = NULL;
16630 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16631 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16632 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16633 commentList[index] = (char *) malloc(len + 2);
16634 strncpy(commentList[index], text, len);
16635 commentList[index][len] = '\n';
16636 commentList[index][len + 1] = NULLCHAR;
16638 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16640 commentList[index] = (char *) malloc(len + 7);
16641 safeStrCpy(commentList[index], "{\n", 3);
16642 safeStrCpy(commentList[index]+2, text, len+1);
16643 commentList[index][len+2] = NULLCHAR;
16644 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16645 strcat(commentList[index], "\n}\n");
16650 CrushCRs (char *text)
16658 if (ch == '\r') continue;
16660 } while (ch != '\0');
16664 AppendComment (int index, char *text, Boolean addBraces)
16665 /* addBraces tells if we should add {} */
16670 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16671 if(addBraces == 3) addBraces = 0; else // force appending literally
16672 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16675 while (*text == '\n') text++;
16676 len = strlen(text);
16677 while (len > 0 && text[len - 1] == '\n') len--;
16678 text[len] = NULLCHAR;
16680 if (len == 0) return;
16682 if (commentList[index] != NULL) {
16683 Boolean addClosingBrace = addBraces;
16684 old = commentList[index];
16685 oldlen = strlen(old);
16686 while(commentList[index][oldlen-1] == '\n')
16687 commentList[index][--oldlen] = NULLCHAR;
16688 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16689 safeStrCpy(commentList[index], old, oldlen + len + 6);
16691 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16692 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16693 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16694 while (*text == '\n') { text++; len--; }
16695 commentList[index][--oldlen] = NULLCHAR;
16697 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16698 else strcat(commentList[index], "\n");
16699 strcat(commentList[index], text);
16700 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16701 else strcat(commentList[index], "\n");
16703 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16705 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16706 else commentList[index][0] = NULLCHAR;
16707 strcat(commentList[index], text);
16708 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16709 if(addBraces == TRUE) strcat(commentList[index], "}\n");
16714 FindStr (char * text, char * sub_text)
16716 char * result = strstr( text, sub_text );
16718 if( result != NULL ) {
16719 result += strlen( sub_text );
16725 /* [AS] Try to extract PV info from PGN comment */
16726 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16728 GetInfoFromComment (int index, char * text)
16730 char * sep = text, *p;
16732 if( text != NULL && index > 0 ) {
16735 int time = -1, sec = 0, deci;
16736 char * s_eval = FindStr( text, "[%eval " );
16737 char * s_emt = FindStr( text, "[%emt " );
16739 if( s_eval != NULL || s_emt != NULL ) {
16741 if(0) { // [HGM] this code is not finished, and could actually be detrimental
16746 if( s_eval != NULL ) {
16747 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16751 if( delim != ']' ) {
16756 if( s_emt != NULL ) {
16761 /* We expect something like: [+|-]nnn.nn/dd */
16764 if(*text != '{') return text; // [HGM] braces: must be normal comment
16766 sep = strchr( text, '/' );
16767 if( sep == NULL || sep < (text+4) ) {
16772 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16773 if(p[1] == '(') { // comment starts with PV
16774 p = strchr(p, ')'); // locate end of PV
16775 if(p == NULL || sep < p+5) return text;
16776 // at this point we have something like "{(.*) +0.23/6 ..."
16777 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16778 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16779 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16781 time = -1; sec = -1; deci = -1;
16782 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16783 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16784 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16785 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
16789 if( score_lo < 0 || score_lo >= 100 ) {
16793 if(sec >= 0) time = 600*time + 10*sec; else
16794 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16796 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16798 /* [HGM] PV time: now locate end of PV info */
16799 while( *++sep >= '0' && *sep <= '9'); // strip depth
16801 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16803 while( *++sep >= '0' && *sep <= '9'); // strip seconds
16805 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16806 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16817 pvInfoList[index-1].depth = depth;
16818 pvInfoList[index-1].score = score;
16819 pvInfoList[index-1].time = 10*time; // centi-sec
16820 if(*sep == '}') *sep = 0; else *--sep = '{';
16822 while(*p++ = *sep++)
16825 } // squeeze out space between PV and comment, and return both
16831 SendToProgram (char *message, ChessProgramState *cps)
16833 int count, outCount, error;
16836 if (cps->pr == NoProc) return;
16839 if (appData.debugMode) {
16842 fprintf(debugFP, "%ld >%-6s: %s",
16843 SubtractTimeMarks(&now, &programStartTime),
16844 cps->which, message);
16846 fprintf(serverFP, "%ld >%-6s: %s",
16847 SubtractTimeMarks(&now, &programStartTime),
16848 cps->which, message), fflush(serverFP);
16851 count = strlen(message);
16852 outCount = OutputToProcess(cps->pr, message, count, &error);
16853 if (outCount < count && !exiting
16854 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16855 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16856 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16857 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16858 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16859 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16860 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16861 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16863 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16864 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16865 gameInfo.result = res;
16867 gameInfo.resultDetails = StrSave(buf);
16869 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16870 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16875 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16879 ChessProgramState *cps = (ChessProgramState *)closure;
16881 if (isr != cps->isr) return; /* Killed intentionally */
16884 RemoveInputSource(cps->isr);
16885 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16886 _(cps->which), cps->program);
16887 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16888 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16889 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16890 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16891 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16892 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16894 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16895 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16896 gameInfo.result = res;
16898 gameInfo.resultDetails = StrSave(buf);
16900 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16901 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16903 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16904 _(cps->which), cps->program);
16905 RemoveInputSource(cps->isr);
16907 /* [AS] Program is misbehaving badly... kill it */
16908 if( count == -2 ) {
16909 DestroyChildProcess( cps->pr, 9 );
16913 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16918 if ((end_str = strchr(message, '\r')) != NULL)
16919 *end_str = NULLCHAR;
16920 if ((end_str = strchr(message, '\n')) != NULL)
16921 *end_str = NULLCHAR;
16923 if (appData.debugMode) {
16924 TimeMark now; int print = 1;
16925 char *quote = ""; char c; int i;
16927 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16928 char start = message[0];
16929 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16930 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16931 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
16932 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16933 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16934 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
16935 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16936 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
16937 sscanf(message, "hint: %c", &c)!=1 &&
16938 sscanf(message, "pong %c", &c)!=1 && start != '#') {
16939 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16940 print = (appData.engineComments >= 2);
16942 message[0] = start; // restore original message
16946 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16947 SubtractTimeMarks(&now, &programStartTime), cps->which,
16951 fprintf(serverFP, "%ld <%-6s: %s%s\n",
16952 SubtractTimeMarks(&now, &programStartTime), cps->which,
16954 message), fflush(serverFP);
16958 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16959 if (appData.icsEngineAnalyze) {
16960 if (strstr(message, "whisper") != NULL ||
16961 strstr(message, "kibitz") != NULL ||
16962 strstr(message, "tellics") != NULL) return;
16965 HandleMachineMove(message, cps);
16970 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16975 if( timeControl_2 > 0 ) {
16976 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16977 tc = timeControl_2;
16980 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16981 inc /= cps->timeOdds;
16982 st /= cps->timeOdds;
16984 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16987 /* Set exact time per move, normally using st command */
16988 if (cps->stKludge) {
16989 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16991 if (seconds == 0) {
16992 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16994 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16997 snprintf(buf, MSG_SIZ, "st %d\n", st);
17000 /* Set conventional or incremental time control, using level command */
17001 if (seconds == 0) {
17002 /* Note old gnuchess bug -- minutes:seconds used to not work.
17003 Fixed in later versions, but still avoid :seconds
17004 when seconds is 0. */
17005 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
17007 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
17008 seconds, inc/1000.);
17011 SendToProgram(buf, cps);
17013 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17014 /* Orthogonally, limit search to given depth */
17016 if (cps->sdKludge) {
17017 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17019 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17021 SendToProgram(buf, cps);
17024 if(cps->nps >= 0) { /* [HGM] nps */
17025 if(cps->supportsNPS == FALSE)
17026 cps->nps = -1; // don't use if engine explicitly says not supported!
17028 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17029 SendToProgram(buf, cps);
17034 ChessProgramState *
17036 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17038 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17039 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17045 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17047 char message[MSG_SIZ];
17050 /* Note: this routine must be called when the clocks are stopped
17051 or when they have *just* been set or switched; otherwise
17052 it will be off by the time since the current tick started.
17054 if (machineWhite) {
17055 time = whiteTimeRemaining / 10;
17056 otime = blackTimeRemaining / 10;
17058 time = blackTimeRemaining / 10;
17059 otime = whiteTimeRemaining / 10;
17061 /* [HGM] translate opponent's time by time-odds factor */
17062 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17064 if (time <= 0) time = 1;
17065 if (otime <= 0) otime = 1;
17067 snprintf(message, MSG_SIZ, "time %ld\n", time);
17068 SendToProgram(message, cps);
17070 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17071 SendToProgram(message, cps);
17075 EngineDefinedVariant (ChessProgramState *cps, int n)
17076 { // return name of n-th unknown variant that engine supports
17077 static char buf[MSG_SIZ];
17078 char *p, *s = cps->variants;
17079 if(!s) return NULL;
17080 do { // parse string from variants feature
17082 p = strchr(s, ',');
17083 if(p) *p = NULLCHAR;
17084 v = StringToVariant(s);
17085 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17086 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17087 if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17088 !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17089 !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17090 !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17091 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17094 if(n < 0) return buf;
17100 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17103 int len = strlen(name);
17106 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17108 sscanf(*p, "%d", &val);
17110 while (**p && **p != ' ')
17112 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17113 SendToProgram(buf, cps);
17120 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17123 int len = strlen(name);
17124 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17126 sscanf(*p, "%d", loc);
17127 while (**p && **p != ' ') (*p)++;
17128 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17129 SendToProgram(buf, cps);
17136 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17139 int len = strlen(name);
17140 if (strncmp((*p), name, len) == 0
17141 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17143 ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
17144 sscanf(*p, "%[^\"]", *loc);
17145 while (**p && **p != '\"') (*p)++;
17146 if (**p == '\"') (*p)++;
17147 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17148 SendToProgram(buf, cps);
17155 ParseOption (Option *opt, ChessProgramState *cps)
17156 // [HGM] options: process the string that defines an engine option, and determine
17157 // name, type, default value, and allowed value range
17159 char *p, *q, buf[MSG_SIZ];
17160 int n, min = (-1)<<31, max = 1<<31, def;
17162 opt->target = &opt->value; // OK for spin/slider and checkbox
17163 if(p = strstr(opt->name, " -spin ")) {
17164 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17165 if(max < min) max = min; // enforce consistency
17166 if(def < min) def = min;
17167 if(def > max) def = max;
17172 } else if((p = strstr(opt->name, " -slider "))) {
17173 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17174 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17175 if(max < min) max = min; // enforce consistency
17176 if(def < min) def = min;
17177 if(def > max) def = max;
17181 opt->type = Spin; // Slider;
17182 } else if((p = strstr(opt->name, " -string "))) {
17183 opt->textValue = p+9;
17184 opt->type = TextBox;
17185 opt->target = &opt->textValue;
17186 } else if((p = strstr(opt->name, " -file "))) {
17187 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17188 opt->target = opt->textValue = p+7;
17189 opt->type = FileName; // FileName;
17190 opt->target = &opt->textValue;
17191 } else if((p = strstr(opt->name, " -path "))) {
17192 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17193 opt->target = opt->textValue = p+7;
17194 opt->type = PathName; // PathName;
17195 opt->target = &opt->textValue;
17196 } else if(p = strstr(opt->name, " -check ")) {
17197 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17198 opt->value = (def != 0);
17199 opt->type = CheckBox;
17200 } else if(p = strstr(opt->name, " -combo ")) {
17201 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17202 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17203 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17204 opt->value = n = 0;
17205 while(q = StrStr(q, " /// ")) {
17206 n++; *q = 0; // count choices, and null-terminate each of them
17208 if(*q == '*') { // remember default, which is marked with * prefix
17212 cps->comboList[cps->comboCnt++] = q;
17214 cps->comboList[cps->comboCnt++] = NULL;
17216 opt->type = ComboBox;
17217 } else if(p = strstr(opt->name, " -button")) {
17218 opt->type = Button;
17219 } else if(p = strstr(opt->name, " -save")) {
17220 opt->type = SaveButton;
17221 } else return FALSE;
17222 *p = 0; // terminate option name
17223 // now look if the command-line options define a setting for this engine option.
17224 if(cps->optionSettings && cps->optionSettings[0])
17225 p = strstr(cps->optionSettings, opt->name); else p = NULL;
17226 if(p && (p == cps->optionSettings || p[-1] == ',')) {
17227 snprintf(buf, MSG_SIZ, "option %s", p);
17228 if(p = strstr(buf, ",")) *p = 0;
17229 if(q = strchr(buf, '=')) switch(opt->type) {
17231 for(n=0; n<opt->max; n++)
17232 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17235 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17239 opt->value = atoi(q+1);
17244 SendToProgram(buf, cps);
17250 FeatureDone (ChessProgramState *cps, int val)
17252 DelayedEventCallback cb = GetDelayedEvent();
17253 if ((cb == InitBackEnd3 && cps == &first) ||
17254 (cb == SettingsMenuIfReady && cps == &second) ||
17255 (cb == LoadEngine) ||
17256 (cb == TwoMachinesEventIfReady)) {
17257 CancelDelayedEvent();
17258 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17259 } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17260 cps->initDone = val;
17261 if(val) cps->reload = FALSE, RefreshSettingsDialog(cps, val);
17264 /* Parse feature command from engine */
17266 ParseFeatures (char *args, ChessProgramState *cps)
17274 while (*p == ' ') p++;
17275 if (*p == NULLCHAR) return;
17277 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17278 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17279 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17280 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17281 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17282 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17283 if (BoolFeature(&p, "reuse", &val, cps)) {
17284 /* Engine can disable reuse, but can't enable it if user said no */
17285 if (!val) cps->reuse = FALSE;
17288 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17289 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17290 if (gameMode == TwoMachinesPlay) {
17291 DisplayTwoMachinesTitle();
17297 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17298 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17299 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17300 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17301 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17302 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17303 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17304 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17305 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17306 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17307 if (IntFeature(&p, "done", &val, cps)) {
17308 FeatureDone(cps, val);
17311 /* Added by Tord: */
17312 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17313 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17314 /* End of additions by Tord */
17316 /* [HGM] added features: */
17317 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17318 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17319 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17320 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17321 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17322 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17323 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17324 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17325 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17326 FREE(cps->option[cps->nrOptions].name);
17327 cps->option[cps->nrOptions].name = q; q = NULL;
17328 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17329 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17330 SendToProgram(buf, cps);
17333 if(cps->nrOptions >= MAX_OPTIONS) {
17335 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17336 DisplayError(buf, 0);
17340 /* End of additions by HGM */
17342 /* unknown feature: complain and skip */
17344 while (*q && *q != '=') q++;
17345 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17346 SendToProgram(buf, cps);
17352 while (*p && *p != '\"') p++;
17353 if (*p == '\"') p++;
17355 while (*p && *p != ' ') p++;
17363 PeriodicUpdatesEvent (int newState)
17365 if (newState == appData.periodicUpdates)
17368 appData.periodicUpdates=newState;
17370 /* Display type changes, so update it now */
17371 // DisplayAnalysis();
17373 /* Get the ball rolling again... */
17375 AnalysisPeriodicEvent(1);
17376 StartAnalysisClock();
17381 PonderNextMoveEvent (int newState)
17383 if (newState == appData.ponderNextMove) return;
17384 if (gameMode == EditPosition) EditPositionDone(TRUE);
17386 SendToProgram("hard\n", &first);
17387 if (gameMode == TwoMachinesPlay) {
17388 SendToProgram("hard\n", &second);
17391 SendToProgram("easy\n", &first);
17392 thinkOutput[0] = NULLCHAR;
17393 if (gameMode == TwoMachinesPlay) {
17394 SendToProgram("easy\n", &second);
17397 appData.ponderNextMove = newState;
17401 NewSettingEvent (int option, int *feature, char *command, int value)
17405 if (gameMode == EditPosition) EditPositionDone(TRUE);
17406 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17407 if(feature == NULL || *feature) SendToProgram(buf, &first);
17408 if (gameMode == TwoMachinesPlay) {
17409 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17414 ShowThinkingEvent ()
17415 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17417 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17418 int newState = appData.showThinking
17419 // [HGM] thinking: other features now need thinking output as well
17420 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17422 if (oldState == newState) return;
17423 oldState = newState;
17424 if (gameMode == EditPosition) EditPositionDone(TRUE);
17426 SendToProgram("post\n", &first);
17427 if (gameMode == TwoMachinesPlay) {
17428 SendToProgram("post\n", &second);
17431 SendToProgram("nopost\n", &first);
17432 thinkOutput[0] = NULLCHAR;
17433 if (gameMode == TwoMachinesPlay) {
17434 SendToProgram("nopost\n", &second);
17437 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17441 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17443 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17444 if (pr == NoProc) return;
17445 AskQuestion(title, question, replyPrefix, pr);
17449 TypeInEvent (char firstChar)
17451 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17452 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17453 gameMode == AnalyzeMode || gameMode == EditGame ||
17454 gameMode == EditPosition || gameMode == IcsExamining ||
17455 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17456 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17457 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17458 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
17459 gameMode == Training) PopUpMoveDialog(firstChar);
17463 TypeInDoneEvent (char *move)
17466 int n, fromX, fromY, toX, toY;
17468 ChessMove moveType;
17471 if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17472 EditPositionPasteFEN(move);
17475 // [HGM] movenum: allow move number to be typed in any mode
17476 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17480 // undocumented kludge: allow command-line option to be typed in!
17481 // (potentially fatal, and does not implement the effect of the option.)
17482 // should only be used for options that are values on which future decisions will be made,
17483 // and definitely not on options that would be used during initialization.
17484 if(strstr(move, "!!! -") == move) {
17485 ParseArgsFromString(move+4);
17489 if (gameMode != EditGame && currentMove != forwardMostMove &&
17490 gameMode != Training) {
17491 DisplayMoveError(_("Displayed move is not current"));
17493 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17494 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17495 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17496 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17497 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17498 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17500 DisplayMoveError(_("Could not parse move"));
17506 DisplayMove (int moveNumber)
17508 char message[MSG_SIZ];
17510 char cpThinkOutput[MSG_SIZ];
17512 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17514 if (moveNumber == forwardMostMove - 1 ||
17515 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17517 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17519 if (strchr(cpThinkOutput, '\n')) {
17520 *strchr(cpThinkOutput, '\n') = NULLCHAR;
17523 *cpThinkOutput = NULLCHAR;
17526 /* [AS] Hide thinking from human user */
17527 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17528 *cpThinkOutput = NULLCHAR;
17529 if( thinkOutput[0] != NULLCHAR ) {
17532 for( i=0; i<=hiddenThinkOutputState; i++ ) {
17533 cpThinkOutput[i] = '.';
17535 cpThinkOutput[i] = NULLCHAR;
17536 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17540 if (moveNumber == forwardMostMove - 1 &&
17541 gameInfo.resultDetails != NULL) {
17542 if (gameInfo.resultDetails[0] == NULLCHAR) {
17543 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17545 snprintf(res, MSG_SIZ, " {%s} %s",
17546 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17552 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17553 DisplayMessage(res, cpThinkOutput);
17555 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17556 WhiteOnMove(moveNumber) ? " " : ".. ",
17557 parseList[moveNumber], res);
17558 DisplayMessage(message, cpThinkOutput);
17563 DisplayComment (int moveNumber, char *text)
17565 char title[MSG_SIZ];
17567 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17568 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17570 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17571 WhiteOnMove(moveNumber) ? " " : ".. ",
17572 parseList[moveNumber]);
17574 if (text != NULL && (appData.autoDisplayComment || commentUp))
17575 CommentPopUp(title, text);
17578 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17579 * might be busy thinking or pondering. It can be omitted if your
17580 * gnuchess is configured to stop thinking immediately on any user
17581 * input. However, that gnuchess feature depends on the FIONREAD
17582 * ioctl, which does not work properly on some flavors of Unix.
17585 Attention (ChessProgramState *cps)
17588 if (!cps->useSigint) return;
17589 if (appData.noChessProgram || (cps->pr == NoProc)) return;
17590 switch (gameMode) {
17591 case MachinePlaysWhite:
17592 case MachinePlaysBlack:
17593 case TwoMachinesPlay:
17594 case IcsPlayingWhite:
17595 case IcsPlayingBlack:
17598 /* Skip if we know it isn't thinking */
17599 if (!cps->maybeThinking) return;
17600 if (appData.debugMode)
17601 fprintf(debugFP, "Interrupting %s\n", cps->which);
17602 InterruptChildProcess(cps->pr);
17603 cps->maybeThinking = FALSE;
17608 #endif /*ATTENTION*/
17614 if (whiteTimeRemaining <= 0) {
17617 if (appData.icsActive) {
17618 if (appData.autoCallFlag &&
17619 gameMode == IcsPlayingBlack && !blackFlag) {
17620 SendToICS(ics_prefix);
17621 SendToICS("flag\n");
17625 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17627 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17628 if (appData.autoCallFlag) {
17629 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17636 if (blackTimeRemaining <= 0) {
17639 if (appData.icsActive) {
17640 if (appData.autoCallFlag &&
17641 gameMode == IcsPlayingWhite && !whiteFlag) {
17642 SendToICS(ics_prefix);
17643 SendToICS("flag\n");
17647 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17649 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17650 if (appData.autoCallFlag) {
17651 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17662 CheckTimeControl ()
17664 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17665 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17668 * add time to clocks when time control is achieved ([HGM] now also used for increment)
17670 if ( !WhiteOnMove(forwardMostMove) ) {
17671 /* White made time control */
17672 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17673 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17674 /* [HGM] time odds: correct new time quota for time odds! */
17675 / WhitePlayer()->timeOdds;
17676 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17678 lastBlack -= blackTimeRemaining;
17679 /* Black made time control */
17680 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17681 / WhitePlayer()->other->timeOdds;
17682 lastWhite = whiteTimeRemaining;
17687 DisplayBothClocks ()
17689 int wom = gameMode == EditPosition ?
17690 !blackPlaysFirst : WhiteOnMove(currentMove);
17691 DisplayWhiteClock(whiteTimeRemaining, wom);
17692 DisplayBlackClock(blackTimeRemaining, !wom);
17696 /* Timekeeping seems to be a portability nightmare. I think everyone
17697 has ftime(), but I'm really not sure, so I'm including some ifdefs
17698 to use other calls if you don't. Clocks will be less accurate if
17699 you have neither ftime nor gettimeofday.
17702 /* VS 2008 requires the #include outside of the function */
17703 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17704 #include <sys/timeb.h>
17707 /* Get the current time as a TimeMark */
17709 GetTimeMark (TimeMark *tm)
17711 #if HAVE_GETTIMEOFDAY
17713 struct timeval timeVal;
17714 struct timezone timeZone;
17716 gettimeofday(&timeVal, &timeZone);
17717 tm->sec = (long) timeVal.tv_sec;
17718 tm->ms = (int) (timeVal.tv_usec / 1000L);
17720 #else /*!HAVE_GETTIMEOFDAY*/
17723 // include <sys/timeb.h> / moved to just above start of function
17724 struct timeb timeB;
17727 tm->sec = (long) timeB.time;
17728 tm->ms = (int) timeB.millitm;
17730 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17731 tm->sec = (long) time(NULL);
17737 /* Return the difference in milliseconds between two
17738 time marks. We assume the difference will fit in a long!
17741 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17743 return 1000L*(tm2->sec - tm1->sec) +
17744 (long) (tm2->ms - tm1->ms);
17749 * Code to manage the game clocks.
17751 * In tournament play, black starts the clock and then white makes a move.
17752 * We give the human user a slight advantage if he is playing white---the
17753 * clocks don't run until he makes his first move, so it takes zero time.
17754 * Also, we don't account for network lag, so we could get out of sync
17755 * with GNU Chess's clock -- but then, referees are always right.
17758 static TimeMark tickStartTM;
17759 static long intendedTickLength;
17762 NextTickLength (long timeRemaining)
17764 long nominalTickLength, nextTickLength;
17766 if (timeRemaining > 0L && timeRemaining <= 10000L)
17767 nominalTickLength = 100L;
17769 nominalTickLength = 1000L;
17770 nextTickLength = timeRemaining % nominalTickLength;
17771 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17773 return nextTickLength;
17776 /* Adjust clock one minute up or down */
17778 AdjustClock (Boolean which, int dir)
17780 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17781 if(which) blackTimeRemaining += 60000*dir;
17782 else whiteTimeRemaining += 60000*dir;
17783 DisplayBothClocks();
17784 adjustedClock = TRUE;
17787 /* Stop clocks and reset to a fresh time control */
17791 (void) StopClockTimer();
17792 if (appData.icsActive) {
17793 whiteTimeRemaining = blackTimeRemaining = 0;
17794 } else if (searchTime) {
17795 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17796 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17797 } else { /* [HGM] correct new time quote for time odds */
17798 whiteTC = blackTC = fullTimeControlString;
17799 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17800 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17802 if (whiteFlag || blackFlag) {
17804 whiteFlag = blackFlag = FALSE;
17806 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17807 DisplayBothClocks();
17808 adjustedClock = FALSE;
17811 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17813 /* Decrement running clock by amount of time that has passed */
17817 long timeRemaining;
17818 long lastTickLength, fudge;
17821 if (!appData.clockMode) return;
17822 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17826 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17828 /* Fudge if we woke up a little too soon */
17829 fudge = intendedTickLength - lastTickLength;
17830 if (fudge < 0 || fudge > FUDGE) fudge = 0;
17832 if (WhiteOnMove(forwardMostMove)) {
17833 if(whiteNPS >= 0) lastTickLength = 0;
17834 timeRemaining = whiteTimeRemaining -= lastTickLength;
17835 if(timeRemaining < 0 && !appData.icsActive) {
17836 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17837 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17838 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17839 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17842 DisplayWhiteClock(whiteTimeRemaining - fudge,
17843 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17845 if(blackNPS >= 0) lastTickLength = 0;
17846 timeRemaining = blackTimeRemaining -= lastTickLength;
17847 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17848 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17850 blackStartMove = forwardMostMove;
17851 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17854 DisplayBlackClock(blackTimeRemaining - fudge,
17855 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17857 if (CheckFlags()) return;
17859 if(twoBoards) { // count down secondary board's clocks as well
17860 activePartnerTime -= lastTickLength;
17862 if(activePartner == 'W')
17863 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17865 DisplayBlackClock(activePartnerTime, TRUE);
17870 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17871 StartClockTimer(intendedTickLength);
17873 /* if the time remaining has fallen below the alarm threshold, sound the
17874 * alarm. if the alarm has sounded and (due to a takeback or time control
17875 * with increment) the time remaining has increased to a level above the
17876 * threshold, reset the alarm so it can sound again.
17879 if (appData.icsActive && appData.icsAlarm) {
17881 /* make sure we are dealing with the user's clock */
17882 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17883 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17886 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17887 alarmSounded = FALSE;
17888 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17890 alarmSounded = TRUE;
17896 /* A player has just moved, so stop the previously running
17897 clock and (if in clock mode) start the other one.
17898 We redisplay both clocks in case we're in ICS mode, because
17899 ICS gives us an update to both clocks after every move.
17900 Note that this routine is called *after* forwardMostMove
17901 is updated, so the last fractional tick must be subtracted
17902 from the color that is *not* on move now.
17905 SwitchClocks (int newMoveNr)
17907 long lastTickLength;
17909 int flagged = FALSE;
17913 if (StopClockTimer() && appData.clockMode) {
17914 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17915 if (!WhiteOnMove(forwardMostMove)) {
17916 if(blackNPS >= 0) lastTickLength = 0;
17917 blackTimeRemaining -= lastTickLength;
17918 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17919 // if(pvInfoList[forwardMostMove].time == -1)
17920 pvInfoList[forwardMostMove].time = // use GUI time
17921 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17923 if(whiteNPS >= 0) lastTickLength = 0;
17924 whiteTimeRemaining -= lastTickLength;
17925 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17926 // if(pvInfoList[forwardMostMove].time == -1)
17927 pvInfoList[forwardMostMove].time =
17928 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17930 flagged = CheckFlags();
17932 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17933 CheckTimeControl();
17935 if (flagged || !appData.clockMode) return;
17937 switch (gameMode) {
17938 case MachinePlaysBlack:
17939 case MachinePlaysWhite:
17940 case BeginningOfGame:
17941 if (pausing) return;
17945 case PlayFromGameFile:
17953 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17954 if(WhiteOnMove(forwardMostMove))
17955 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17956 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17960 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17961 whiteTimeRemaining : blackTimeRemaining);
17962 StartClockTimer(intendedTickLength);
17966 /* Stop both clocks */
17970 long lastTickLength;
17973 if (!StopClockTimer()) return;
17974 if (!appData.clockMode) return;
17978 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17979 if (WhiteOnMove(forwardMostMove)) {
17980 if(whiteNPS >= 0) lastTickLength = 0;
17981 whiteTimeRemaining -= lastTickLength;
17982 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17984 if(blackNPS >= 0) lastTickLength = 0;
17985 blackTimeRemaining -= lastTickLength;
17986 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17991 /* Start clock of player on move. Time may have been reset, so
17992 if clock is already running, stop and restart it. */
17996 (void) StopClockTimer(); /* in case it was running already */
17997 DisplayBothClocks();
17998 if (CheckFlags()) return;
18000 if (!appData.clockMode) return;
18001 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
18003 GetTimeMark(&tickStartTM);
18004 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18005 whiteTimeRemaining : blackTimeRemaining);
18007 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
18008 whiteNPS = blackNPS = -1;
18009 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
18010 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
18011 whiteNPS = first.nps;
18012 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18013 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18014 blackNPS = first.nps;
18015 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18016 whiteNPS = second.nps;
18017 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18018 blackNPS = second.nps;
18019 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18021 StartClockTimer(intendedTickLength);
18025 TimeString (long ms)
18027 long second, minute, hour, day;
18029 static char buf[32];
18031 if (ms > 0 && ms <= 9900) {
18032 /* convert milliseconds to tenths, rounding up */
18033 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18035 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18039 /* convert milliseconds to seconds, rounding up */
18040 /* use floating point to avoid strangeness of integer division
18041 with negative dividends on many machines */
18042 second = (long) floor(((double) (ms + 999L)) / 1000.0);
18049 day = second / (60 * 60 * 24);
18050 second = second % (60 * 60 * 24);
18051 hour = second / (60 * 60);
18052 second = second % (60 * 60);
18053 minute = second / 60;
18054 second = second % 60;
18057 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
18058 sign, day, hour, minute, second);
18060 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
18062 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
18069 * This is necessary because some C libraries aren't ANSI C compliant yet.
18072 StrStr (char *string, char *match)
18076 length = strlen(match);
18078 for (i = strlen(string) - length; i >= 0; i--, string++)
18079 if (!strncmp(match, string, length))
18086 StrCaseStr (char *string, char *match)
18090 length = strlen(match);
18092 for (i = strlen(string) - length; i >= 0; i--, string++) {
18093 for (j = 0; j < length; j++) {
18094 if (ToLower(match[j]) != ToLower(string[j]))
18097 if (j == length) return string;
18105 StrCaseCmp (char *s1, char *s2)
18110 c1 = ToLower(*s1++);
18111 c2 = ToLower(*s2++);
18112 if (c1 > c2) return 1;
18113 if (c1 < c2) return -1;
18114 if (c1 == NULLCHAR) return 0;
18122 return isupper(c) ? tolower(c) : c;
18129 return islower(c) ? toupper(c) : c;
18131 #endif /* !_amigados */
18138 if ((ret = (char *) malloc(strlen(s) + 1)))
18140 safeStrCpy(ret, s, strlen(s)+1);
18146 StrSavePtr (char *s, char **savePtr)
18151 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18152 safeStrCpy(*savePtr, s, strlen(s)+1);
18164 clock = time((time_t *)NULL);
18165 tm = localtime(&clock);
18166 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18167 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18168 return StrSave(buf);
18173 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18175 int i, j, fromX, fromY, toX, toY;
18176 int whiteToPlay, haveRights = nrCastlingRights;
18182 whiteToPlay = (gameMode == EditPosition) ?
18183 !blackPlaysFirst : (move % 2 == 0);
18186 /* Piece placement data */
18187 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18188 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18190 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18191 if (boards[move][i][j] == EmptySquare) {
18193 } else { ChessSquare piece = boards[move][i][j];
18194 if (emptycount > 0) {
18195 if(emptycount<10) /* [HGM] can be >= 10 */
18196 *p++ = '0' + emptycount;
18197 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18200 if(PieceToChar(piece) == '+') {
18201 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18203 piece = (ChessSquare)(CHUDEMOTED(piece));
18205 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18206 if(*p = PieceSuffix(piece)) p++;
18208 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18209 p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18214 if (emptycount > 0) {
18215 if(emptycount<10) /* [HGM] can be >= 10 */
18216 *p++ = '0' + emptycount;
18217 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18224 /* [HGM] print Crazyhouse or Shogi holdings */
18225 if( gameInfo.holdingsWidth ) {
18226 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18228 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18229 piece = boards[move][i][BOARD_WIDTH-1];
18230 if( piece != EmptySquare )
18231 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18232 *p++ = PieceToChar(piece);
18234 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18235 piece = boards[move][BOARD_HEIGHT-i-1][0];
18236 if( piece != EmptySquare )
18237 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18238 *p++ = PieceToChar(piece);
18241 if( q == p ) *p++ = '-';
18247 *p++ = whiteToPlay ? 'w' : 'b';
18250 if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18251 haveRights = 0; q = p;
18252 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18253 piece = boards[move][0][i];
18254 if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18255 if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18258 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18259 piece = boards[move][BOARD_HEIGHT-1][i];
18260 if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18261 if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18264 if(p == q) *p++ = '-';
18268 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18271 if(q != overrideCastling+1) p[-1] = ' '; else --p;
18274 int handW=0, handB=0;
18275 if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18276 for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18277 for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18280 if(appData.fischerCastling) {
18281 if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18282 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18283 if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18285 /* [HGM] write directly from rights */
18286 if(boards[move][CASTLING][2] != NoRights &&
18287 boards[move][CASTLING][0] != NoRights )
18288 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18289 if(boards[move][CASTLING][2] != NoRights &&
18290 boards[move][CASTLING][1] != NoRights )
18291 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18294 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18295 if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18297 if(boards[move][CASTLING][5] != NoRights &&
18298 boards[move][CASTLING][3] != NoRights )
18299 *p++ = boards[move][CASTLING][3] + AAA;
18300 if(boards[move][CASTLING][5] != NoRights &&
18301 boards[move][CASTLING][4] != NoRights )
18302 *p++ = boards[move][CASTLING][4] + AAA;
18306 /* [HGM] write true castling rights */
18307 if( nrCastlingRights == 6 ) {
18309 if(boards[move][CASTLING][0] != NoRights &&
18310 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
18311 q = (boards[move][CASTLING][1] != NoRights &&
18312 boards[move][CASTLING][2] != NoRights );
18313 if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18314 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18315 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18316 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18320 if(boards[move][CASTLING][3] != NoRights &&
18321 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
18322 q = (boards[move][CASTLING][4] != NoRights &&
18323 boards[move][CASTLING][5] != NoRights );
18325 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18326 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18327 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18332 if (q == p) *p++ = '-'; /* No castling rights */
18336 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
18337 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18338 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18339 /* En passant target square */
18340 if (move > backwardMostMove) {
18341 fromX = moveList[move - 1][0] - AAA;
18342 fromY = moveList[move - 1][1] - ONE;
18343 toX = moveList[move - 1][2] - AAA;
18344 toY = moveList[move - 1][3] - ONE;
18345 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18346 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18347 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18349 /* 2-square pawn move just happened */
18351 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18355 } else if(move == backwardMostMove) {
18356 // [HGM] perhaps we should always do it like this, and forget the above?
18357 if((signed char)boards[move][EP_STATUS] >= 0) {
18358 *p++ = boards[move][EP_STATUS] + AAA;
18359 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18371 { int i = 0, j=move;
18373 /* [HGM] find reversible plies */
18374 if (appData.debugMode) { int k;
18375 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18376 for(k=backwardMostMove; k<=forwardMostMove; k++)
18377 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18381 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18382 if( j == backwardMostMove ) i += initialRulePlies;
18383 sprintf(p, "%d ", i);
18384 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18386 /* Fullmove number */
18387 sprintf(p, "%d", (move / 2) + 1);
18388 } else *--p = NULLCHAR;
18390 return StrSave(buf);
18394 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18396 int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18398 int emptycount, virgin[BOARD_FILES];
18399 ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18403 /* Piece placement data */
18404 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18407 if (*p == '/' || *p == ' ' || *p == '[' ) {
18409 emptycount = gameInfo.boardWidth - j;
18410 while (emptycount--)
18411 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18412 if (*p == '/') p++;
18413 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18414 for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18415 for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18417 appData.NrRanks = gameInfo.boardHeight - i; i=0;
18420 #if(BOARD_FILES >= 10)*0
18421 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18422 p++; emptycount=10;
18423 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18424 while (emptycount--)
18425 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18427 } else if (*p == '*') {
18428 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18429 } else if (isdigit(*p)) {
18430 emptycount = *p++ - '0';
18431 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18432 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18433 while (emptycount--)
18434 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18435 } else if (*p == '<') {
18436 if(i == BOARD_HEIGHT-1) shuffle = 1;
18437 else if (i != 0 || !shuffle) return FALSE;
18439 } else if (shuffle && *p == '>') {
18440 p++; // for now ignore closing shuffle range, and assume rank-end
18441 } else if (*p == '?') {
18442 if (j >= gameInfo.boardWidth) return FALSE;
18443 if (i != 0 && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18444 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18445 } else if (*p == '+' || isalpha(*p)) {
18446 char *q, *s = SUFFIXES;
18447 if (j >= gameInfo.boardWidth) return FALSE;
18450 if(q = strchr(s, p[1])) p++;
18451 piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18452 if(piece == EmptySquare) return FALSE; /* unknown piece */
18453 piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18454 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18457 if(q = strchr(s, *p)) p++;
18458 piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18461 if(piece==EmptySquare) return FALSE; /* unknown piece */
18462 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18463 piece = (ChessSquare) (PROMOTED(piece));
18464 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18467 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18468 if(piece == king) wKingRank = i;
18469 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18475 while (*p == '/' || *p == ' ') p++;
18477 if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18479 /* [HGM] by default clear Crazyhouse holdings, if present */
18480 if(gameInfo.holdingsWidth) {
18481 for(i=0; i<BOARD_HEIGHT; i++) {
18482 board[i][0] = EmptySquare; /* black holdings */
18483 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18484 board[i][1] = (ChessSquare) 0; /* black counts */
18485 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18489 /* [HGM] look for Crazyhouse holdings here */
18490 while(*p==' ') p++;
18491 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18492 int swap=0, wcnt=0, bcnt=0;
18494 if(*p == '<') swap++, p++;
18495 if(*p == '-' ) p++; /* empty holdings */ else {
18496 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18497 /* if we would allow FEN reading to set board size, we would */
18498 /* have to add holdings and shift the board read so far here */
18499 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18501 if((int) piece >= (int) BlackPawn ) {
18502 i = (int)piece - (int)BlackPawn;
18503 i = PieceToNumber((ChessSquare)i);
18504 if( i >= gameInfo.holdingsSize ) return FALSE;
18505 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18506 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
18509 i = (int)piece - (int)WhitePawn;
18510 i = PieceToNumber((ChessSquare)i);
18511 if( i >= gameInfo.holdingsSize ) return FALSE;
18512 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
18513 board[i][BOARD_WIDTH-2]++; /* black holdings */
18517 if(subst) { // substitute back-rank question marks by holdings pieces
18518 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18519 int k, m, n = bcnt + 1;
18520 if(board[0][j] == ClearBoard) {
18521 if(!wcnt) return FALSE;
18523 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18524 board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18525 if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18529 if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18530 if(!bcnt) return FALSE;
18531 if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18532 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18533 board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18534 if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18545 if(subst) return FALSE; // substitution requested, but no holdings
18547 while(*p == ' ') p++;
18551 if(appData.colorNickNames) {
18552 if( c == appData.colorNickNames[0] ) c = 'w'; else
18553 if( c == appData.colorNickNames[1] ) c = 'b';
18557 *blackPlaysFirst = FALSE;
18560 *blackPlaysFirst = TRUE;
18566 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18567 /* return the extra info in global variiables */
18569 while(*p==' ') p++;
18571 if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18572 if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18573 if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18576 /* set defaults in case FEN is incomplete */
18577 board[EP_STATUS] = EP_UNKNOWN;
18578 board[TOUCHED_W] = board[TOUCHED_B] = 0;
18579 for(i=0; i<nrCastlingRights; i++ ) {
18580 board[CASTLING][i] =
18581 appData.fischerCastling ? NoRights : initialRights[i];
18582 } /* assume possible unless obviously impossible */
18583 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18584 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18585 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18586 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18587 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18588 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18589 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18590 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18593 if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18596 while(isalpha(*p)) {
18597 if(isupper(*p)) w |= 1 << (*p++ - 'A');
18598 if(islower(*p)) b |= 1 << (*p++ - 'a');
18602 board[TOUCHED_W] = ~w;
18603 board[TOUCHED_B] = ~b;
18604 while(*p == ' ') p++;
18608 if(nrCastlingRights) {
18610 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18611 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18612 /* castling indicator present, so default becomes no castlings */
18613 for(i=0; i<nrCastlingRights; i++ ) {
18614 board[CASTLING][i] = NoRights;
18617 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18618 (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18619 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18620 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
18621 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18623 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18624 if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18625 if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18627 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18628 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18629 if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18630 && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18631 if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18632 && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18635 for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18636 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18637 board[CASTLING][2] = whiteKingFile;
18638 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18639 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18640 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18643 for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18644 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18645 board[CASTLING][2] = whiteKingFile;
18646 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18647 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18648 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18651 for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18652 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18653 board[CASTLING][5] = blackKingFile;
18654 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18655 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18656 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18659 for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18660 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18661 board[CASTLING][5] = blackKingFile;
18662 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18663 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18664 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18667 default: /* FRC castlings */
18668 if(c >= 'a') { /* black rights */
18669 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18670 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18671 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18672 if(i == BOARD_RGHT) break;
18673 board[CASTLING][5] = i;
18675 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
18676 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
18678 board[CASTLING][3] = c;
18680 board[CASTLING][4] = c;
18681 } else { /* white rights */
18682 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18683 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18684 if(board[0][i] == WhiteKing) break;
18685 if(i == BOARD_RGHT) break;
18686 board[CASTLING][2] = i;
18687 c -= AAA - 'a' + 'A';
18688 if(board[0][c] >= WhiteKing) break;
18690 board[CASTLING][0] = c;
18692 board[CASTLING][1] = c;
18696 for(i=0; i<nrCastlingRights; i++)
18697 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18698 if(gameInfo.variant == VariantSChess)
18699 for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18700 if(fischer && shuffle) appData.fischerCastling = TRUE;
18701 if (appData.debugMode) {
18702 fprintf(debugFP, "FEN castling rights:");
18703 for(i=0; i<nrCastlingRights; i++)
18704 fprintf(debugFP, " %d", board[CASTLING][i]);
18705 fprintf(debugFP, "\n");
18708 while(*p==' ') p++;
18711 if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18713 /* read e.p. field in games that know e.p. capture */
18714 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
18715 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18716 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18718 p++; board[EP_STATUS] = EP_NONE;
18720 char c = *p++ - AAA;
18722 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18723 if(*p >= '0' && *p <='9') p++;
18724 board[EP_STATUS] = c;
18729 if(sscanf(p, "%d", &i) == 1) {
18730 FENrulePlies = i; /* 50-move ply counter */
18731 /* (The move number is still ignored) */
18738 EditPositionPasteFEN (char *fen)
18741 Board initial_position;
18743 if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18744 DisplayError(_("Bad FEN position in clipboard"), 0);
18747 int savedBlackPlaysFirst = blackPlaysFirst;
18748 EditPositionEvent();
18749 blackPlaysFirst = savedBlackPlaysFirst;
18750 CopyBoard(boards[0], initial_position);
18751 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18752 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18753 DisplayBothClocks();
18754 DrawPosition(FALSE, boards[currentMove]);
18759 static char cseq[12] = "\\ ";
18762 set_cont_sequence (char *new_seq)
18767 // handle bad attempts to set the sequence
18769 return 0; // acceptable error - no debug
18771 len = strlen(new_seq);
18772 ret = (len > 0) && (len < sizeof(cseq));
18774 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18775 else if (appData.debugMode)
18776 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18781 reformat a source message so words don't cross the width boundary. internal
18782 newlines are not removed. returns the wrapped size (no null character unless
18783 included in source message). If dest is NULL, only calculate the size required
18784 for the dest buffer. lp argument indicats line position upon entry, and it's
18785 passed back upon exit.
18788 wrap (char *dest, char *src, int count, int width, int *lp)
18790 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18792 cseq_len = strlen(cseq);
18793 old_line = line = *lp;
18794 ansi = len = clen = 0;
18796 for (i=0; i < count; i++)
18798 if (src[i] == '\033')
18801 // if we hit the width, back up
18802 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18804 // store i & len in case the word is too long
18805 old_i = i, old_len = len;
18807 // find the end of the last word
18808 while (i && src[i] != ' ' && src[i] != '\n')
18814 // word too long? restore i & len before splitting it
18815 if ((old_i-i+clen) >= width)
18822 if (i && src[i-1] == ' ')
18825 if (src[i] != ' ' && src[i] != '\n')
18832 // now append the newline and continuation sequence
18837 strncpy(dest+len, cseq, cseq_len);
18845 dest[len] = src[i];
18849 if (src[i] == '\n')
18854 if (dest && appData.debugMode)
18856 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18857 count, width, line, len, *lp);
18858 show_bytes(debugFP, src, count);
18859 fprintf(debugFP, "\ndest: ");
18860 show_bytes(debugFP, dest, len);
18861 fprintf(debugFP, "\n");
18863 *lp = dest ? line : old_line;
18868 // [HGM] vari: routines for shelving variations
18869 Boolean modeRestore = FALSE;
18872 PushInner (int firstMove, int lastMove)
18874 int i, j, nrMoves = lastMove - firstMove;
18876 // push current tail of game on stack
18877 savedResult[storedGames] = gameInfo.result;
18878 savedDetails[storedGames] = gameInfo.resultDetails;
18879 gameInfo.resultDetails = NULL;
18880 savedFirst[storedGames] = firstMove;
18881 savedLast [storedGames] = lastMove;
18882 savedFramePtr[storedGames] = framePtr;
18883 framePtr -= nrMoves; // reserve space for the boards
18884 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18885 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18886 for(j=0; j<MOVE_LEN; j++)
18887 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18888 for(j=0; j<2*MOVE_LEN; j++)
18889 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18890 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18891 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18892 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18893 pvInfoList[firstMove+i-1].depth = 0;
18894 commentList[framePtr+i] = commentList[firstMove+i];
18895 commentList[firstMove+i] = NULL;
18899 forwardMostMove = firstMove; // truncate game so we can start variation
18903 PushTail (int firstMove, int lastMove)
18905 if(appData.icsActive) { // only in local mode
18906 forwardMostMove = currentMove; // mimic old ICS behavior
18909 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18911 PushInner(firstMove, lastMove);
18912 if(storedGames == 1) GreyRevert(FALSE);
18913 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18917 PopInner (Boolean annotate)
18920 char buf[8000], moveBuf[20];
18922 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18923 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18924 nrMoves = savedLast[storedGames] - currentMove;
18927 if(!WhiteOnMove(currentMove))
18928 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18929 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18930 for(i=currentMove; i<forwardMostMove; i++) {
18932 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18933 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18934 strcat(buf, moveBuf);
18935 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18936 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18940 for(i=1; i<=nrMoves; i++) { // copy last variation back
18941 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18942 for(j=0; j<MOVE_LEN; j++)
18943 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18944 for(j=0; j<2*MOVE_LEN; j++)
18945 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18946 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18947 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18948 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18949 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18950 commentList[currentMove+i] = commentList[framePtr+i];
18951 commentList[framePtr+i] = NULL;
18953 if(annotate) AppendComment(currentMove+1, buf, FALSE);
18954 framePtr = savedFramePtr[storedGames];
18955 gameInfo.result = savedResult[storedGames];
18956 if(gameInfo.resultDetails != NULL) {
18957 free(gameInfo.resultDetails);
18959 gameInfo.resultDetails = savedDetails[storedGames];
18960 forwardMostMove = currentMove + nrMoves;
18964 PopTail (Boolean annotate)
18966 if(appData.icsActive) return FALSE; // only in local mode
18967 if(!storedGames) return FALSE; // sanity
18968 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18970 PopInner(annotate);
18971 if(currentMove < forwardMostMove) ForwardEvent(); else
18972 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18974 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18980 { // remove all shelved variations
18982 for(i=0; i<storedGames; i++) {
18983 if(savedDetails[i])
18984 free(savedDetails[i]);
18985 savedDetails[i] = NULL;
18987 for(i=framePtr; i<MAX_MOVES; i++) {
18988 if(commentList[i]) free(commentList[i]);
18989 commentList[i] = NULL;
18991 framePtr = MAX_MOVES-1;
18996 LoadVariation (int index, char *text)
18997 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18998 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18999 int level = 0, move;
19001 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
19002 // first find outermost bracketing variation
19003 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
19004 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
19005 if(*p == '{') wait = '}'; else
19006 if(*p == '[') wait = ']'; else
19007 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
19008 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
19010 if(*p == wait) wait = NULLCHAR; // closing ]} found
19013 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19014 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19015 end[1] = NULLCHAR; // clip off comment beyond variation
19016 ToNrEvent(currentMove-1);
19017 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19018 // kludge: use ParsePV() to append variation to game
19019 move = currentMove;
19020 ParsePV(start, TRUE, TRUE);
19021 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19022 ClearPremoveHighlights();
19024 ToNrEvent(currentMove+1);
19030 char *p, *q, buf[MSG_SIZ];
19031 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19032 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
19033 ParseArgsFromString(buf);
19034 ActivateTheme(TRUE); // also redo colors
19038 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19041 q = appData.themeNames;
19042 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
19043 if(appData.useBitmaps) {
19044 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
19045 appData.liteBackTextureFile, appData.darkBackTextureFile,
19046 appData.liteBackTextureMode,
19047 appData.darkBackTextureMode );
19049 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
19050 Col2Text(2), // lightSquareColor
19051 Col2Text(3) ); // darkSquareColor
19053 if(appData.useBorder) {
19054 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
19057 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
19059 if(appData.useFont) {
19060 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19061 appData.renderPiecesWithFont,
19062 appData.fontToPieceTable,
19063 Col2Text(9), // appData.fontBackColorWhite
19064 Col2Text(10) ); // appData.fontForeColorBlack
19066 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
19067 appData.pieceDirectory);
19068 if(!appData.pieceDirectory[0])
19069 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
19070 Col2Text(0), // whitePieceColor
19071 Col2Text(1) ); // blackPieceColor
19073 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19074 Col2Text(4), // highlightSquareColor
19075 Col2Text(5) ); // premoveHighlightColor
19076 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19077 if(insert != q) insert[-1] = NULLCHAR;
19078 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19081 ActivateTheme(FALSE);