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 */
266 extern int tinyLayout, smallLayout;
267 ChessProgramStats programStats;
268 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
270 static int exiting = 0; /* [HGM] moved to top */
271 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
272 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
273 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
274 int partnerHighlight[2];
275 Boolean partnerBoardValid = 0;
276 char partnerStatus[MSG_SIZ];
278 Boolean originalFlip;
279 Boolean twoBoards = 0;
280 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
281 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
282 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
283 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
284 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
285 int opponentKibitzes;
286 int lastSavedGame; /* [HGM] save: ID of game */
287 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
288 extern int chatCount;
290 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
291 char legal[BOARD_RANKS][BOARD_FILES]; /* [HGM] legal target squares */
292 char lastMsg[MSG_SIZ];
293 char lastTalker[MSG_SIZ];
294 ChessSquare pieceSweep = EmptySquare;
295 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
296 int promoDefaultAltered;
297 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
298 static int initPing = -1;
299 int border; /* [HGM] width of board rim, needed to size seek graph */
300 char bestMove[MSG_SIZ], avoidMove[MSG_SIZ];
301 int solvingTime, totalTime;
303 /* States for ics_getting_history */
305 #define H_REQUESTED 1
306 #define H_GOT_REQ_HEADER 2
307 #define H_GOT_UNREQ_HEADER 3
308 #define H_GETTING_MOVES 4
309 #define H_GOT_UNWANTED_HEADER 5
311 /* whosays values for GameEnds */
320 /* Maximum number of games in a cmail message */
321 #define CMAIL_MAX_GAMES 20
323 /* Different types of move when calling RegisterMove */
325 #define CMAIL_RESIGN 1
327 #define CMAIL_ACCEPT 3
329 /* Different types of result to remember for each game */
330 #define CMAIL_NOT_RESULT 0
331 #define CMAIL_OLD_RESULT 1
332 #define CMAIL_NEW_RESULT 2
334 /* Telnet protocol constants */
345 safeStrCpy (char *dst, const char *src, size_t count)
348 assert( dst != NULL );
349 assert( src != NULL );
352 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
353 if( i == count && dst[count-1] != NULLCHAR)
355 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
356 if(appData.debugMode)
357 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
363 /* Some compiler can't cast u64 to double
364 * This function do the job for us:
366 * We use the highest bit for cast, this only
367 * works if the highest bit is not
368 * in use (This should not happen)
370 * We used this for all compiler
373 u64ToDouble (u64 value)
376 u64 tmp = value & u64Const(0x7fffffffffffffff);
377 r = (double)(s64)tmp;
378 if (value & u64Const(0x8000000000000000))
379 r += 9.2233720368547758080e18; /* 2^63 */
383 /* Fake up flags for now, as we aren't keeping track of castling
384 availability yet. [HGM] Change of logic: the flag now only
385 indicates the type of castlings allowed by the rule of the game.
386 The actual rights themselves are maintained in the array
387 castlingRights, as part of the game history, and are not probed
393 int flags = F_ALL_CASTLE_OK;
394 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
395 switch (gameInfo.variant) {
397 flags &= ~F_ALL_CASTLE_OK;
398 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
399 flags |= F_IGNORE_CHECK;
401 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
404 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
406 case VariantKriegspiel:
407 flags |= F_KRIEGSPIEL_CAPTURE;
409 case VariantCapaRandom:
410 case VariantFischeRandom:
411 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
412 case VariantNoCastle:
413 case VariantShatranj:
418 flags &= ~F_ALL_CASTLE_OK;
421 case VariantChuChess:
423 flags |= F_NULL_MOVE;
428 if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
432 FILE *gameFileFP, *debugFP, *serverFP;
433 char *currentDebugFile; // [HGM] debug split: to remember name
436 [AS] Note: sometimes, the sscanf() function is used to parse the input
437 into a fixed-size buffer. Because of this, we must be prepared to
438 receive strings as long as the size of the input buffer, which is currently
439 set to 4K for Windows and 8K for the rest.
440 So, we must either allocate sufficiently large buffers here, or
441 reduce the size of the input buffer in the input reading part.
444 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
445 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
446 char thinkOutput1[MSG_SIZ*10];
447 char promoRestrict[MSG_SIZ];
449 ChessProgramState first, second, pairing;
451 /* premove variables */
454 int premoveFromX = 0;
455 int premoveFromY = 0;
456 int premovePromoChar = 0;
458 Boolean alarmSounded;
459 /* end premove variables */
461 char *ics_prefix = "$";
462 enum ICS_TYPE ics_type = ICS_GENERIC;
464 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
465 int pauseExamForwardMostMove = 0;
466 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
467 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
468 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
469 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
470 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
471 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
472 int whiteFlag = FALSE, blackFlag = FALSE;
473 int userOfferedDraw = FALSE;
474 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
475 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
476 int cmailMoveType[CMAIL_MAX_GAMES];
477 long ics_clock_paused = 0;
478 ProcRef icsPR = NoProc, cmailPR = NoProc;
479 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
480 GameMode gameMode = BeginningOfGame;
481 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
482 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
483 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
484 int hiddenThinkOutputState = 0; /* [AS] */
485 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
486 int adjudicateLossPlies = 6;
487 char white_holding[64], black_holding[64];
488 TimeMark lastNodeCountTime;
489 long lastNodeCount=0;
490 int shiftKey, controlKey; // [HGM] set by mouse handler
492 int have_sent_ICS_logon = 0;
494 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
495 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
496 Boolean adjustedClock;
497 long timeControl_2; /* [AS] Allow separate time controls */
498 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
499 long timeRemaining[2][MAX_MOVES];
500 int matchGame = 0, nextGame = 0, roundNr = 0;
501 Boolean waitingForGame = FALSE, startingEngine = FALSE;
502 TimeMark programStartTime, pauseStart;
503 char ics_handle[MSG_SIZ];
504 int have_set_title = 0;
506 /* animateTraining preserves the state of appData.animate
507 * when Training mode is activated. This allows the
508 * response to be animated when appData.animate == TRUE and
509 * appData.animateDragging == TRUE.
511 Boolean animateTraining;
517 Board boards[MAX_MOVES];
518 /* [HGM] Following 7 needed for accurate legality tests: */
519 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
520 unsigned char initialRights[BOARD_FILES];
521 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
522 int initialRulePlies, FENrulePlies;
523 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
525 Boolean shuffleOpenings;
526 int mute; // mute all sounds
528 // [HGM] vari: next 12 to save and restore variations
529 #define MAX_VARIATIONS 10
530 int framePtr = MAX_MOVES-1; // points to free stack entry
532 int savedFirst[MAX_VARIATIONS];
533 int savedLast[MAX_VARIATIONS];
534 int savedFramePtr[MAX_VARIATIONS];
535 char *savedDetails[MAX_VARIATIONS];
536 ChessMove savedResult[MAX_VARIATIONS];
538 void PushTail P((int firstMove, int lastMove));
539 Boolean PopTail P((Boolean annotate));
540 void PushInner P((int firstMove, int lastMove));
541 void PopInner P((Boolean annotate));
542 void CleanupTail P((void));
544 ChessSquare FIDEArray[2][BOARD_FILES] = {
545 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
546 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
547 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
548 BlackKing, BlackBishop, BlackKnight, BlackRook }
551 ChessSquare twoKingsArray[2][BOARD_FILES] = {
552 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
553 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
554 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
555 BlackKing, BlackKing, BlackKnight, BlackRook }
558 ChessSquare KnightmateArray[2][BOARD_FILES] = {
559 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
560 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
561 { BlackRook, BlackMan, BlackBishop, BlackQueen,
562 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
565 ChessSquare SpartanArray[2][BOARD_FILES] = {
566 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
567 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
568 { BlackAlfil, BlackDragon, BlackKing, BlackTower,
569 BlackTower, BlackKing, BlackAngel, BlackAlfil }
572 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
573 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
574 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
575 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
576 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
579 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
580 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
581 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
582 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
583 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
586 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
587 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
588 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
589 { BlackRook, BlackKnight, BlackMan, BlackFerz,
590 BlackKing, BlackMan, BlackKnight, BlackRook }
593 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
594 { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
595 WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
596 { BlackRook, BlackKnight, BlackMan, BlackFerz,
597 BlackKing, BlackMan, BlackKnight, BlackRook }
600 ChessSquare lionArray[2][BOARD_FILES] = {
601 { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
602 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
603 { BlackRook, BlackLion, BlackBishop, BlackQueen,
604 BlackKing, BlackBishop, BlackKnight, BlackRook }
608 #if (BOARD_FILES>=10)
609 ChessSquare ShogiArray[2][BOARD_FILES] = {
610 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
611 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
612 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
613 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
616 ChessSquare XiangqiArray[2][BOARD_FILES] = {
617 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
618 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
619 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
620 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
623 ChessSquare CapablancaArray[2][BOARD_FILES] = {
624 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
625 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
626 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
627 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
630 ChessSquare GreatArray[2][BOARD_FILES] = {
631 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
632 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
633 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
634 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
637 ChessSquare JanusArray[2][BOARD_FILES] = {
638 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
639 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
640 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
641 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
644 ChessSquare GrandArray[2][BOARD_FILES] = {
645 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
646 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
647 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
648 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
651 ChessSquare ChuChessArray[2][BOARD_FILES] = {
652 { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
653 WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
654 { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
655 BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
659 ChessSquare GothicArray[2][BOARD_FILES] = {
660 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
661 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
662 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
663 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
666 #define GothicArray CapablancaArray
670 ChessSquare FalconArray[2][BOARD_FILES] = {
671 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
672 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
673 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
674 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
677 #define FalconArray CapablancaArray
680 #else // !(BOARD_FILES>=10)
681 #define XiangqiPosition FIDEArray
682 #define CapablancaArray FIDEArray
683 #define GothicArray FIDEArray
684 #define GreatArray FIDEArray
685 #endif // !(BOARD_FILES>=10)
687 #if (BOARD_FILES>=12)
688 ChessSquare CourierArray[2][BOARD_FILES] = {
689 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
690 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
691 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
692 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
694 ChessSquare ChuArray[6][BOARD_FILES] = {
695 { WhiteLance, WhiteCat, WhiteCopper, WhiteFerz, WhiteWazir, WhiteKing,
696 WhiteAlfil, WhiteWazir, WhiteFerz, WhiteCopper, WhiteCat, WhiteLance },
697 { BlackLance, BlackCat, BlackCopper, BlackFerz, BlackWazir, BlackAlfil,
698 BlackKing, BlackWazir, BlackFerz, BlackCopper, BlackCat, BlackLance },
699 { WhiteAxe, EmptySquare, WhiteBishop, EmptySquare, WhiteClaw, WhiteMarshall,
700 WhiteAngel, WhiteClaw, EmptySquare, WhiteBishop, EmptySquare, WhiteAxe },
701 { BlackAxe, EmptySquare, BlackBishop, EmptySquare, BlackClaw, BlackAngel,
702 BlackMarshall, BlackClaw, EmptySquare, BlackBishop, EmptySquare, BlackAxe },
703 { WhiteDagger, WhiteSword, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
704 WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSword, WhiteDagger },
705 { BlackDagger, BlackSword, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
706 BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSword, BlackDagger }
708 #else // !(BOARD_FILES>=12)
709 #define CourierArray CapablancaArray
710 #define ChuArray CapablancaArray
711 #endif // !(BOARD_FILES>=12)
714 Board initialPosition;
717 /* Convert str to a rating. Checks for special cases of "----",
719 "++++", etc. Also strips ()'s */
721 string_to_rating (char *str)
723 while(*str && !isdigit(*str)) ++str;
725 return 0; /* One of the special "no rating" cases */
733 /* Init programStats */
734 programStats.movelist[0] = 0;
735 programStats.depth = 0;
736 programStats.nr_moves = 0;
737 programStats.moves_left = 0;
738 programStats.nodes = 0;
739 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
740 programStats.score = 0;
741 programStats.got_only_move = 0;
742 programStats.got_fail = 0;
743 programStats.line_is_book = 0;
748 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
749 if (appData.firstPlaysBlack) {
750 first.twoMachinesColor = "black\n";
751 second.twoMachinesColor = "white\n";
753 first.twoMachinesColor = "white\n";
754 second.twoMachinesColor = "black\n";
757 first.other = &second;
758 second.other = &first;
761 if(appData.timeOddsMode) {
762 norm = appData.timeOdds[0];
763 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
765 first.timeOdds = appData.timeOdds[0]/norm;
766 second.timeOdds = appData.timeOdds[1]/norm;
769 if(programVersion) free(programVersion);
770 if (appData.noChessProgram) {
771 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
772 sprintf(programVersion, "%s", PACKAGE_STRING);
774 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
775 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
776 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
781 UnloadEngine (ChessProgramState *cps)
783 /* Kill off first chess program */
784 if (cps->isr != NULL)
785 RemoveInputSource(cps->isr);
788 if (cps->pr != NoProc) {
790 DoSleep( appData.delayBeforeQuit );
791 SendToProgram("quit\n", cps);
792 DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
795 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
799 ClearOptions (ChessProgramState *cps)
802 cps->nrOptions = cps->comboCnt = 0;
803 for(i=0; i<MAX_OPTIONS; i++) {
804 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
805 cps->option[i].textValue = 0;
809 char *engineNames[] = {
810 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
811 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
813 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
814 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
819 InitEngine (ChessProgramState *cps, int n)
820 { // [HGM] all engine initialiation put in a function that does one engine
824 cps->which = engineNames[n];
825 cps->maybeThinking = FALSE;
829 cps->sendDrawOffers = 1;
831 cps->program = appData.chessProgram[n];
832 cps->host = appData.host[n];
833 cps->dir = appData.directory[n];
834 cps->initString = appData.engInitString[n];
835 cps->computerString = appData.computerString[n];
836 cps->useSigint = TRUE;
837 cps->useSigterm = TRUE;
838 cps->reuse = appData.reuse[n];
839 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
840 cps->useSetboard = FALSE;
842 cps->usePing = FALSE;
845 cps->usePlayother = FALSE;
846 cps->useColors = TRUE;
847 cps->useUsermove = FALSE;
848 cps->sendICS = FALSE;
849 cps->sendName = appData.icsActive;
850 cps->sdKludge = FALSE;
851 cps->stKludge = FALSE;
852 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
853 TidyProgramName(cps->program, cps->host, cps->tidy);
855 ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
856 cps->analysisSupport = 2; /* detect */
857 cps->analyzing = FALSE;
858 cps->initDone = FALSE;
860 cps->pseudo = appData.pseudo[n];
862 /* New features added by Tord: */
863 cps->useFEN960 = FALSE;
864 cps->useOOCastle = TRUE;
865 /* End of new features added by Tord. */
866 cps->fenOverride = appData.fenOverride[n];
868 /* [HGM] time odds: set factor for each machine */
869 cps->timeOdds = appData.timeOdds[n];
871 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
872 cps->accumulateTC = appData.accumulateTC[n];
873 cps->maxNrOfSessions = 1;
878 cps->drawDepth = appData.drawDepth[n];
879 cps->supportsNPS = UNKNOWN;
880 cps->memSize = FALSE;
881 cps->maxCores = FALSE;
882 ASSIGN(cps->egtFormats, "");
885 cps->optionSettings = appData.engOptions[n];
887 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
888 cps->isUCI = appData.isUCI[n]; /* [AS] */
889 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
892 if (appData.protocolVersion[n] > PROTOVER
893 || appData.protocolVersion[n] < 1)
898 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
899 appData.protocolVersion[n]);
900 if( (len >= MSG_SIZ) && appData.debugMode )
901 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
903 DisplayFatalError(buf, 0, 2);
907 cps->protocolVersion = appData.protocolVersion[n];
910 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
911 ParseFeatures(appData.featureDefaults, cps);
914 ChessProgramState *savCps;
922 if(WaitForEngine(savCps, LoadEngine)) return;
923 CommonEngineInit(); // recalculate time odds
924 if(gameInfo.variant != StringToVariant(appData.variant)) {
925 // we changed variant when loading the engine; this forces us to reset
926 Reset(TRUE, savCps != &first);
927 oldMode = BeginningOfGame; // to prevent restoring old mode
929 InitChessProgram(savCps, FALSE);
930 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
931 DisplayMessage("", "");
932 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
933 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
936 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
940 ReplaceEngine (ChessProgramState *cps, int n)
942 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
944 if(oldMode != BeginningOfGame) EditGameEvent();
947 appData.noChessProgram = FALSE;
948 appData.clockMode = TRUE;
951 if(n) return; // only startup first engine immediately; second can wait
952 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
956 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
957 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
959 static char resetOptions[] =
960 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
961 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
962 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
963 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
966 FloatToFront(char **list, char *engineLine)
968 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
970 if(appData.recentEngines <= 0) return;
971 TidyProgramName(engineLine, "localhost", tidy+1);
972 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
973 strncpy(buf+1, *list, MSG_SIZ-50);
974 if(p = strstr(buf, tidy)) { // tidy name appears in list
975 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
976 while(*p++ = *++q); // squeeze out
978 strcat(tidy, buf+1); // put list behind tidy name
979 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
980 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
981 ASSIGN(*list, tidy+1);
984 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
987 Load (ChessProgramState *cps, int i)
989 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
990 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
991 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
992 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
993 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
994 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
995 appData.firstProtocolVersion = PROTOVER;
996 ParseArgsFromString(buf);
998 ReplaceEngine(cps, i);
999 FloatToFront(&appData.recentEngineList, engineLine);
1000 if(gameMode == BeginningOfGame) Reset(TRUE, TRUE);
1004 while(q = strchr(p, SLASH)) p = q+1;
1005 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1006 if(engineDir[0] != NULLCHAR) {
1007 ASSIGN(appData.directory[i], engineDir); p = engineName;
1008 } else if(p != engineName) { // derive directory from engine path, when not given
1010 ASSIGN(appData.directory[i], engineName);
1012 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1013 } else { ASSIGN(appData.directory[i], "."); }
1014 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1016 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1017 snprintf(command, MSG_SIZ, "%s %s", p, params);
1020 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1021 ASSIGN(appData.chessProgram[i], p);
1022 appData.isUCI[i] = isUCI;
1023 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1024 appData.hasOwnBookUCI[i] = hasBook;
1025 if(!nickName[0]) useNick = FALSE;
1026 if(useNick) ASSIGN(appData.pgnName[i], nickName);
1030 q = firstChessProgramNames;
1031 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1032 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1033 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1034 quote, p, quote, appData.directory[i],
1035 useNick ? " -fn \"" : "",
1036 useNick ? nickName : "",
1037 useNick ? "\"" : "",
1038 v1 ? " -firstProtocolVersion 1" : "",
1039 hasBook ? "" : " -fNoOwnBookUCI",
1040 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1041 storeVariant ? " -variant " : "",
1042 storeVariant ? VariantName(gameInfo.variant) : "");
1043 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1044 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1045 if(insert != q) insert[-1] = NULLCHAR;
1046 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1048 FloatToFront(&appData.recentEngineList, buf);
1050 ReplaceEngine(cps, i);
1056 int matched, min, sec;
1058 * Parse timeControl resource
1060 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1061 appData.movesPerSession)) {
1063 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1064 DisplayFatalError(buf, 0, 2);
1068 * Parse searchTime resource
1070 if (*appData.searchTime != NULLCHAR) {
1071 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1073 searchTime = min * 60;
1074 } else if (matched == 2) {
1075 searchTime = min * 60 + sec;
1078 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1079 DisplayFatalError(buf, 0, 2);
1088 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1089 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1091 GetTimeMark(&programStartTime);
1092 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1093 appData.seedBase = random() + (random()<<15);
1094 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1096 ClearProgramStats();
1097 programStats.ok_to_send = 1;
1098 programStats.seen_stat = 0;
1101 * Initialize game list
1107 * Internet chess server status
1109 if (appData.icsActive) {
1110 appData.matchMode = FALSE;
1111 appData.matchGames = 0;
1113 appData.noChessProgram = !appData.zippyPlay;
1115 appData.zippyPlay = FALSE;
1116 appData.zippyTalk = FALSE;
1117 appData.noChessProgram = TRUE;
1119 if (*appData.icsHelper != NULLCHAR) {
1120 appData.useTelnet = TRUE;
1121 appData.telnetProgram = appData.icsHelper;
1124 appData.zippyTalk = appData.zippyPlay = FALSE;
1127 /* [AS] Initialize pv info list [HGM] and game state */
1131 for( i=0; i<=framePtr; i++ ) {
1132 pvInfoList[i].depth = -1;
1133 boards[i][EP_STATUS] = EP_NONE;
1134 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1140 /* [AS] Adjudication threshold */
1141 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1143 InitEngine(&first, 0);
1144 InitEngine(&second, 1);
1147 pairing.which = "pairing"; // pairing engine
1148 pairing.pr = NoProc;
1150 pairing.program = appData.pairingEngine;
1151 pairing.host = "localhost";
1154 if (appData.icsActive) {
1155 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1156 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1157 appData.clockMode = FALSE;
1158 first.sendTime = second.sendTime = 0;
1162 /* Override some settings from environment variables, for backward
1163 compatibility. Unfortunately it's not feasible to have the env
1164 vars just set defaults, at least in xboard. Ugh.
1166 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1171 if (!appData.icsActive) {
1175 /* Check for variants that are supported only in ICS mode,
1176 or not at all. Some that are accepted here nevertheless
1177 have bugs; see comments below.
1179 VariantClass variant = StringToVariant(appData.variant);
1181 case VariantBughouse: /* need four players and two boards */
1182 case VariantKriegspiel: /* need to hide pieces and move details */
1183 /* case VariantFischeRandom: (Fabien: moved below) */
1184 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1185 if( (len >= MSG_SIZ) && appData.debugMode )
1186 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1188 DisplayFatalError(buf, 0, 2);
1191 case VariantUnknown:
1192 case VariantLoadable:
1202 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1203 if( (len >= MSG_SIZ) && appData.debugMode )
1204 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1206 DisplayFatalError(buf, 0, 2);
1209 case VariantNormal: /* definitely works! */
1210 if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1211 safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1214 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1215 case VariantFairy: /* [HGM] TestLegality definitely off! */
1216 case VariantGothic: /* [HGM] should work */
1217 case VariantCapablanca: /* [HGM] should work */
1218 case VariantCourier: /* [HGM] initial forced moves not implemented */
1219 case VariantShogi: /* [HGM] could still mate with pawn drop */
1220 case VariantChu: /* [HGM] experimental */
1221 case VariantKnightmate: /* [HGM] should work */
1222 case VariantCylinder: /* [HGM] untested */
1223 case VariantFalcon: /* [HGM] untested */
1224 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1225 offboard interposition not understood */
1226 case VariantWildCastle: /* pieces not automatically shuffled */
1227 case VariantNoCastle: /* pieces not automatically shuffled */
1228 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1229 case VariantLosers: /* should work except for win condition,
1230 and doesn't know captures are mandatory */
1231 case VariantSuicide: /* should work except for win condition,
1232 and doesn't know captures are mandatory */
1233 case VariantGiveaway: /* should work except for win condition,
1234 and doesn't know captures are mandatory */
1235 case VariantTwoKings: /* should work */
1236 case VariantAtomic: /* should work except for win condition */
1237 case Variant3Check: /* should work except for win condition */
1238 case VariantShatranj: /* should work except for all win conditions */
1239 case VariantMakruk: /* should work except for draw countdown */
1240 case VariantASEAN : /* should work except for draw countdown */
1241 case VariantBerolina: /* might work if TestLegality is off */
1242 case VariantCapaRandom: /* should work */
1243 case VariantJanus: /* should work */
1244 case VariantSuper: /* experimental */
1245 case VariantGreat: /* experimental, requires legality testing to be off */
1246 case VariantSChess: /* S-Chess, should work */
1247 case VariantGrand: /* should work */
1248 case VariantSpartan: /* should work */
1249 case VariantLion: /* should work */
1250 case VariantChuChess: /* should work */
1258 NextIntegerFromString (char ** str, long * value)
1263 while( *s == ' ' || *s == '\t' ) {
1269 if( *s >= '0' && *s <= '9' ) {
1270 while( *s >= '0' && *s <= '9' ) {
1271 *value = *value * 10 + (*s - '0');
1284 NextTimeControlFromString (char ** str, long * value)
1287 int result = NextIntegerFromString( str, &temp );
1290 *value = temp * 60; /* Minutes */
1291 if( **str == ':' ) {
1293 result = NextIntegerFromString( str, &temp );
1294 *value += temp; /* Seconds */
1302 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1303 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1304 int result = -1, type = 0; long temp, temp2;
1306 if(**str != ':') return -1; // old params remain in force!
1308 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1309 if( NextIntegerFromString( str, &temp ) ) return -1;
1310 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1313 /* time only: incremental or sudden-death time control */
1314 if(**str == '+') { /* increment follows; read it */
1316 if(**str == '!') type = *(*str)++; // Bronstein TC
1317 if(result = NextIntegerFromString( str, &temp2)) return -1;
1318 *inc = temp2 * 1000;
1319 if(**str == '.') { // read fraction of increment
1320 char *start = ++(*str);
1321 if(result = NextIntegerFromString( str, &temp2)) return -1;
1323 while(start++ < *str) temp2 /= 10;
1327 *moves = 0; *tc = temp * 1000; *incType = type;
1331 (*str)++; /* classical time control */
1332 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1344 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1345 { /* [HGM] get time to add from the multi-session time-control string */
1346 int incType, moves=1; /* kludge to force reading of first session */
1347 long time, increment;
1350 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1352 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1353 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1354 if(movenr == -1) return time; /* last move before new session */
1355 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1356 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1357 if(!moves) return increment; /* current session is incremental */
1358 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1359 } while(movenr >= -1); /* try again for next session */
1361 return 0; // no new time quota on this move
1365 ParseTimeControl (char *tc, float ti, int mps)
1369 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1372 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1373 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1374 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1378 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1380 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1383 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1385 snprintf(buf, MSG_SIZ, ":%s", mytc);
1387 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1389 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1394 /* Parse second time control */
1397 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1405 timeControl_2 = tc2 * 1000;
1415 timeControl = tc1 * 1000;
1418 timeIncrement = ti * 1000; /* convert to ms */
1419 movesPerSession = 0;
1422 movesPerSession = mps;
1430 if (appData.debugMode) {
1431 # ifdef __GIT_VERSION
1432 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1434 fprintf(debugFP, "Version: %s\n", programVersion);
1437 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1439 set_cont_sequence(appData.wrapContSeq);
1440 if (appData.matchGames > 0) {
1441 appData.matchMode = TRUE;
1442 } else if (appData.matchMode) {
1443 appData.matchGames = 1;
1445 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1446 appData.matchGames = appData.sameColorGames;
1447 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1448 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1449 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1452 if (appData.noChessProgram || first.protocolVersion == 1) {
1455 /* kludge: allow timeout for initial "feature" commands */
1457 DisplayMessage("", _("Starting chess program"));
1458 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1463 CalculateIndex (int index, int gameNr)
1464 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1466 if(index > 0) return index; // fixed nmber
1467 if(index == 0) return 1;
1468 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1469 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1474 LoadGameOrPosition (int gameNr)
1475 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1476 if (*appData.loadGameFile != NULLCHAR) {
1477 if (!LoadGameFromFile(appData.loadGameFile,
1478 CalculateIndex(appData.loadGameIndex, gameNr),
1479 appData.loadGameFile, FALSE)) {
1480 DisplayFatalError(_("Bad game file"), 0, 1);
1483 } else if (*appData.loadPositionFile != NULLCHAR) {
1484 if (!LoadPositionFromFile(appData.loadPositionFile,
1485 CalculateIndex(appData.loadPositionIndex, gameNr),
1486 appData.loadPositionFile)) {
1487 DisplayFatalError(_("Bad position file"), 0, 1);
1495 ReserveGame (int gameNr, char resChar)
1497 FILE *tf = fopen(appData.tourneyFile, "r+");
1498 char *p, *q, c, buf[MSG_SIZ];
1499 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1500 safeStrCpy(buf, lastMsg, MSG_SIZ);
1501 DisplayMessage(_("Pick new game"), "");
1502 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1503 ParseArgsFromFile(tf);
1504 p = q = appData.results;
1505 if(appData.debugMode) {
1506 char *r = appData.participants;
1507 fprintf(debugFP, "results = '%s'\n", p);
1508 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1509 fprintf(debugFP, "\n");
1511 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1513 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1514 safeStrCpy(q, p, strlen(p) + 2);
1515 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1516 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1517 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1518 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1521 fseek(tf, -(strlen(p)+4), SEEK_END);
1523 if(c != '"') // depending on DOS or Unix line endings we can be one off
1524 fseek(tf, -(strlen(p)+2), SEEK_END);
1525 else fseek(tf, -(strlen(p)+3), SEEK_END);
1526 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1527 DisplayMessage(buf, "");
1528 free(p); appData.results = q;
1529 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1530 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1531 int round = appData.defaultMatchGames * appData.tourneyType;
1532 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1533 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1534 UnloadEngine(&first); // next game belongs to other pairing;
1535 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1537 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1541 MatchEvent (int mode)
1542 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1544 if(matchMode) { // already in match mode: switch it off
1546 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1549 // if(gameMode != BeginningOfGame) {
1550 // DisplayError(_("You can only start a match from the initial position."), 0);
1554 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1555 /* Set up machine vs. machine match */
1557 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1558 if(appData.tourneyFile[0]) {
1560 if(nextGame > appData.matchGames) {
1562 if(strchr(appData.results, '*') == NULL) {
1564 appData.tourneyCycles++;
1565 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1567 NextTourneyGame(-1, &dummy);
1569 if(nextGame <= appData.matchGames) {
1570 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1572 ScheduleDelayedEvent(NextMatchGame, 10000);
1577 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1578 DisplayError(buf, 0);
1579 appData.tourneyFile[0] = 0;
1583 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1584 DisplayFatalError(_("Can't have a match with no chess programs"),
1589 matchGame = roundNr = 1;
1590 first.matchWins = second.matchWins = totalTime = 0; // [HGM] match: needed in later matches
1594 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1597 InitBackEnd3 P((void))
1599 GameMode initialMode;
1603 if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode && // mode involves only first engine
1604 !strcmp(appData.variant, "normal") && // no explicit variant request
1605 appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 && // no size overrides requested
1606 !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") && // but 'normal' won't work with engine
1607 !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1608 char c, *q = first.variants, *p = strchr(q, ',');
1609 if(p) *p = NULLCHAR;
1610 if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1612 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1613 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1614 ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1615 Reset(TRUE, FALSE); // and re-initialize
1620 InitChessProgram(&first, startedFromSetupPosition);
1622 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1623 free(programVersion);
1624 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1625 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1626 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1629 if (appData.icsActive) {
1631 /* [DM] Make a console window if needed [HGM] merged ifs */
1637 if (*appData.icsCommPort != NULLCHAR)
1638 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1639 appData.icsCommPort);
1641 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1642 appData.icsHost, appData.icsPort);
1644 if( (len >= MSG_SIZ) && appData.debugMode )
1645 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1647 DisplayFatalError(buf, err, 1);
1652 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1654 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1655 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1656 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1657 } else if (appData.noChessProgram) {
1663 if (*appData.cmailGameName != NULLCHAR) {
1665 OpenLoopback(&cmailPR);
1667 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1671 DisplayMessage("", "");
1672 if (StrCaseCmp(appData.initialMode, "") == 0) {
1673 initialMode = BeginningOfGame;
1674 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1675 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1676 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1677 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1680 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1681 initialMode = TwoMachinesPlay;
1682 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1683 initialMode = AnalyzeFile;
1684 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1685 initialMode = AnalyzeMode;
1686 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1687 initialMode = MachinePlaysWhite;
1688 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1689 initialMode = MachinePlaysBlack;
1690 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1691 initialMode = EditGame;
1692 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1693 initialMode = EditPosition;
1694 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1695 initialMode = Training;
1697 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1698 if( (len >= MSG_SIZ) && appData.debugMode )
1699 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1701 DisplayFatalError(buf, 0, 2);
1705 if (appData.matchMode) {
1706 if(appData.tourneyFile[0]) { // start tourney from command line
1708 if(f = fopen(appData.tourneyFile, "r")) {
1709 ParseArgsFromFile(f); // make sure tourney parmeters re known
1711 appData.clockMode = TRUE;
1713 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1716 } else if (*appData.cmailGameName != NULLCHAR) {
1717 /* Set up cmail mode */
1718 ReloadCmailMsgEvent(TRUE);
1720 /* Set up other modes */
1721 if (initialMode == AnalyzeFile) {
1722 if (*appData.loadGameFile == NULLCHAR) {
1723 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1727 if (*appData.loadGameFile != NULLCHAR) {
1728 (void) LoadGameFromFile(appData.loadGameFile,
1729 appData.loadGameIndex,
1730 appData.loadGameFile, TRUE);
1731 } else if (*appData.loadPositionFile != NULLCHAR) {
1732 (void) LoadPositionFromFile(appData.loadPositionFile,
1733 appData.loadPositionIndex,
1734 appData.loadPositionFile);
1735 /* [HGM] try to make self-starting even after FEN load */
1736 /* to allow automatic setup of fairy variants with wtm */
1737 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1738 gameMode = BeginningOfGame;
1739 setboardSpoiledMachineBlack = 1;
1741 /* [HGM] loadPos: make that every new game uses the setup */
1742 /* from file as long as we do not switch variant */
1743 if(!blackPlaysFirst) {
1744 startedFromPositionFile = TRUE;
1745 CopyBoard(filePosition, boards[0]);
1746 CopyBoard(initialPosition, boards[0]);
1748 } else if(*appData.fen != NULLCHAR) {
1749 if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
1750 startedFromPositionFile = TRUE;
1754 if (initialMode == AnalyzeMode) {
1755 if (appData.noChessProgram) {
1756 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1759 if (appData.icsActive) {
1760 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1764 } else if (initialMode == AnalyzeFile) {
1765 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1766 ShowThinkingEvent();
1768 AnalysisPeriodicEvent(1);
1769 } else if (initialMode == MachinePlaysWhite) {
1770 if (appData.noChessProgram) {
1771 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1775 if (appData.icsActive) {
1776 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1780 MachineWhiteEvent();
1781 } else if (initialMode == MachinePlaysBlack) {
1782 if (appData.noChessProgram) {
1783 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1787 if (appData.icsActive) {
1788 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1792 MachineBlackEvent();
1793 } else if (initialMode == TwoMachinesPlay) {
1794 if (appData.noChessProgram) {
1795 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1799 if (appData.icsActive) {
1800 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1805 } else if (initialMode == EditGame) {
1807 } else if (initialMode == EditPosition) {
1808 EditPositionEvent();
1809 } else if (initialMode == Training) {
1810 if (*appData.loadGameFile == NULLCHAR) {
1811 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1820 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1822 DisplayBook(current+1);
1824 MoveHistorySet( movelist, first, last, current, pvInfoList );
1826 EvalGraphSet( first, last, current, pvInfoList );
1828 MakeEngineOutputTitle();
1832 * Establish will establish a contact to a remote host.port.
1833 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1834 * used to talk to the host.
1835 * Returns 0 if okay, error code if not.
1842 if (*appData.icsCommPort != NULLCHAR) {
1843 /* Talk to the host through a serial comm port */
1844 return OpenCommPort(appData.icsCommPort, &icsPR);
1846 } else if (*appData.gateway != NULLCHAR) {
1847 if (*appData.remoteShell == NULLCHAR) {
1848 /* Use the rcmd protocol to run telnet program on a gateway host */
1849 snprintf(buf, sizeof(buf), "%s %s %s",
1850 appData.telnetProgram, appData.icsHost, appData.icsPort);
1851 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1854 /* Use the rsh program to run telnet program on a gateway host */
1855 if (*appData.remoteUser == NULLCHAR) {
1856 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1857 appData.gateway, appData.telnetProgram,
1858 appData.icsHost, appData.icsPort);
1860 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1861 appData.remoteShell, appData.gateway,
1862 appData.remoteUser, appData.telnetProgram,
1863 appData.icsHost, appData.icsPort);
1865 return StartChildProcess(buf, "", &icsPR);
1868 } else if (appData.useTelnet) {
1869 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1872 /* TCP socket interface differs somewhat between
1873 Unix and NT; handle details in the front end.
1875 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1880 EscapeExpand (char *p, char *q)
1881 { // [HGM] initstring: routine to shape up string arguments
1882 while(*p++ = *q++) if(p[-1] == '\\')
1884 case 'n': p[-1] = '\n'; break;
1885 case 'r': p[-1] = '\r'; break;
1886 case 't': p[-1] = '\t'; break;
1887 case '\\': p[-1] = '\\'; break;
1888 case 0: *p = 0; return;
1889 default: p[-1] = q[-1]; break;
1894 show_bytes (FILE *fp, char *buf, int count)
1897 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1898 fprintf(fp, "\\%03o", *buf & 0xff);
1907 /* Returns an errno value */
1909 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1911 char buf[8192], *p, *q, *buflim;
1912 int left, newcount, outcount;
1914 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1915 *appData.gateway != NULLCHAR) {
1916 if (appData.debugMode) {
1917 fprintf(debugFP, ">ICS: ");
1918 show_bytes(debugFP, message, count);
1919 fprintf(debugFP, "\n");
1921 return OutputToProcess(pr, message, count, outError);
1924 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1931 if (appData.debugMode) {
1932 fprintf(debugFP, ">ICS: ");
1933 show_bytes(debugFP, buf, newcount);
1934 fprintf(debugFP, "\n");
1936 outcount = OutputToProcess(pr, buf, newcount, outError);
1937 if (outcount < newcount) return -1; /* to be sure */
1944 } else if (((unsigned char) *p) == TN_IAC) {
1945 *q++ = (char) TN_IAC;
1952 if (appData.debugMode) {
1953 fprintf(debugFP, ">ICS: ");
1954 show_bytes(debugFP, buf, newcount);
1955 fprintf(debugFP, "\n");
1957 outcount = OutputToProcess(pr, buf, newcount, outError);
1958 if (outcount < newcount) return -1; /* to be sure */
1963 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1965 int outError, outCount;
1966 static int gotEof = 0;
1969 /* Pass data read from player on to ICS */
1972 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1973 if (outCount < count) {
1974 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1976 if(have_sent_ICS_logon == 2) {
1977 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1978 fprintf(ini, "%s", message);
1979 have_sent_ICS_logon = 3;
1981 have_sent_ICS_logon = 1;
1982 } else if(have_sent_ICS_logon == 3) {
1983 fprintf(ini, "%s", message);
1985 have_sent_ICS_logon = 1;
1987 } else if (count < 0) {
1988 RemoveInputSource(isr);
1989 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1990 } else if (gotEof++ > 0) {
1991 RemoveInputSource(isr);
1992 DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
1998 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1999 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
2000 connectionAlive = FALSE; // only sticks if no response to 'date' command.
2001 SendToICS("date\n");
2002 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
2005 /* added routine for printf style output to ics */
2007 ics_printf (char *format, ...)
2009 char buffer[MSG_SIZ];
2012 va_start(args, format);
2013 vsnprintf(buffer, sizeof(buffer), format, args);
2014 buffer[sizeof(buffer)-1] = '\0';
2022 int count, outCount, outError;
2024 if (icsPR == NoProc) return;
2027 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2028 if (outCount < count) {
2029 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2033 /* This is used for sending logon scripts to the ICS. Sending
2034 without a delay causes problems when using timestamp on ICC
2035 (at least on my machine). */
2037 SendToICSDelayed (char *s, long msdelay)
2039 int count, outCount, outError;
2041 if (icsPR == NoProc) return;
2044 if (appData.debugMode) {
2045 fprintf(debugFP, ">ICS: ");
2046 show_bytes(debugFP, s, count);
2047 fprintf(debugFP, "\n");
2049 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2051 if (outCount < count) {
2052 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2057 /* Remove all highlighting escape sequences in s
2058 Also deletes any suffix starting with '('
2061 StripHighlightAndTitle (char *s)
2063 static char retbuf[MSG_SIZ];
2066 while (*s != NULLCHAR) {
2067 while (*s == '\033') {
2068 while (*s != NULLCHAR && !isalpha(*s)) s++;
2069 if (*s != NULLCHAR) s++;
2071 while (*s != NULLCHAR && *s != '\033') {
2072 if (*s == '(' || *s == '[') {
2083 /* Remove all highlighting escape sequences in s */
2085 StripHighlight (char *s)
2087 static char retbuf[MSG_SIZ];
2090 while (*s != NULLCHAR) {
2091 while (*s == '\033') {
2092 while (*s != NULLCHAR && !isalpha(*s)) s++;
2093 if (*s != NULLCHAR) s++;
2095 while (*s != NULLCHAR && *s != '\033') {
2103 char engineVariant[MSG_SIZ];
2104 char *variantNames[] = VARIANT_NAMES;
2106 VariantName (VariantClass v)
2108 if(v == VariantUnknown || *engineVariant) return engineVariant;
2109 return variantNames[v];
2113 /* Identify a variant from the strings the chess servers use or the
2114 PGN Variant tag names we use. */
2116 StringToVariant (char *e)
2120 VariantClass v = VariantNormal;
2121 int i, found = FALSE;
2122 char buf[MSG_SIZ], c;
2127 /* [HGM] skip over optional board-size prefixes */
2128 if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2129 sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2130 while( *e++ != '_');
2133 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2137 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2138 if (p = StrCaseStr(e, variantNames[i])) {
2139 if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2140 v = (VariantClass) i;
2147 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2148 || StrCaseStr(e, "wild/fr")
2149 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2150 v = VariantFischeRandom;
2151 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2152 (i = 1, p = StrCaseStr(e, "w"))) {
2154 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2161 case 0: /* FICS only, actually */
2163 /* Castling legal even if K starts on d-file */
2164 v = VariantWildCastle;
2169 /* Castling illegal even if K & R happen to start in
2170 normal positions. */
2171 v = VariantNoCastle;
2184 /* Castling legal iff K & R start in normal positions */
2190 /* Special wilds for position setup; unclear what to do here */
2191 v = VariantLoadable;
2194 /* Bizarre ICC game */
2195 v = VariantTwoKings;
2198 v = VariantKriegspiel;
2204 v = VariantFischeRandom;
2207 v = VariantCrazyhouse;
2210 v = VariantBughouse;
2216 /* Not quite the same as FICS suicide! */
2217 v = VariantGiveaway;
2223 v = VariantShatranj;
2226 /* Temporary names for future ICC types. The name *will* change in
2227 the next xboard/WinBoard release after ICC defines it. */
2265 v = VariantCapablanca;
2268 v = VariantKnightmate;
2274 v = VariantCylinder;
2280 v = VariantCapaRandom;
2283 v = VariantBerolina;
2295 /* Found "wild" or "w" in the string but no number;
2296 must assume it's normal chess. */
2300 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2301 if( (len >= MSG_SIZ) && appData.debugMode )
2302 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2304 DisplayError(buf, 0);
2310 if (appData.debugMode) {
2311 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2312 e, wnum, VariantName(v));
2317 static int leftover_start = 0, leftover_len = 0;
2318 char star_match[STAR_MATCH_N][MSG_SIZ];
2320 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2321 advance *index beyond it, and set leftover_start to the new value of
2322 *index; else return FALSE. If pattern contains the character '*', it
2323 matches any sequence of characters not containing '\r', '\n', or the
2324 character following the '*' (if any), and the matched sequence(s) are
2325 copied into star_match.
2328 looking_at ( char *buf, int *index, char *pattern)
2330 char *bufp = &buf[*index], *patternp = pattern;
2332 char *matchp = star_match[0];
2335 if (*patternp == NULLCHAR) {
2336 *index = leftover_start = bufp - buf;
2340 if (*bufp == NULLCHAR) return FALSE;
2341 if (*patternp == '*') {
2342 if (*bufp == *(patternp + 1)) {
2344 matchp = star_match[++star_count];
2348 } else if (*bufp == '\n' || *bufp == '\r') {
2350 if (*patternp == NULLCHAR)
2355 *matchp++ = *bufp++;
2359 if (*patternp != *bufp) return FALSE;
2366 SendToPlayer (char *data, int length)
2368 int error, outCount;
2369 outCount = OutputToProcess(NoProc, data, length, &error);
2370 if (outCount < length) {
2371 DisplayFatalError(_("Error writing to display"), error, 1);
2376 PackHolding (char packed[], char *holding)
2386 switch (runlength) {
2397 sprintf(q, "%d", runlength);
2409 /* Telnet protocol requests from the front end */
2411 TelnetRequest (unsigned char ddww, unsigned char option)
2413 unsigned char msg[3];
2414 int outCount, outError;
2416 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2418 if (appData.debugMode) {
2419 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2435 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2444 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2447 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2452 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2454 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2461 if (!appData.icsActive) return;
2462 TelnetRequest(TN_DO, TN_ECHO);
2468 if (!appData.icsActive) return;
2469 TelnetRequest(TN_DONT, TN_ECHO);
2473 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2475 /* put the holdings sent to us by the server on the board holdings area */
2476 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2480 if(gameInfo.holdingsWidth < 2) return;
2481 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2482 return; // prevent overwriting by pre-board holdings
2484 if( (int)lowestPiece >= BlackPawn ) {
2487 holdingsStartRow = BOARD_HEIGHT-1;
2490 holdingsColumn = BOARD_WIDTH-1;
2491 countsColumn = BOARD_WIDTH-2;
2492 holdingsStartRow = 0;
2496 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2497 board[i][holdingsColumn] = EmptySquare;
2498 board[i][countsColumn] = (ChessSquare) 0;
2500 while( (p=*holdings++) != NULLCHAR ) {
2501 piece = CharToPiece( ToUpper(p) );
2502 if(piece == EmptySquare) continue;
2503 /*j = (int) piece - (int) WhitePawn;*/
2504 j = PieceToNumber(piece);
2505 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2506 if(j < 0) continue; /* should not happen */
2507 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2508 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2509 board[holdingsStartRow+j*direction][countsColumn]++;
2515 VariantSwitch (Board board, VariantClass newVariant)
2517 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2518 static Board oldBoard;
2520 startedFromPositionFile = FALSE;
2521 if(gameInfo.variant == newVariant) return;
2523 /* [HGM] This routine is called each time an assignment is made to
2524 * gameInfo.variant during a game, to make sure the board sizes
2525 * are set to match the new variant. If that means adding or deleting
2526 * holdings, we shift the playing board accordingly
2527 * This kludge is needed because in ICS observe mode, we get boards
2528 * of an ongoing game without knowing the variant, and learn about the
2529 * latter only later. This can be because of the move list we requested,
2530 * in which case the game history is refilled from the beginning anyway,
2531 * but also when receiving holdings of a crazyhouse game. In the latter
2532 * case we want to add those holdings to the already received position.
2536 if (appData.debugMode) {
2537 fprintf(debugFP, "Switch board from %s to %s\n",
2538 VariantName(gameInfo.variant), VariantName(newVariant));
2539 setbuf(debugFP, NULL);
2541 shuffleOpenings = 0; /* [HGM] shuffle */
2542 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2546 newWidth = 9; newHeight = 9;
2547 gameInfo.holdingsSize = 7;
2548 case VariantBughouse:
2549 case VariantCrazyhouse:
2550 newHoldingsWidth = 2; break;
2554 newHoldingsWidth = 2;
2555 gameInfo.holdingsSize = 8;
2558 case VariantCapablanca:
2559 case VariantCapaRandom:
2562 newHoldingsWidth = gameInfo.holdingsSize = 0;
2565 if(newWidth != gameInfo.boardWidth ||
2566 newHeight != gameInfo.boardHeight ||
2567 newHoldingsWidth != gameInfo.holdingsWidth ) {
2569 /* shift position to new playing area, if needed */
2570 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2571 for(i=0; i<BOARD_HEIGHT; i++)
2572 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2573 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2575 for(i=0; i<newHeight; i++) {
2576 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2577 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2579 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2580 for(i=0; i<BOARD_HEIGHT; i++)
2581 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2582 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2585 board[HOLDINGS_SET] = 0;
2586 gameInfo.boardWidth = newWidth;
2587 gameInfo.boardHeight = newHeight;
2588 gameInfo.holdingsWidth = newHoldingsWidth;
2589 gameInfo.variant = newVariant;
2590 InitDrawingSizes(-2, 0);
2591 } else gameInfo.variant = newVariant;
2592 CopyBoard(oldBoard, board); // remember correctly formatted board
2593 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2594 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2597 static int loggedOn = FALSE;
2599 /*-- Game start info cache: --*/
2601 char gs_kind[MSG_SIZ];
2602 static char player1Name[128] = "";
2603 static char player2Name[128] = "";
2604 static char cont_seq[] = "\n\\ ";
2605 static int player1Rating = -1;
2606 static int player2Rating = -1;
2607 /*----------------------------*/
2609 ColorClass curColor = ColorNormal;
2610 int suppressKibitz = 0;
2613 Boolean soughtPending = FALSE;
2614 Boolean seekGraphUp;
2615 #define MAX_SEEK_ADS 200
2617 char *seekAdList[MAX_SEEK_ADS];
2618 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2619 float tcList[MAX_SEEK_ADS];
2620 char colorList[MAX_SEEK_ADS];
2621 int nrOfSeekAds = 0;
2622 int minRating = 1010, maxRating = 2800;
2623 int hMargin = 10, vMargin = 20, h, w;
2624 extern int squareSize, lineGap;
2629 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2630 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2631 if(r < minRating+100 && r >=0 ) r = minRating+100;
2632 if(r > maxRating) r = maxRating;
2633 if(tc < 1.f) tc = 1.f;
2634 if(tc > 95.f) tc = 95.f;
2635 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2636 y = ((double)r - minRating)/(maxRating - minRating)
2637 * (h-vMargin-squareSize/8-1) + vMargin;
2638 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2639 if(strstr(seekAdList[i], " u ")) color = 1;
2640 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2641 !strstr(seekAdList[i], "bullet") &&
2642 !strstr(seekAdList[i], "blitz") &&
2643 !strstr(seekAdList[i], "standard") ) color = 2;
2644 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2645 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2649 PlotSingleSeekAd (int i)
2655 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2657 char buf[MSG_SIZ], *ext = "";
2658 VariantClass v = StringToVariant(type);
2659 if(strstr(type, "wild")) {
2660 ext = type + 4; // append wild number
2661 if(v == VariantFischeRandom) type = "chess960"; else
2662 if(v == VariantLoadable) type = "setup"; else
2663 type = VariantName(v);
2665 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2666 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2667 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2668 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2669 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2670 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2671 seekNrList[nrOfSeekAds] = nr;
2672 zList[nrOfSeekAds] = 0;
2673 seekAdList[nrOfSeekAds++] = StrSave(buf);
2674 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2679 EraseSeekDot (int i)
2681 int x = xList[i], y = yList[i], d=squareSize/4, k;
2682 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2683 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2684 // now replot every dot that overlapped
2685 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2686 int xx = xList[k], yy = yList[k];
2687 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2688 DrawSeekDot(xx, yy, colorList[k]);
2693 RemoveSeekAd (int nr)
2696 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2698 if(seekAdList[i]) free(seekAdList[i]);
2699 seekAdList[i] = seekAdList[--nrOfSeekAds];
2700 seekNrList[i] = seekNrList[nrOfSeekAds];
2701 ratingList[i] = ratingList[nrOfSeekAds];
2702 colorList[i] = colorList[nrOfSeekAds];
2703 tcList[i] = tcList[nrOfSeekAds];
2704 xList[i] = xList[nrOfSeekAds];
2705 yList[i] = yList[nrOfSeekAds];
2706 zList[i] = zList[nrOfSeekAds];
2707 seekAdList[nrOfSeekAds] = NULL;
2713 MatchSoughtLine (char *line)
2715 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2716 int nr, base, inc, u=0; char dummy;
2718 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2719 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2721 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2722 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2723 // match: compact and save the line
2724 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2734 if(!seekGraphUp) return FALSE;
2735 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2736 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap + 2*border;
2738 DrawSeekBackground(0, 0, w, h);
2739 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2740 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2741 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2742 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2744 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2747 snprintf(buf, MSG_SIZ, "%d", i);
2748 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2751 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2752 for(i=1; i<100; i+=(i<10?1:5)) {
2753 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2754 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2755 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2757 snprintf(buf, MSG_SIZ, "%d", i);
2758 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2761 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2766 SeekGraphClick (ClickType click, int x, int y, int moving)
2768 static int lastDown = 0, displayed = 0, lastSecond;
2769 if(y < 0) return FALSE;
2770 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2771 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2772 if(!seekGraphUp) return FALSE;
2773 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2774 DrawPosition(TRUE, NULL);
2777 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2778 if(click == Release || moving) return FALSE;
2780 soughtPending = TRUE;
2781 SendToICS(ics_prefix);
2782 SendToICS("sought\n"); // should this be "sought all"?
2783 } else { // issue challenge based on clicked ad
2784 int dist = 10000; int i, closest = 0, second = 0;
2785 for(i=0; i<nrOfSeekAds; i++) {
2786 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2787 if(d < dist) { dist = d; closest = i; }
2788 second += (d - zList[i] < 120); // count in-range ads
2789 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2793 second = (second > 1);
2794 if(displayed != closest || second != lastSecond) {
2795 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2796 lastSecond = second; displayed = closest;
2798 if(click == Press) {
2799 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2802 } // on press 'hit', only show info
2803 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2804 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2805 SendToICS(ics_prefix);
2807 return TRUE; // let incoming board of started game pop down the graph
2808 } else if(click == Release) { // release 'miss' is ignored
2809 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2810 if(moving == 2) { // right up-click
2811 nrOfSeekAds = 0; // refresh graph
2812 soughtPending = TRUE;
2813 SendToICS(ics_prefix);
2814 SendToICS("sought\n"); // should this be "sought all"?
2817 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2818 // press miss or release hit 'pop down' seek graph
2819 seekGraphUp = FALSE;
2820 DrawPosition(TRUE, NULL);
2826 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2828 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2829 #define STARTED_NONE 0
2830 #define STARTED_MOVES 1
2831 #define STARTED_BOARD 2
2832 #define STARTED_OBSERVE 3
2833 #define STARTED_HOLDINGS 4
2834 #define STARTED_CHATTER 5
2835 #define STARTED_COMMENT 6
2836 #define STARTED_MOVES_NOHIDE 7
2838 static int started = STARTED_NONE;
2839 static char parse[20000];
2840 static int parse_pos = 0;
2841 static char buf[BUF_SIZE + 1];
2842 static int firstTime = TRUE, intfSet = FALSE;
2843 static ColorClass prevColor = ColorNormal;
2844 static int savingComment = FALSE;
2845 static int cmatch = 0; // continuation sequence match
2852 int backup; /* [DM] For zippy color lines */
2854 char talker[MSG_SIZ]; // [HGM] chat
2855 int channel, collective=0;
2857 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2859 if (appData.debugMode) {
2861 fprintf(debugFP, "<ICS: ");
2862 show_bytes(debugFP, data, count);
2863 fprintf(debugFP, "\n");
2867 if (appData.debugMode) { int f = forwardMostMove;
2868 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2869 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2870 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2873 /* If last read ended with a partial line that we couldn't parse,
2874 prepend it to the new read and try again. */
2875 if (leftover_len > 0) {
2876 for (i=0; i<leftover_len; i++)
2877 buf[i] = buf[leftover_start + i];
2880 /* copy new characters into the buffer */
2881 bp = buf + leftover_len;
2882 buf_len=leftover_len;
2883 for (i=0; i<count; i++)
2886 if (data[i] == '\r')
2889 // join lines split by ICS?
2890 if (!appData.noJoin)
2893 Joining just consists of finding matches against the
2894 continuation sequence, and discarding that sequence
2895 if found instead of copying it. So, until a match
2896 fails, there's nothing to do since it might be the
2897 complete sequence, and thus, something we don't want
2900 if (data[i] == cont_seq[cmatch])
2903 if (cmatch == strlen(cont_seq))
2905 cmatch = 0; // complete match. just reset the counter
2908 it's possible for the ICS to not include the space
2909 at the end of the last word, making our [correct]
2910 join operation fuse two separate words. the server
2911 does this when the space occurs at the width setting.
2913 if (!buf_len || buf[buf_len-1] != ' ')
2924 match failed, so we have to copy what matched before
2925 falling through and copying this character. In reality,
2926 this will only ever be just the newline character, but
2927 it doesn't hurt to be precise.
2929 strncpy(bp, cont_seq, cmatch);
2941 buf[buf_len] = NULLCHAR;
2942 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2947 while (i < buf_len) {
2948 /* Deal with part of the TELNET option negotiation
2949 protocol. We refuse to do anything beyond the
2950 defaults, except that we allow the WILL ECHO option,
2951 which ICS uses to turn off password echoing when we are
2952 directly connected to it. We reject this option
2953 if localLineEditing mode is on (always on in xboard)
2954 and we are talking to port 23, which might be a real
2955 telnet server that will try to keep WILL ECHO on permanently.
2957 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2958 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2959 unsigned char option;
2961 switch ((unsigned char) buf[++i]) {
2963 if (appData.debugMode)
2964 fprintf(debugFP, "\n<WILL ");
2965 switch (option = (unsigned char) buf[++i]) {
2967 if (appData.debugMode)
2968 fprintf(debugFP, "ECHO ");
2969 /* Reply only if this is a change, according
2970 to the protocol rules. */
2971 if (remoteEchoOption) break;
2972 if (appData.localLineEditing &&
2973 atoi(appData.icsPort) == TN_PORT) {
2974 TelnetRequest(TN_DONT, TN_ECHO);
2977 TelnetRequest(TN_DO, TN_ECHO);
2978 remoteEchoOption = TRUE;
2982 if (appData.debugMode)
2983 fprintf(debugFP, "%d ", option);
2984 /* Whatever this is, we don't want it. */
2985 TelnetRequest(TN_DONT, option);
2990 if (appData.debugMode)
2991 fprintf(debugFP, "\n<WONT ");
2992 switch (option = (unsigned char) buf[++i]) {
2994 if (appData.debugMode)
2995 fprintf(debugFP, "ECHO ");
2996 /* Reply only if this is a change, according
2997 to the protocol rules. */
2998 if (!remoteEchoOption) break;
3000 TelnetRequest(TN_DONT, TN_ECHO);
3001 remoteEchoOption = FALSE;
3004 if (appData.debugMode)
3005 fprintf(debugFP, "%d ", (unsigned char) option);
3006 /* Whatever this is, it must already be turned
3007 off, because we never agree to turn on
3008 anything non-default, so according to the
3009 protocol rules, we don't reply. */
3014 if (appData.debugMode)
3015 fprintf(debugFP, "\n<DO ");
3016 switch (option = (unsigned char) buf[++i]) {
3018 /* Whatever this is, we refuse to do it. */
3019 if (appData.debugMode)
3020 fprintf(debugFP, "%d ", option);
3021 TelnetRequest(TN_WONT, option);
3026 if (appData.debugMode)
3027 fprintf(debugFP, "\n<DONT ");
3028 switch (option = (unsigned char) buf[++i]) {
3030 if (appData.debugMode)
3031 fprintf(debugFP, "%d ", option);
3032 /* Whatever this is, we are already not doing
3033 it, because we never agree to do anything
3034 non-default, so according to the protocol
3035 rules, we don't reply. */
3040 if (appData.debugMode)
3041 fprintf(debugFP, "\n<IAC ");
3042 /* Doubled IAC; pass it through */
3046 if (appData.debugMode)
3047 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3048 /* Drop all other telnet commands on the floor */
3051 if (oldi > next_out)
3052 SendToPlayer(&buf[next_out], oldi - next_out);
3058 /* OK, this at least will *usually* work */
3059 if (!loggedOn && looking_at(buf, &i, "ics%")) {
3063 if (loggedOn && !intfSet) {
3064 if (ics_type == ICS_ICC) {
3065 snprintf(str, MSG_SIZ,
3066 "/set-quietly interface %s\n/set-quietly style 12\n",
3068 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3069 strcat(str, "/set-2 51 1\n/set seek 1\n");
3070 } else if (ics_type == ICS_CHESSNET) {
3071 snprintf(str, MSG_SIZ, "/style 12\n");
3073 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3074 strcat(str, programVersion);
3075 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3076 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3077 strcat(str, "$iset seekremove 1\n$set seek 1\n");
3079 strcat(str, "$iset nohighlight 1\n");
3081 strcat(str, "$iset lock 1\n$style 12\n");
3084 NotifyFrontendLogin();
3088 if (started == STARTED_COMMENT) {
3089 /* Accumulate characters in comment */
3090 parse[parse_pos++] = buf[i];
3091 if (buf[i] == '\n') {
3092 parse[parse_pos] = NULLCHAR;
3093 if(chattingPartner>=0) {
3095 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3096 OutputChatMessage(chattingPartner, mess);
3097 if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3099 talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3100 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3101 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3102 OutputChatMessage(p, mess);
3106 chattingPartner = -1;
3107 if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3110 if(!suppressKibitz) // [HGM] kibitz
3111 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3112 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3113 int nrDigit = 0, nrAlph = 0, j;
3114 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3115 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3116 parse[parse_pos] = NULLCHAR;
3117 // try to be smart: if it does not look like search info, it should go to
3118 // ICS interaction window after all, not to engine-output window.
3119 for(j=0; j<parse_pos; j++) { // count letters and digits
3120 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3121 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3122 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3124 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3125 int depth=0; float score;
3126 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3127 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3128 pvInfoList[forwardMostMove-1].depth = depth;
3129 pvInfoList[forwardMostMove-1].score = 100*score;
3131 OutputKibitz(suppressKibitz, parse);
3134 if(gameMode == IcsObserving) // restore original ICS messages
3135 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3136 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3138 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3139 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3140 SendToPlayer(tmp, strlen(tmp));
3142 next_out = i+1; // [HGM] suppress printing in ICS window
3144 started = STARTED_NONE;
3146 /* Don't match patterns against characters in comment */
3151 if (started == STARTED_CHATTER) {
3152 if (buf[i] != '\n') {
3153 /* Don't match patterns against characters in chatter */
3157 started = STARTED_NONE;
3158 if(suppressKibitz) next_out = i+1;
3161 /* Kludge to deal with rcmd protocol */
3162 if (firstTime && looking_at(buf, &i, "\001*")) {
3163 DisplayFatalError(&buf[1], 0, 1);
3169 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3172 if (appData.debugMode)
3173 fprintf(debugFP, "ics_type %d\n", ics_type);
3176 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3177 ics_type = ICS_FICS;
3179 if (appData.debugMode)
3180 fprintf(debugFP, "ics_type %d\n", ics_type);
3183 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3184 ics_type = ICS_CHESSNET;
3186 if (appData.debugMode)
3187 fprintf(debugFP, "ics_type %d\n", ics_type);
3192 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3193 looking_at(buf, &i, "Logging you in as \"*\"") ||
3194 looking_at(buf, &i, "will be \"*\""))) {
3195 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3199 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3201 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3202 DisplayIcsInteractionTitle(buf);
3203 have_set_title = TRUE;
3206 /* skip finger notes */
3207 if (started == STARTED_NONE &&
3208 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3209 (buf[i] == '1' && buf[i+1] == '0')) &&
3210 buf[i+2] == ':' && buf[i+3] == ' ') {
3211 started = STARTED_CHATTER;
3217 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3218 if(appData.seekGraph) {
3219 if(soughtPending && MatchSoughtLine(buf+i)) {
3220 i = strstr(buf+i, "rated") - buf;
3221 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3222 next_out = leftover_start = i;
3223 started = STARTED_CHATTER;
3224 suppressKibitz = TRUE;
3227 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3228 && looking_at(buf, &i, "* ads displayed")) {
3229 soughtPending = FALSE;
3234 if(appData.autoRefresh) {
3235 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3236 int s = (ics_type == ICS_ICC); // ICC format differs
3238 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3239 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3240 looking_at(buf, &i, "*% "); // eat prompt
3241 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3242 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3243 next_out = i; // suppress
3246 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3247 char *p = star_match[0];
3249 if(seekGraphUp) RemoveSeekAd(atoi(p));
3250 while(*p && *p++ != ' '); // next
3252 looking_at(buf, &i, "*% "); // eat prompt
3253 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3260 /* skip formula vars */
3261 if (started == STARTED_NONE &&
3262 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3263 started = STARTED_CHATTER;
3268 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3269 if (appData.autoKibitz && started == STARTED_NONE &&
3270 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3271 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3272 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3273 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3274 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3275 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3276 suppressKibitz = TRUE;
3277 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3279 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3280 && (gameMode == IcsPlayingWhite)) ||
3281 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3282 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3283 started = STARTED_CHATTER; // own kibitz we simply discard
3285 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3286 parse_pos = 0; parse[0] = NULLCHAR;
3287 savingComment = TRUE;
3288 suppressKibitz = gameMode != IcsObserving ? 2 :
3289 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3293 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3294 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3295 && atoi(star_match[0])) {
3296 // suppress the acknowledgements of our own autoKibitz
3298 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3299 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3300 SendToPlayer(star_match[0], strlen(star_match[0]));
3301 if(looking_at(buf, &i, "*% ")) // eat prompt
3302 suppressKibitz = FALSE;
3306 } // [HGM] kibitz: end of patch
3308 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3310 // [HGM] chat: intercept tells by users for which we have an open chat window
3312 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3313 looking_at(buf, &i, "* whispers:") ||
3314 looking_at(buf, &i, "* kibitzes:") ||
3315 looking_at(buf, &i, "* shouts:") ||
3316 looking_at(buf, &i, "* c-shouts:") ||
3317 looking_at(buf, &i, "--> * ") ||
3318 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3319 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3320 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3321 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3323 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3324 chattingPartner = -1; collective = 0;
3326 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3327 for(p=0; p<MAX_CHAT; p++) {
3329 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3330 talker[0] = '['; strcat(talker, "] ");
3331 Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3332 chattingPartner = p; break;
3335 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3336 for(p=0; p<MAX_CHAT; p++) {
3338 if(!strcmp("kibitzes", chatPartner[p])) {
3339 talker[0] = '['; strcat(talker, "] ");
3340 chattingPartner = p; break;
3343 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3344 for(p=0; p<MAX_CHAT; p++) {
3346 if(!strcmp("whispers", chatPartner[p])) {
3347 talker[0] = '['; strcat(talker, "] ");
3348 chattingPartner = p; break;
3351 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3352 if(buf[i-8] == '-' && buf[i-3] == 't')
3353 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3355 if(!strcmp("c-shouts", chatPartner[p])) {
3356 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3357 chattingPartner = p; break;
3360 if(chattingPartner < 0)
3361 for(p=0; p<MAX_CHAT; p++) {
3363 if(!strcmp("shouts", chatPartner[p])) {
3364 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3365 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3366 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3367 chattingPartner = p; break;
3371 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3372 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3374 Colorize(ColorTell, FALSE);
3375 if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3377 chattingPartner = p; break;
3379 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3380 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3381 started = STARTED_COMMENT;
3382 parse_pos = 0; parse[0] = NULLCHAR;
3383 savingComment = 3 + chattingPartner; // counts as TRUE
3384 if(collective == 3) i = oldi; else {
3385 suppressKibitz = TRUE;
3386 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3387 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3391 } // [HGM] chat: end of patch
3394 if (appData.zippyTalk || appData.zippyPlay) {
3395 /* [DM] Backup address for color zippy lines */
3397 if (loggedOn == TRUE)
3398 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3399 (appData.zippyPlay && ZippyMatch(buf, &backup)))
3402 } // [DM] 'else { ' deleted
3404 /* Regular tells and says */
3405 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3406 looking_at(buf, &i, "* (your partner) tells you: ") ||
3407 looking_at(buf, &i, "* says: ") ||
3408 /* Don't color "message" or "messages" output */
3409 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3410 looking_at(buf, &i, "*. * at *:*: ") ||
3411 looking_at(buf, &i, "--* (*:*): ") ||
3412 /* Message notifications (same color as tells) */
3413 looking_at(buf, &i, "* has left a message ") ||
3414 looking_at(buf, &i, "* just sent you a message:\n") ||
3415 /* Whispers and kibitzes */
3416 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3417 looking_at(buf, &i, "* kibitzes: ") ||
3419 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3421 if (tkind == 1 && strchr(star_match[0], ':')) {
3422 /* Avoid "tells you:" spoofs in channels */
3425 if (star_match[0][0] == NULLCHAR ||
3426 strchr(star_match[0], ' ') ||
3427 (tkind == 3 && strchr(star_match[1], ' '))) {
3428 /* Reject bogus matches */
3431 if (appData.colorize) {
3432 if (oldi > next_out) {
3433 SendToPlayer(&buf[next_out], oldi - next_out);
3438 Colorize(ColorTell, FALSE);
3439 curColor = ColorTell;
3442 Colorize(ColorKibitz, FALSE);
3443 curColor = ColorKibitz;
3446 p = strrchr(star_match[1], '(');
3453 Colorize(ColorChannel1, FALSE);
3454 curColor = ColorChannel1;
3456 Colorize(ColorChannel, FALSE);
3457 curColor = ColorChannel;
3461 curColor = ColorNormal;
3465 if (started == STARTED_NONE && appData.autoComment &&
3466 (gameMode == IcsObserving ||
3467 gameMode == IcsPlayingWhite ||
3468 gameMode == IcsPlayingBlack)) {
3469 parse_pos = i - oldi;
3470 memcpy(parse, &buf[oldi], parse_pos);
3471 parse[parse_pos] = NULLCHAR;
3472 started = STARTED_COMMENT;
3473 savingComment = TRUE;
3474 } else if(collective != 3) {
3475 started = STARTED_CHATTER;
3476 savingComment = FALSE;
3483 if (looking_at(buf, &i, "* s-shouts: ") ||
3484 looking_at(buf, &i, "* c-shouts: ")) {
3485 if (appData.colorize) {
3486 if (oldi > next_out) {
3487 SendToPlayer(&buf[next_out], oldi - next_out);
3490 Colorize(ColorSShout, FALSE);
3491 curColor = ColorSShout;
3494 started = STARTED_CHATTER;
3498 if (looking_at(buf, &i, "--->")) {
3503 if (looking_at(buf, &i, "* shouts: ") ||
3504 looking_at(buf, &i, "--> ")) {
3505 if (appData.colorize) {
3506 if (oldi > next_out) {
3507 SendToPlayer(&buf[next_out], oldi - next_out);
3510 Colorize(ColorShout, FALSE);
3511 curColor = ColorShout;
3514 started = STARTED_CHATTER;
3518 if (looking_at( buf, &i, "Challenge:")) {
3519 if (appData.colorize) {
3520 if (oldi > next_out) {
3521 SendToPlayer(&buf[next_out], oldi - next_out);
3524 Colorize(ColorChallenge, FALSE);
3525 curColor = ColorChallenge;
3531 if (looking_at(buf, &i, "* offers you") ||
3532 looking_at(buf, &i, "* offers to be") ||
3533 looking_at(buf, &i, "* would like to") ||
3534 looking_at(buf, &i, "* requests to") ||
3535 looking_at(buf, &i, "Your opponent offers") ||
3536 looking_at(buf, &i, "Your opponent requests")) {
3538 if (appData.colorize) {
3539 if (oldi > next_out) {
3540 SendToPlayer(&buf[next_out], oldi - next_out);
3543 Colorize(ColorRequest, FALSE);
3544 curColor = ColorRequest;
3549 if (looking_at(buf, &i, "* (*) seeking")) {
3550 if (appData.colorize) {
3551 if (oldi > next_out) {
3552 SendToPlayer(&buf[next_out], oldi - next_out);
3555 Colorize(ColorSeek, FALSE);
3556 curColor = ColorSeek;
3561 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3563 if (looking_at(buf, &i, "\\ ")) {
3564 if (prevColor != ColorNormal) {
3565 if (oldi > next_out) {
3566 SendToPlayer(&buf[next_out], oldi - next_out);
3569 Colorize(prevColor, TRUE);
3570 curColor = prevColor;
3572 if (savingComment) {
3573 parse_pos = i - oldi;
3574 memcpy(parse, &buf[oldi], parse_pos);
3575 parse[parse_pos] = NULLCHAR;
3576 started = STARTED_COMMENT;
3577 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3578 chattingPartner = savingComment - 3; // kludge to remember the box
3580 started = STARTED_CHATTER;
3585 if (looking_at(buf, &i, "Black Strength :") ||
3586 looking_at(buf, &i, "<<< style 10 board >>>") ||
3587 looking_at(buf, &i, "<10>") ||
3588 looking_at(buf, &i, "#@#")) {
3589 /* Wrong board style */
3591 SendToICS(ics_prefix);
3592 SendToICS("set style 12\n");
3593 SendToICS(ics_prefix);
3594 SendToICS("refresh\n");
3598 if (looking_at(buf, &i, "login:")) {
3599 if (!have_sent_ICS_logon) {
3601 have_sent_ICS_logon = 1;
3602 else // no init script was found
3603 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3604 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3605 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3610 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3611 (looking_at(buf, &i, "\n<12> ") ||
3612 looking_at(buf, &i, "<12> "))) {
3614 if (oldi > next_out) {
3615 SendToPlayer(&buf[next_out], oldi - next_out);
3618 started = STARTED_BOARD;
3623 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3624 looking_at(buf, &i, "<b1> ")) {
3625 if (oldi > next_out) {
3626 SendToPlayer(&buf[next_out], oldi - next_out);
3629 started = STARTED_HOLDINGS;
3634 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3636 /* Header for a move list -- first line */
3638 switch (ics_getting_history) {
3642 case BeginningOfGame:
3643 /* User typed "moves" or "oldmoves" while we
3644 were idle. Pretend we asked for these
3645 moves and soak them up so user can step
3646 through them and/or save them.
3649 gameMode = IcsObserving;
3652 ics_getting_history = H_GOT_UNREQ_HEADER;
3654 case EditGame: /*?*/
3655 case EditPosition: /*?*/
3656 /* Should above feature work in these modes too? */
3657 /* For now it doesn't */
3658 ics_getting_history = H_GOT_UNWANTED_HEADER;
3661 ics_getting_history = H_GOT_UNWANTED_HEADER;
3666 /* Is this the right one? */
3667 if (gameInfo.white && gameInfo.black &&
3668 strcmp(gameInfo.white, star_match[0]) == 0 &&
3669 strcmp(gameInfo.black, star_match[2]) == 0) {
3671 ics_getting_history = H_GOT_REQ_HEADER;
3674 case H_GOT_REQ_HEADER:
3675 case H_GOT_UNREQ_HEADER:
3676 case H_GOT_UNWANTED_HEADER:
3677 case H_GETTING_MOVES:
3678 /* Should not happen */
3679 DisplayError(_("Error gathering move list: two headers"), 0);
3680 ics_getting_history = H_FALSE;
3684 /* Save player ratings into gameInfo if needed */
3685 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3686 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3687 (gameInfo.whiteRating == -1 ||
3688 gameInfo.blackRating == -1)) {
3690 gameInfo.whiteRating = string_to_rating(star_match[1]);
3691 gameInfo.blackRating = string_to_rating(star_match[3]);
3692 if (appData.debugMode)
3693 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3694 gameInfo.whiteRating, gameInfo.blackRating);
3699 if (looking_at(buf, &i,
3700 "* * match, initial time: * minute*, increment: * second")) {
3701 /* Header for a move list -- second line */
3702 /* Initial board will follow if this is a wild game */
3703 if (gameInfo.event != NULL) free(gameInfo.event);
3704 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3705 gameInfo.event = StrSave(str);
3706 /* [HGM] we switched variant. Translate boards if needed. */
3707 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3711 if (looking_at(buf, &i, "Move ")) {
3712 /* Beginning of a move list */
3713 switch (ics_getting_history) {
3715 /* Normally should not happen */
3716 /* Maybe user hit reset while we were parsing */
3719 /* Happens if we are ignoring a move list that is not
3720 * the one we just requested. Common if the user
3721 * tries to observe two games without turning off
3724 case H_GETTING_MOVES:
3725 /* Should not happen */
3726 DisplayError(_("Error gathering move list: nested"), 0);
3727 ics_getting_history = H_FALSE;
3729 case H_GOT_REQ_HEADER:
3730 ics_getting_history = H_GETTING_MOVES;
3731 started = STARTED_MOVES;
3733 if (oldi > next_out) {
3734 SendToPlayer(&buf[next_out], oldi - next_out);
3737 case H_GOT_UNREQ_HEADER:
3738 ics_getting_history = H_GETTING_MOVES;
3739 started = STARTED_MOVES_NOHIDE;
3742 case H_GOT_UNWANTED_HEADER:
3743 ics_getting_history = H_FALSE;
3749 if (looking_at(buf, &i, "% ") ||
3750 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3751 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3752 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3753 soughtPending = FALSE;
3757 if(suppressKibitz) next_out = i;
3758 savingComment = FALSE;
3762 case STARTED_MOVES_NOHIDE:
3763 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3764 parse[parse_pos + i - oldi] = NULLCHAR;
3765 ParseGameHistory(parse);
3767 if (appData.zippyPlay && first.initDone) {
3768 FeedMovesToProgram(&first, forwardMostMove);
3769 if (gameMode == IcsPlayingWhite) {
3770 if (WhiteOnMove(forwardMostMove)) {
3771 if (first.sendTime) {
3772 if (first.useColors) {
3773 SendToProgram("black\n", &first);
3775 SendTimeRemaining(&first, TRUE);
3777 if (first.useColors) {
3778 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3780 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3781 first.maybeThinking = TRUE;
3783 if (first.usePlayother) {
3784 if (first.sendTime) {
3785 SendTimeRemaining(&first, TRUE);
3787 SendToProgram("playother\n", &first);
3793 } else if (gameMode == IcsPlayingBlack) {
3794 if (!WhiteOnMove(forwardMostMove)) {
3795 if (first.sendTime) {
3796 if (first.useColors) {
3797 SendToProgram("white\n", &first);
3799 SendTimeRemaining(&first, FALSE);
3801 if (first.useColors) {
3802 SendToProgram("black\n", &first);
3804 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3805 first.maybeThinking = TRUE;
3807 if (first.usePlayother) {
3808 if (first.sendTime) {
3809 SendTimeRemaining(&first, FALSE);
3811 SendToProgram("playother\n", &first);
3820 if (gameMode == IcsObserving && ics_gamenum == -1) {
3821 /* Moves came from oldmoves or moves command
3822 while we weren't doing anything else.
3824 currentMove = forwardMostMove;
3825 ClearHighlights();/*!!could figure this out*/
3826 flipView = appData.flipView;
3827 DrawPosition(TRUE, boards[currentMove]);
3828 DisplayBothClocks();
3829 snprintf(str, MSG_SIZ, "%s %s %s",
3830 gameInfo.white, _("vs."), gameInfo.black);
3834 /* Moves were history of an active game */
3835 if (gameInfo.resultDetails != NULL) {
3836 free(gameInfo.resultDetails);
3837 gameInfo.resultDetails = NULL;
3840 HistorySet(parseList, backwardMostMove,
3841 forwardMostMove, currentMove-1);
3842 DisplayMove(currentMove - 1);
3843 if (started == STARTED_MOVES) next_out = i;
3844 started = STARTED_NONE;
3845 ics_getting_history = H_FALSE;
3848 case STARTED_OBSERVE:
3849 started = STARTED_NONE;
3850 SendToICS(ics_prefix);
3851 SendToICS("refresh\n");
3857 if(bookHit) { // [HGM] book: simulate book reply
3858 static char bookMove[MSG_SIZ]; // a bit generous?
3860 programStats.nodes = programStats.depth = programStats.time =
3861 programStats.score = programStats.got_only_move = 0;
3862 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3864 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3865 strcat(bookMove, bookHit);
3866 HandleMachineMove(bookMove, &first);
3871 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3872 started == STARTED_HOLDINGS ||
3873 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3874 /* Accumulate characters in move list or board */
3875 parse[parse_pos++] = buf[i];
3878 /* Start of game messages. Mostly we detect start of game
3879 when the first board image arrives. On some versions
3880 of the ICS, though, we need to do a "refresh" after starting
3881 to observe in order to get the current board right away. */
3882 if (looking_at(buf, &i, "Adding game * to observation list")) {
3883 started = STARTED_OBSERVE;
3887 /* Handle auto-observe */
3888 if (appData.autoObserve &&
3889 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3890 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3892 /* Choose the player that was highlighted, if any. */
3893 if (star_match[0][0] == '\033' ||
3894 star_match[1][0] != '\033') {
3895 player = star_match[0];
3897 player = star_match[2];
3899 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3900 ics_prefix, StripHighlightAndTitle(player));
3903 /* Save ratings from notify string */
3904 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3905 player1Rating = string_to_rating(star_match[1]);
3906 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3907 player2Rating = string_to_rating(star_match[3]);
3909 if (appData.debugMode)
3911 "Ratings from 'Game notification:' %s %d, %s %d\n",
3912 player1Name, player1Rating,
3913 player2Name, player2Rating);
3918 /* Deal with automatic examine mode after a game,
3919 and with IcsObserving -> IcsExamining transition */
3920 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3921 looking_at(buf, &i, "has made you an examiner of game *")) {
3923 int gamenum = atoi(star_match[0]);
3924 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3925 gamenum == ics_gamenum) {
3926 /* We were already playing or observing this game;
3927 no need to refetch history */
3928 gameMode = IcsExamining;
3930 pauseExamForwardMostMove = forwardMostMove;
3931 } else if (currentMove < forwardMostMove) {
3932 ForwardInner(forwardMostMove);
3935 /* I don't think this case really can happen */
3936 SendToICS(ics_prefix);
3937 SendToICS("refresh\n");
3942 /* Error messages */
3943 // if (ics_user_moved) {
3944 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3945 if (looking_at(buf, &i, "Illegal move") ||
3946 looking_at(buf, &i, "Not a legal move") ||
3947 looking_at(buf, &i, "Your king is in check") ||
3948 looking_at(buf, &i, "It isn't your turn") ||
3949 looking_at(buf, &i, "It is not your move")) {
3951 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3952 currentMove = forwardMostMove-1;
3953 DisplayMove(currentMove - 1); /* before DMError */
3954 DrawPosition(FALSE, boards[currentMove]);
3955 SwitchClocks(forwardMostMove-1); // [HGM] race
3956 DisplayBothClocks();
3958 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3964 if (looking_at(buf, &i, "still have time") ||
3965 looking_at(buf, &i, "not out of time") ||
3966 looking_at(buf, &i, "either player is out of time") ||
3967 looking_at(buf, &i, "has timeseal; checking")) {
3968 /* We must have called his flag a little too soon */
3969 whiteFlag = blackFlag = FALSE;
3973 if (looking_at(buf, &i, "added * seconds to") ||
3974 looking_at(buf, &i, "seconds were added to")) {
3975 /* Update the clocks */
3976 SendToICS(ics_prefix);
3977 SendToICS("refresh\n");
3981 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3982 ics_clock_paused = TRUE;
3987 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3988 ics_clock_paused = FALSE;
3993 /* Grab player ratings from the Creating: message.
3994 Note we have to check for the special case when
3995 the ICS inserts things like [white] or [black]. */
3996 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3997 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3999 0 player 1 name (not necessarily white)
4001 2 empty, white, or black (IGNORED)
4002 3 player 2 name (not necessarily black)
4005 The names/ratings are sorted out when the game
4006 actually starts (below).
4008 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4009 player1Rating = string_to_rating(star_match[1]);
4010 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4011 player2Rating = string_to_rating(star_match[4]);
4013 if (appData.debugMode)
4015 "Ratings from 'Creating:' %s %d, %s %d\n",
4016 player1Name, player1Rating,
4017 player2Name, player2Rating);
4022 /* Improved generic start/end-of-game messages */
4023 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4024 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4025 /* If tkind == 0: */
4026 /* star_match[0] is the game number */
4027 /* [1] is the white player's name */
4028 /* [2] is the black player's name */
4029 /* For end-of-game: */
4030 /* [3] is the reason for the game end */
4031 /* [4] is a PGN end game-token, preceded by " " */
4032 /* For start-of-game: */
4033 /* [3] begins with "Creating" or "Continuing" */
4034 /* [4] is " *" or empty (don't care). */
4035 int gamenum = atoi(star_match[0]);
4036 char *whitename, *blackname, *why, *endtoken;
4037 ChessMove endtype = EndOfFile;
4040 whitename = star_match[1];
4041 blackname = star_match[2];
4042 why = star_match[3];
4043 endtoken = star_match[4];
4045 whitename = star_match[1];
4046 blackname = star_match[3];
4047 why = star_match[5];
4048 endtoken = star_match[6];
4051 /* Game start messages */
4052 if (strncmp(why, "Creating ", 9) == 0 ||
4053 strncmp(why, "Continuing ", 11) == 0) {
4054 gs_gamenum = gamenum;
4055 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4056 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4057 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4059 if (appData.zippyPlay) {
4060 ZippyGameStart(whitename, blackname);
4063 partnerBoardValid = FALSE; // [HGM] bughouse
4067 /* Game end messages */
4068 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4069 ics_gamenum != gamenum) {
4072 while (endtoken[0] == ' ') endtoken++;
4073 switch (endtoken[0]) {
4076 endtype = GameUnfinished;
4079 endtype = BlackWins;
4082 if (endtoken[1] == '/')
4083 endtype = GameIsDrawn;
4085 endtype = WhiteWins;
4088 GameEnds(endtype, why, GE_ICS);
4090 if (appData.zippyPlay && first.initDone) {
4091 ZippyGameEnd(endtype, why);
4092 if (first.pr == NoProc) {
4093 /* Start the next process early so that we'll
4094 be ready for the next challenge */
4095 StartChessProgram(&first);
4097 /* Send "new" early, in case this command takes
4098 a long time to finish, so that we'll be ready
4099 for the next challenge. */
4100 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4104 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4108 if (looking_at(buf, &i, "Removing game * from observation") ||
4109 looking_at(buf, &i, "no longer observing game *") ||
4110 looking_at(buf, &i, "Game * (*) has no examiners")) {
4111 if (gameMode == IcsObserving &&
4112 atoi(star_match[0]) == ics_gamenum)
4114 /* icsEngineAnalyze */
4115 if (appData.icsEngineAnalyze) {
4122 ics_user_moved = FALSE;
4127 if (looking_at(buf, &i, "no longer examining game *")) {
4128 if (gameMode == IcsExamining &&
4129 atoi(star_match[0]) == ics_gamenum)
4133 ics_user_moved = FALSE;
4138 /* Advance leftover_start past any newlines we find,
4139 so only partial lines can get reparsed */
4140 if (looking_at(buf, &i, "\n")) {
4141 prevColor = curColor;
4142 if (curColor != ColorNormal) {
4143 if (oldi > next_out) {
4144 SendToPlayer(&buf[next_out], oldi - next_out);
4147 Colorize(ColorNormal, FALSE);
4148 curColor = ColorNormal;
4150 if (started == STARTED_BOARD) {
4151 started = STARTED_NONE;
4152 parse[parse_pos] = NULLCHAR;
4153 ParseBoard12(parse);
4156 /* Send premove here */
4157 if (appData.premove) {
4159 if (currentMove == 0 &&
4160 gameMode == IcsPlayingWhite &&
4161 appData.premoveWhite) {
4162 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4163 if (appData.debugMode)
4164 fprintf(debugFP, "Sending premove:\n");
4166 } else if (currentMove == 1 &&
4167 gameMode == IcsPlayingBlack &&
4168 appData.premoveBlack) {
4169 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4170 if (appData.debugMode)
4171 fprintf(debugFP, "Sending premove:\n");
4173 } else if (gotPremove) {
4174 int oldFMM = forwardMostMove;
4176 ClearPremoveHighlights();
4177 if (appData.debugMode)
4178 fprintf(debugFP, "Sending premove:\n");
4179 UserMoveEvent(premoveFromX, premoveFromY,
4180 premoveToX, premoveToY,
4182 if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4183 if(moveList[oldFMM-1][1] != '@')
4184 SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4185 moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4187 SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4192 /* Usually suppress following prompt */
4193 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4194 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4195 if (looking_at(buf, &i, "*% ")) {
4196 savingComment = FALSE;
4201 } else if (started == STARTED_HOLDINGS) {
4203 char new_piece[MSG_SIZ];
4204 started = STARTED_NONE;
4205 parse[parse_pos] = NULLCHAR;
4206 if (appData.debugMode)
4207 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4208 parse, currentMove);
4209 if (sscanf(parse, " game %d", &gamenum) == 1) {
4210 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4211 if (gameInfo.variant == VariantNormal) {
4212 /* [HGM] We seem to switch variant during a game!
4213 * Presumably no holdings were displayed, so we have
4214 * to move the position two files to the right to
4215 * create room for them!
4217 VariantClass newVariant;
4218 switch(gameInfo.boardWidth) { // base guess on board width
4219 case 9: newVariant = VariantShogi; break;
4220 case 10: newVariant = VariantGreat; break;
4221 default: newVariant = VariantCrazyhouse; break;
4223 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4224 /* Get a move list just to see the header, which
4225 will tell us whether this is really bug or zh */
4226 if (ics_getting_history == H_FALSE) {
4227 ics_getting_history = H_REQUESTED;
4228 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4232 new_piece[0] = NULLCHAR;
4233 sscanf(parse, "game %d white [%s black [%s <- %s",
4234 &gamenum, white_holding, black_holding,
4236 white_holding[strlen(white_holding)-1] = NULLCHAR;
4237 black_holding[strlen(black_holding)-1] = NULLCHAR;
4238 /* [HGM] copy holdings to board holdings area */
4239 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4240 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4241 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4243 if (appData.zippyPlay && first.initDone) {
4244 ZippyHoldings(white_holding, black_holding,
4248 if (tinyLayout || smallLayout) {
4249 char wh[16], bh[16];
4250 PackHolding(wh, white_holding);
4251 PackHolding(bh, black_holding);
4252 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4253 gameInfo.white, gameInfo.black);
4255 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4256 gameInfo.white, white_holding, _("vs."),
4257 gameInfo.black, black_holding);
4259 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4260 DrawPosition(FALSE, boards[currentMove]);
4262 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4263 sscanf(parse, "game %d white [%s black [%s <- %s",
4264 &gamenum, white_holding, black_holding,
4266 white_holding[strlen(white_holding)-1] = NULLCHAR;
4267 black_holding[strlen(black_holding)-1] = NULLCHAR;
4268 /* [HGM] copy holdings to partner-board holdings area */
4269 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4270 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4271 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4272 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4273 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4276 /* Suppress following prompt */
4277 if (looking_at(buf, &i, "*% ")) {
4278 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4279 savingComment = FALSE;
4287 i++; /* skip unparsed character and loop back */
4290 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4291 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4292 // SendToPlayer(&buf[next_out], i - next_out);
4293 started != STARTED_HOLDINGS && leftover_start > next_out) {
4294 SendToPlayer(&buf[next_out], leftover_start - next_out);
4298 leftover_len = buf_len - leftover_start;
4299 /* if buffer ends with something we couldn't parse,
4300 reparse it after appending the next read */
4302 } else if (count == 0) {
4303 RemoveInputSource(isr);
4304 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4306 DisplayFatalError(_("Error reading from ICS"), error, 1);
4311 /* Board style 12 looks like this:
4313 <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
4315 * The "<12> " is stripped before it gets to this routine. The two
4316 * trailing 0's (flip state and clock ticking) are later addition, and
4317 * some chess servers may not have them, or may have only the first.
4318 * Additional trailing fields may be added in the future.
4321 #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"
4323 #define RELATION_OBSERVING_PLAYED 0
4324 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4325 #define RELATION_PLAYING_MYMOVE 1
4326 #define RELATION_PLAYING_NOTMYMOVE -1
4327 #define RELATION_EXAMINING 2
4328 #define RELATION_ISOLATED_BOARD -3
4329 #define RELATION_STARTING_POSITION -4 /* FICS only */
4332 ParseBoard12 (char *string)
4336 char *bookHit = NULL; // [HGM] book
4338 GameMode newGameMode;
4339 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4340 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4341 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4342 char to_play, board_chars[200];
4343 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4344 char black[32], white[32];
4346 int prevMove = currentMove;
4349 int fromX, fromY, toX, toY;
4351 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4352 Boolean weird = FALSE, reqFlag = FALSE;
4354 fromX = fromY = toX = toY = -1;
4358 if (appData.debugMode)
4359 fprintf(debugFP, "Parsing board: %s\n", string);
4361 move_str[0] = NULLCHAR;
4362 elapsed_time[0] = NULLCHAR;
4363 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4365 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4366 if(string[i] == ' ') { ranks++; files = 0; }
4368 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4371 for(j = 0; j <i; j++) board_chars[j] = string[j];
4372 board_chars[i] = '\0';
4375 n = sscanf(string, PATTERN, &to_play, &double_push,
4376 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4377 &gamenum, white, black, &relation, &basetime, &increment,
4378 &white_stren, &black_stren, &white_time, &black_time,
4379 &moveNum, str, elapsed_time, move_str, &ics_flip,
4383 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4384 DisplayError(str, 0);
4388 /* Convert the move number to internal form */
4389 moveNum = (moveNum - 1) * 2;
4390 if (to_play == 'B') moveNum++;
4391 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4392 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4398 case RELATION_OBSERVING_PLAYED:
4399 case RELATION_OBSERVING_STATIC:
4400 if (gamenum == -1) {
4401 /* Old ICC buglet */
4402 relation = RELATION_OBSERVING_STATIC;
4404 newGameMode = IcsObserving;
4406 case RELATION_PLAYING_MYMOVE:
4407 case RELATION_PLAYING_NOTMYMOVE:
4409 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4410 IcsPlayingWhite : IcsPlayingBlack;
4411 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4413 case RELATION_EXAMINING:
4414 newGameMode = IcsExamining;
4416 case RELATION_ISOLATED_BOARD:
4418 /* Just display this board. If user was doing something else,
4419 we will forget about it until the next board comes. */
4420 newGameMode = IcsIdle;
4422 case RELATION_STARTING_POSITION:
4423 newGameMode = gameMode;
4427 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4428 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4429 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4430 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4431 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4432 static int lastBgGame = -1;
4434 for (k = 0; k < ranks; k++) {
4435 for (j = 0; j < files; j++)
4436 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4437 if(gameInfo.holdingsWidth > 1) {
4438 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4439 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4442 CopyBoard(partnerBoard, board);
4443 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4444 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4445 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4446 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4447 if(toSqr = strchr(str, '-')) {
4448 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4449 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4450 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4451 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4452 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4453 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4455 DisplayWhiteClock(white_time*fac, to_play == 'W');
4456 DisplayBlackClock(black_time*fac, to_play != 'W');
4457 activePartner = to_play;
4458 if(gamenum != lastBgGame) {
4460 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4463 lastBgGame = gamenum;
4464 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4465 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4466 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4467 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4468 if(!twoBoards) DisplayMessage(partnerStatus, "");
4469 partnerBoardValid = TRUE;
4473 if(appData.dualBoard && appData.bgObserve) {
4474 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4475 SendToICS(ics_prefix), SendToICS("pobserve\n");
4476 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4478 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4483 /* Modify behavior for initial board display on move listing
4486 switch (ics_getting_history) {
4490 case H_GOT_REQ_HEADER:
4491 case H_GOT_UNREQ_HEADER:
4492 /* This is the initial position of the current game */
4493 gamenum = ics_gamenum;
4494 moveNum = 0; /* old ICS bug workaround */
4495 if (to_play == 'B') {
4496 startedFromSetupPosition = TRUE;
4497 blackPlaysFirst = TRUE;
4499 if (forwardMostMove == 0) forwardMostMove = 1;
4500 if (backwardMostMove == 0) backwardMostMove = 1;
4501 if (currentMove == 0) currentMove = 1;
4503 newGameMode = gameMode;
4504 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4506 case H_GOT_UNWANTED_HEADER:
4507 /* This is an initial board that we don't want */
4509 case H_GETTING_MOVES:
4510 /* Should not happen */
4511 DisplayError(_("Error gathering move list: extra board"), 0);
4512 ics_getting_history = H_FALSE;
4516 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4517 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4518 weird && (int)gameInfo.variant < (int)VariantShogi) {
4519 /* [HGM] We seem to have switched variant unexpectedly
4520 * Try to guess new variant from board size
4522 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4523 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4524 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4525 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4526 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4527 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4528 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4529 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4530 /* Get a move list just to see the header, which
4531 will tell us whether this is really bug or zh */
4532 if (ics_getting_history == H_FALSE) {
4533 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4534 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4539 /* Take action if this is the first board of a new game, or of a
4540 different game than is currently being displayed. */
4541 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4542 relation == RELATION_ISOLATED_BOARD) {
4544 /* Forget the old game and get the history (if any) of the new one */
4545 if (gameMode != BeginningOfGame) {
4549 if (appData.autoRaiseBoard) BoardToTop();
4551 if (gamenum == -1) {
4552 newGameMode = IcsIdle;
4553 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4554 appData.getMoveList && !reqFlag) {
4555 /* Need to get game history */
4556 ics_getting_history = H_REQUESTED;
4557 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4561 /* Initially flip the board to have black on the bottom if playing
4562 black or if the ICS flip flag is set, but let the user change
4563 it with the Flip View button. */
4564 flipView = appData.autoFlipView ?
4565 (newGameMode == IcsPlayingBlack) || ics_flip :
4568 /* Done with values from previous mode; copy in new ones */
4569 gameMode = newGameMode;
4571 ics_gamenum = gamenum;
4572 if (gamenum == gs_gamenum) {
4573 int klen = strlen(gs_kind);
4574 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4575 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4576 gameInfo.event = StrSave(str);
4578 gameInfo.event = StrSave("ICS game");
4580 gameInfo.site = StrSave(appData.icsHost);
4581 gameInfo.date = PGNDate();
4582 gameInfo.round = StrSave("-");
4583 gameInfo.white = StrSave(white);
4584 gameInfo.black = StrSave(black);
4585 timeControl = basetime * 60 * 1000;
4587 timeIncrement = increment * 1000;
4588 movesPerSession = 0;
4589 gameInfo.timeControl = TimeControlTagValue();
4590 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4591 if (appData.debugMode) {
4592 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4593 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4594 setbuf(debugFP, NULL);
4597 gameInfo.outOfBook = NULL;
4599 /* Do we have the ratings? */
4600 if (strcmp(player1Name, white) == 0 &&
4601 strcmp(player2Name, black) == 0) {
4602 if (appData.debugMode)
4603 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4604 player1Rating, player2Rating);
4605 gameInfo.whiteRating = player1Rating;
4606 gameInfo.blackRating = player2Rating;
4607 } else if (strcmp(player2Name, white) == 0 &&
4608 strcmp(player1Name, black) == 0) {
4609 if (appData.debugMode)
4610 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4611 player2Rating, player1Rating);
4612 gameInfo.whiteRating = player2Rating;
4613 gameInfo.blackRating = player1Rating;
4615 player1Name[0] = player2Name[0] = NULLCHAR;
4617 /* Silence shouts if requested */
4618 if (appData.quietPlay &&
4619 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4620 SendToICS(ics_prefix);
4621 SendToICS("set shout 0\n");
4625 /* Deal with midgame name changes */
4627 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4628 if (gameInfo.white) free(gameInfo.white);
4629 gameInfo.white = StrSave(white);
4631 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4632 if (gameInfo.black) free(gameInfo.black);
4633 gameInfo.black = StrSave(black);
4637 /* Throw away game result if anything actually changes in examine mode */
4638 if (gameMode == IcsExamining && !newGame) {
4639 gameInfo.result = GameUnfinished;
4640 if (gameInfo.resultDetails != NULL) {
4641 free(gameInfo.resultDetails);
4642 gameInfo.resultDetails = NULL;
4646 /* In pausing && IcsExamining mode, we ignore boards coming
4647 in if they are in a different variation than we are. */
4648 if (pauseExamInvalid) return;
4649 if (pausing && gameMode == IcsExamining) {
4650 if (moveNum <= pauseExamForwardMostMove) {
4651 pauseExamInvalid = TRUE;
4652 forwardMostMove = pauseExamForwardMostMove;
4657 if (appData.debugMode) {
4658 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4660 /* Parse the board */
4661 for (k = 0; k < ranks; k++) {
4662 for (j = 0; j < files; j++)
4663 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4664 if(gameInfo.holdingsWidth > 1) {
4665 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4666 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4669 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4670 board[5][BOARD_RGHT+1] = WhiteAngel;
4671 board[6][BOARD_RGHT+1] = WhiteMarshall;
4672 board[1][0] = BlackMarshall;
4673 board[2][0] = BlackAngel;
4674 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4676 CopyBoard(boards[moveNum], board);
4677 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4679 startedFromSetupPosition =
4680 !CompareBoards(board, initialPosition);
4681 if(startedFromSetupPosition)
4682 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4685 /* [HGM] Set castling rights. Take the outermost Rooks,
4686 to make it also work for FRC opening positions. Note that board12
4687 is really defective for later FRC positions, as it has no way to
4688 indicate which Rook can castle if they are on the same side of King.
4689 For the initial position we grant rights to the outermost Rooks,
4690 and remember thos rights, and we then copy them on positions
4691 later in an FRC game. This means WB might not recognize castlings with
4692 Rooks that have moved back to their original position as illegal,
4693 but in ICS mode that is not its job anyway.
4695 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4696 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4698 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4699 if(board[0][i] == WhiteRook) j = i;
4700 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4701 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4702 if(board[0][i] == WhiteRook) j = i;
4703 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4704 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4705 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4706 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4707 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4708 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4709 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4711 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4712 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4713 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4714 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4715 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4716 if(board[BOARD_HEIGHT-1][k] == bKing)
4717 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4718 if(gameInfo.variant == VariantTwoKings) {
4719 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4720 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4721 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4724 r = boards[moveNum][CASTLING][0] = initialRights[0];
4725 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4726 r = boards[moveNum][CASTLING][1] = initialRights[1];
4727 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4728 r = boards[moveNum][CASTLING][3] = initialRights[3];
4729 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4730 r = boards[moveNum][CASTLING][4] = initialRights[4];
4731 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4732 /* wildcastle kludge: always assume King has rights */
4733 r = boards[moveNum][CASTLING][2] = initialRights[2];
4734 r = boards[moveNum][CASTLING][5] = initialRights[5];
4736 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4737 boards[moveNum][EP_STATUS] = EP_NONE;
4738 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4739 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4740 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4743 if (ics_getting_history == H_GOT_REQ_HEADER ||
4744 ics_getting_history == H_GOT_UNREQ_HEADER) {
4745 /* This was an initial position from a move list, not
4746 the current position */
4750 /* Update currentMove and known move number limits */
4751 newMove = newGame || moveNum > forwardMostMove;
4754 forwardMostMove = backwardMostMove = currentMove = moveNum;
4755 if (gameMode == IcsExamining && moveNum == 0) {
4756 /* Workaround for ICS limitation: we are not told the wild
4757 type when starting to examine a game. But if we ask for
4758 the move list, the move list header will tell us */
4759 ics_getting_history = H_REQUESTED;
4760 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4763 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4764 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4766 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4767 /* [HGM] applied this also to an engine that is silently watching */
4768 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4769 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4770 gameInfo.variant == currentlyInitializedVariant) {
4771 takeback = forwardMostMove - moveNum;
4772 for (i = 0; i < takeback; i++) {
4773 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4774 SendToProgram("undo\n", &first);
4779 forwardMostMove = moveNum;
4780 if (!pausing || currentMove > forwardMostMove)
4781 currentMove = forwardMostMove;
4783 /* New part of history that is not contiguous with old part */
4784 if (pausing && gameMode == IcsExamining) {
4785 pauseExamInvalid = TRUE;
4786 forwardMostMove = pauseExamForwardMostMove;
4789 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4791 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4792 // [HGM] when we will receive the move list we now request, it will be
4793 // fed to the engine from the first move on. So if the engine is not
4794 // in the initial position now, bring it there.
4795 InitChessProgram(&first, 0);
4798 ics_getting_history = H_REQUESTED;
4799 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4802 forwardMostMove = backwardMostMove = currentMove = moveNum;
4805 /* Update the clocks */
4806 if (strchr(elapsed_time, '.')) {
4808 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4809 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4811 /* Time is in seconds */
4812 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4813 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4818 if (appData.zippyPlay && newGame &&
4819 gameMode != IcsObserving && gameMode != IcsIdle &&
4820 gameMode != IcsExamining)
4821 ZippyFirstBoard(moveNum, basetime, increment);
4824 /* Put the move on the move list, first converting
4825 to canonical algebraic form. */
4827 if (appData.debugMode) {
4828 int f = forwardMostMove;
4829 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4830 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4831 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4832 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4833 fprintf(debugFP, "moveNum = %d\n", moveNum);
4834 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4835 setbuf(debugFP, NULL);
4837 if (moveNum <= backwardMostMove) {
4838 /* We don't know what the board looked like before
4840 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4841 strcat(parseList[moveNum - 1], " ");
4842 strcat(parseList[moveNum - 1], elapsed_time);
4843 moveList[moveNum - 1][0] = NULLCHAR;
4844 } else if (strcmp(move_str, "none") == 0) {
4845 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4846 /* Again, we don't know what the board looked like;
4847 this is really the start of the game. */
4848 parseList[moveNum - 1][0] = NULLCHAR;
4849 moveList[moveNum - 1][0] = NULLCHAR;
4850 backwardMostMove = moveNum;
4851 startedFromSetupPosition = TRUE;
4852 fromX = fromY = toX = toY = -1;
4854 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4855 // So we parse the long-algebraic move string in stead of the SAN move
4856 int valid; char buf[MSG_SIZ], *prom;
4858 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4859 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4860 // str looks something like "Q/a1-a2"; kill the slash
4862 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4863 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4864 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4865 strcat(buf, prom); // long move lacks promo specification!
4866 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4867 if(appData.debugMode)
4868 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4869 safeStrCpy(move_str, buf, MSG_SIZ);
4871 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4872 &fromX, &fromY, &toX, &toY, &promoChar)
4873 || ParseOneMove(buf, moveNum - 1, &moveType,
4874 &fromX, &fromY, &toX, &toY, &promoChar);
4875 // end of long SAN patch
4877 (void) CoordsToAlgebraic(boards[moveNum - 1],
4878 PosFlags(moveNum - 1),
4879 fromY, fromX, toY, toX, promoChar,
4880 parseList[moveNum-1]);
4881 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4887 if(!IS_SHOGI(gameInfo.variant))
4888 strcat(parseList[moveNum - 1], "+");
4891 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4892 strcat(parseList[moveNum - 1], "#");
4895 strcat(parseList[moveNum - 1], " ");
4896 strcat(parseList[moveNum - 1], elapsed_time);
4897 /* currentMoveString is set as a side-effect of ParseOneMove */
4898 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4899 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4900 strcat(moveList[moveNum - 1], "\n");
4902 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4903 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4904 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4905 ChessSquare old, new = boards[moveNum][k][j];
4906 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4907 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4908 if(old == new) continue;
4909 if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4910 else if(new == WhiteWazir || new == BlackWazir) {
4911 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4912 boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4913 else boards[moveNum][k][j] = old; // preserve type of Gold
4914 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4915 boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4918 /* Move from ICS was illegal!? Punt. */
4919 if (appData.debugMode) {
4920 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4921 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4923 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4924 strcat(parseList[moveNum - 1], " ");
4925 strcat(parseList[moveNum - 1], elapsed_time);
4926 moveList[moveNum - 1][0] = NULLCHAR;
4927 fromX = fromY = toX = toY = -1;
4930 if (appData.debugMode) {
4931 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4932 setbuf(debugFP, NULL);
4936 /* Send move to chess program (BEFORE animating it). */
4937 if (appData.zippyPlay && !newGame && newMove &&
4938 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4940 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4941 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4942 if (moveList[moveNum - 1][0] == NULLCHAR) {
4943 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4945 DisplayError(str, 0);
4947 if (first.sendTime) {
4948 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4950 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4951 if (firstMove && !bookHit) {
4953 if (first.useColors) {
4954 SendToProgram(gameMode == IcsPlayingWhite ?
4956 "black\ngo\n", &first);
4958 SendToProgram("go\n", &first);
4960 first.maybeThinking = TRUE;
4963 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4964 if (moveList[moveNum - 1][0] == NULLCHAR) {
4965 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4966 DisplayError(str, 0);
4968 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4969 SendMoveToProgram(moveNum - 1, &first);
4976 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4977 /* If move comes from a remote source, animate it. If it
4978 isn't remote, it will have already been animated. */
4979 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4980 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4982 if (!pausing && appData.highlightLastMove) {
4983 SetHighlights(fromX, fromY, toX, toY);
4987 /* Start the clocks */
4988 whiteFlag = blackFlag = FALSE;
4989 appData.clockMode = !(basetime == 0 && increment == 0);
4991 ics_clock_paused = TRUE;
4993 } else if (ticking == 1) {
4994 ics_clock_paused = FALSE;
4996 if (gameMode == IcsIdle ||
4997 relation == RELATION_OBSERVING_STATIC ||
4998 relation == RELATION_EXAMINING ||
5000 DisplayBothClocks();
5004 /* Display opponents and material strengths */
5005 if (gameInfo.variant != VariantBughouse &&
5006 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
5007 if (tinyLayout || smallLayout) {
5008 if(gameInfo.variant == VariantNormal)
5009 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5010 gameInfo.white, white_stren, gameInfo.black, black_stren,
5011 basetime, increment);
5013 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5014 gameInfo.white, white_stren, gameInfo.black, black_stren,
5015 basetime, increment, (int) gameInfo.variant);
5017 if(gameInfo.variant == VariantNormal)
5018 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5019 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5020 basetime, increment);
5022 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5023 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5024 basetime, increment, VariantName(gameInfo.variant));
5027 if (appData.debugMode) {
5028 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5033 /* Display the board */
5034 if (!pausing && !appData.noGUI) {
5036 if (appData.premove)
5038 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5039 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5040 ClearPremoveHighlights();
5042 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5043 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5044 DrawPosition(j, boards[currentMove]);
5046 DisplayMove(moveNum - 1);
5047 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5048 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5049 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
5050 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5054 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5056 if(bookHit) { // [HGM] book: simulate book reply
5057 static char bookMove[MSG_SIZ]; // a bit generous?
5059 programStats.nodes = programStats.depth = programStats.time =
5060 programStats.score = programStats.got_only_move = 0;
5061 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5063 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5064 strcat(bookMove, bookHit);
5065 HandleMachineMove(bookMove, &first);
5074 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5075 ics_getting_history = H_REQUESTED;
5076 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5082 SendToBoth (char *msg)
5083 { // to make it easy to keep two engines in step in dual analysis
5084 SendToProgram(msg, &first);
5085 if(second.analyzing) SendToProgram(msg, &second);
5089 AnalysisPeriodicEvent (int force)
5091 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5092 && !force) || !appData.periodicUpdates)
5095 /* Send . command to Crafty to collect stats */
5098 /* Don't send another until we get a response (this makes
5099 us stop sending to old Crafty's which don't understand
5100 the "." command (sending illegal cmds resets node count & time,
5101 which looks bad)) */
5102 programStats.ok_to_send = 0;
5106 ics_update_width (int new_width)
5108 ics_printf("set width %d\n", new_width);
5112 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5116 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5117 if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5118 sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5119 SendToProgram(buf, cps);
5122 // null move in variant where engine does not understand it (for analysis purposes)
5123 SendBoard(cps, moveNum + 1); // send position after move in stead.
5126 if (cps->useUsermove) {
5127 SendToProgram("usermove ", cps);
5131 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5132 int len = space - parseList[moveNum];
5133 memcpy(buf, parseList[moveNum], len);
5135 buf[len] = NULLCHAR;
5137 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5139 SendToProgram(buf, cps);
5141 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5142 AlphaRank(moveList[moveNum], 4);
5143 SendToProgram(moveList[moveNum], cps);
5144 AlphaRank(moveList[moveNum], 4); // and back
5146 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5147 * the engine. It would be nice to have a better way to identify castle
5149 if(appData.fischerCastling && cps->useOOCastle) {
5150 int fromX = moveList[moveNum][0] - AAA;
5151 int fromY = moveList[moveNum][1] - ONE;
5152 int toX = moveList[moveNum][2] - AAA;
5153 int toY = moveList[moveNum][3] - ONE;
5154 if((boards[moveNum][fromY][fromX] == WhiteKing
5155 && boards[moveNum][toY][toX] == WhiteRook)
5156 || (boards[moveNum][fromY][fromX] == BlackKing
5157 && boards[moveNum][toY][toX] == BlackRook)) {
5158 if(toX > fromX) SendToProgram("O-O\n", cps);
5159 else SendToProgram("O-O-O\n", cps);
5161 else SendToProgram(moveList[moveNum], cps);
5163 if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5164 char *m = moveList[moveNum];
5166 *c = m[7]; if(*c == '\n') *c = NULLCHAR; // promoChar
5167 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
5168 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5171 m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5172 else if(*c && m[8]) { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5173 *c = m[9]; if(*c == '\n') *c = NULLCHAR;
5174 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
5179 m[2], m[3] - '0', c);
5181 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5184 m[2], m[3] - '0', c);
5185 SendToProgram(buf, cps);
5187 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5188 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5189 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5190 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5191 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5193 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5194 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5195 SendToProgram(buf, cps);
5197 else SendToProgram(moveList[moveNum], cps);
5198 /* End of additions by Tord */
5201 /* [HGM] setting up the opening has brought engine in force mode! */
5202 /* Send 'go' if we are in a mode where machine should play. */
5203 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5204 (gameMode == TwoMachinesPlay ||
5206 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5208 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5209 SendToProgram("go\n", cps);
5210 if (appData.debugMode) {
5211 fprintf(debugFP, "(extra)\n");
5214 setboardSpoiledMachineBlack = 0;
5218 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5220 char user_move[MSG_SIZ];
5223 if(gameInfo.variant == VariantSChess && promoChar) {
5224 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5225 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5226 } else suffix[0] = NULLCHAR;
5230 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5231 (int)moveType, fromX, fromY, toX, toY);
5232 DisplayError(user_move + strlen("say "), 0);
5234 case WhiteKingSideCastle:
5235 case BlackKingSideCastle:
5236 case WhiteQueenSideCastleWild:
5237 case BlackQueenSideCastleWild:
5239 case WhiteHSideCastleFR:
5240 case BlackHSideCastleFR:
5242 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5244 case WhiteQueenSideCastle:
5245 case BlackQueenSideCastle:
5246 case WhiteKingSideCastleWild:
5247 case BlackKingSideCastleWild:
5249 case WhiteASideCastleFR:
5250 case BlackASideCastleFR:
5252 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5254 case WhiteNonPromotion:
5255 case BlackNonPromotion:
5256 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5258 case WhitePromotion:
5259 case BlackPromotion:
5260 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5261 gameInfo.variant == VariantMakruk)
5262 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5263 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5264 PieceToChar(WhiteFerz));
5265 else if(gameInfo.variant == VariantGreat)
5266 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5267 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5268 PieceToChar(WhiteMan));
5270 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5271 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5277 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5278 ToUpper(PieceToChar((ChessSquare) fromX)),
5279 AAA + toX, ONE + toY);
5281 case IllegalMove: /* could be a variant we don't quite understand */
5282 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5284 case WhiteCapturesEnPassant:
5285 case BlackCapturesEnPassant:
5286 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5287 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5290 SendToICS(user_move);
5291 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5292 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5297 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5298 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5299 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5300 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5301 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5304 if(gameMode != IcsExamining) { // is this ever not the case?
5305 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5307 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5308 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5309 } else { // on FICS we must first go to general examine mode
5310 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5312 if(gameInfo.variant != VariantNormal) {
5313 // try figure out wild number, as xboard names are not always valid on ICS
5314 for(i=1; i<=36; i++) {
5315 snprintf(buf, MSG_SIZ, "wild/%d", i);
5316 if(StringToVariant(buf) == gameInfo.variant) break;
5318 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5319 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5320 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5321 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5322 SendToICS(ics_prefix);
5324 if(startedFromSetupPosition || backwardMostMove != 0) {
5325 fen = PositionToFEN(backwardMostMove, NULL, 1);
5326 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5327 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5329 } else { // FICS: everything has to set by separate bsetup commands
5330 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5331 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5333 if(!WhiteOnMove(backwardMostMove)) {
5334 SendToICS("bsetup tomove black\n");
5336 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5337 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5339 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5340 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5342 i = boards[backwardMostMove][EP_STATUS];
5343 if(i >= 0) { // set e.p.
5344 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5350 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5351 SendToICS("bsetup done\n"); // switch to normal examining.
5353 for(i = backwardMostMove; i<last; i++) {
5355 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5356 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5357 int len = strlen(moveList[i]);
5358 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5359 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5363 SendToICS(ics_prefix);
5364 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5367 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5371 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5373 if (rf == DROP_RANK) {
5374 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5375 sprintf(move, "%c@%c%c\n",
5376 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5378 if (promoChar == 'x' || promoChar == NULLCHAR) {
5379 sprintf(move, "%c%c%c%c\n",
5380 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5381 if(killX >= 0 && killY >= 0) {
5382 sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5383 if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5386 sprintf(move, "%c%c%c%c%c\n",
5387 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5388 if(killX >= 0 && killY >= 0) {
5389 sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5390 if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5397 ProcessICSInitScript (FILE *f)
5401 while (fgets(buf, MSG_SIZ, f)) {
5402 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5409 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5411 static ClickType lastClickType;
5414 PieceInString (char *s, ChessSquare piece)
5416 char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5417 while((p = strchr(s, ID))) {
5418 if(!suffix || p[1] == suffix) return TRUE;
5425 Partner (ChessSquare *p)
5426 { // change piece into promotion partner if one shogi-promotes to the other
5427 ChessSquare partner = promoPartner[*p];
5428 if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5429 if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5437 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5438 static int toggleFlag;
5439 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5440 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5441 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5442 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5443 if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5444 if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5446 if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5447 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5448 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5449 else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5450 else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5451 if(!step) step = -1;
5452 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5453 !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5454 promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it
5455 promoSweep == pawn ||
5456 appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5457 (promoSweep == WhiteLion || promoSweep == BlackLion)));
5459 int victim = boards[currentMove][toY][toX];
5460 boards[currentMove][toY][toX] = promoSweep;
5461 DrawPosition(FALSE, boards[currentMove]);
5462 boards[currentMove][toY][toX] = victim;
5464 ChangeDragPiece(promoSweep);
5468 PromoScroll (int x, int y)
5472 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5473 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5474 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5475 if(!step) return FALSE;
5476 lastX = x; lastY = y;
5477 if((promoSweep < BlackPawn) == flipView) step = -step;
5478 if(step > 0) selectFlag = 1;
5479 if(!selectFlag) Sweep(step);
5484 NextPiece (int step)
5486 ChessSquare piece = boards[currentMove][toY][toX];
5489 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5490 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5491 if(!step) step = -1;
5492 } while(PieceToChar(pieceSweep) == '.');
5493 boards[currentMove][toY][toX] = pieceSweep;
5494 DrawPosition(FALSE, boards[currentMove]);
5495 boards[currentMove][toY][toX] = piece;
5497 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5499 AlphaRank (char *move, int n)
5501 // char *p = move, c; int x, y;
5503 if (appData.debugMode) {
5504 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5508 move[2]>='0' && move[2]<='9' &&
5509 move[3]>='a' && move[3]<='x' ) {
5511 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5512 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5514 if(move[0]>='0' && move[0]<='9' &&
5515 move[1]>='a' && move[1]<='x' &&
5516 move[2]>='0' && move[2]<='9' &&
5517 move[3]>='a' && move[3]<='x' ) {
5518 /* input move, Shogi -> normal */
5519 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5520 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5521 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5522 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5525 move[3]>='0' && move[3]<='9' &&
5526 move[2]>='a' && move[2]<='x' ) {
5528 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5529 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5532 move[0]>='a' && move[0]<='x' &&
5533 move[3]>='0' && move[3]<='9' &&
5534 move[2]>='a' && move[2]<='x' ) {
5535 /* output move, normal -> Shogi */
5536 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5537 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5538 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5539 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5540 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5542 if (appData.debugMode) {
5543 fprintf(debugFP, " out = '%s'\n", move);
5547 char yy_textstr[8000];
5549 /* Parser for moves from gnuchess, ICS, or user typein box */
5551 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5553 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5555 switch (*moveType) {
5556 case WhitePromotion:
5557 case BlackPromotion:
5558 case WhiteNonPromotion:
5559 case BlackNonPromotion:
5562 case WhiteCapturesEnPassant:
5563 case BlackCapturesEnPassant:
5564 case WhiteKingSideCastle:
5565 case WhiteQueenSideCastle:
5566 case BlackKingSideCastle:
5567 case BlackQueenSideCastle:
5568 case WhiteKingSideCastleWild:
5569 case WhiteQueenSideCastleWild:
5570 case BlackKingSideCastleWild:
5571 case BlackQueenSideCastleWild:
5572 /* Code added by Tord: */
5573 case WhiteHSideCastleFR:
5574 case WhiteASideCastleFR:
5575 case BlackHSideCastleFR:
5576 case BlackASideCastleFR:
5577 /* End of code added by Tord */
5578 case IllegalMove: /* bug or odd chess variant */
5579 if(currentMoveString[1] == '@') { // illegal drop
5580 *fromX = WhiteOnMove(moveNum) ?
5581 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5582 (int) CharToPiece(ToLower(currentMoveString[0]));
5585 *fromX = currentMoveString[0] - AAA;
5586 *fromY = currentMoveString[1] - ONE;
5587 *toX = currentMoveString[2] - AAA;
5588 *toY = currentMoveString[3] - ONE;
5589 *promoChar = currentMoveString[4];
5590 if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5591 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5592 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5593 if (appData.debugMode) {
5594 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5596 *fromX = *fromY = *toX = *toY = 0;
5599 if (appData.testLegality) {
5600 return (*moveType != IllegalMove);
5602 return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5603 // [HGM] lion: if this is a double move we are less critical
5604 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5609 *fromX = *moveType == WhiteDrop ?
5610 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5611 (int) CharToPiece(ToLower(currentMoveString[0]));
5614 *toX = currentMoveString[2] - AAA;
5615 *toY = currentMoveString[3] - ONE;
5616 *promoChar = NULLCHAR;
5620 case ImpossibleMove:
5630 if (appData.debugMode) {
5631 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5634 *fromX = *fromY = *toX = *toY = 0;
5635 *promoChar = NULLCHAR;
5640 Boolean pushed = FALSE;
5641 char *lastParseAttempt;
5644 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5645 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5646 int fromX, fromY, toX, toY; char promoChar;
5651 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5652 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5653 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5656 endPV = forwardMostMove;
5658 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5659 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5660 lastParseAttempt = pv;
5661 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5662 if(!valid && nr == 0 &&
5663 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5664 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5665 // Hande case where played move is different from leading PV move
5666 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5667 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5668 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5669 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5670 endPV += 2; // if position different, keep this
5671 moveList[endPV-1][0] = fromX + AAA;
5672 moveList[endPV-1][1] = fromY + ONE;
5673 moveList[endPV-1][2] = toX + AAA;
5674 moveList[endPV-1][3] = toY + ONE;
5675 parseList[endPV-1][0] = NULLCHAR;
5676 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5679 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5680 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5681 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5682 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5683 valid++; // allow comments in PV
5687 if(endPV+1 > framePtr) break; // no space, truncate
5690 CopyBoard(boards[endPV], boards[endPV-1]);
5691 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5692 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5693 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5694 CoordsToAlgebraic(boards[endPV - 1],
5695 PosFlags(endPV - 1),
5696 fromY, fromX, toY, toX, promoChar,
5697 parseList[endPV - 1]);
5699 if(atEnd == 2) return; // used hidden, for PV conversion
5700 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5701 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5702 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5703 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5704 DrawPosition(TRUE, boards[currentMove]);
5708 MultiPV (ChessProgramState *cps, int kind)
5709 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5711 for(i=0; i<cps->nrOptions; i++) {
5712 char *s = cps->option[i].name;
5713 if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5714 if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5715 && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5720 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5721 static int multi, pv_margin;
5722 static ChessProgramState *activeCps;
5725 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5727 int startPV, lineStart, origIndex = index;
5728 char *p, buf2[MSG_SIZ];
5729 ChessProgramState *cps = (pane ? &second : &first);
5731 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5732 lastX = x; lastY = y;
5733 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5734 lineStart = startPV = index;
5735 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5736 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5738 do{ while(buf[index] && buf[index] != '\n') index++;
5739 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5741 if(lineStart == 0 && gameMode == AnalyzeMode) {
5743 if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5744 if(n == 0) { // click not on "fewer" or "more"
5745 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5746 pv_margin = cps->option[multi].value;
5747 activeCps = cps; // non-null signals margin adjustment
5749 } else if((multi = MultiPV(cps, 1)) >= 0) {
5750 n += cps->option[multi].value; if(n < 1) n = 1;
5751 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5752 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5753 cps->option[multi].value = n;
5757 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5758 ExcludeClick(origIndex - lineStart);
5760 } else if(!strncmp(buf+lineStart, "dep\t", 4)) { // column headers clicked
5761 Collapse(origIndex - lineStart);
5764 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5765 *start = startPV; *end = index-1;
5766 extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5773 static char buf[10*MSG_SIZ];
5774 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5776 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5777 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5778 for(i = forwardMostMove; i<endPV; i++){
5779 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5780 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5783 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5784 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5785 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5791 LoadPV (int x, int y)
5792 { // called on right mouse click to load PV
5793 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5794 lastX = x; lastY = y;
5795 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5803 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5805 if(pv_margin != activeCps->option[multi].value) {
5807 snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5808 SendToProgram(buf, activeCps);
5809 activeCps->option[multi].value = pv_margin;
5814 if(endPV < 0) return;
5815 if(appData.autoCopyPV) CopyFENToClipboard();
5817 if(extendGame && currentMove > forwardMostMove) {
5818 Boolean saveAnimate = appData.animate;
5820 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5821 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5822 } else storedGames--; // abandon shelved tail of original game
5825 forwardMostMove = currentMove;
5826 currentMove = oldFMM;
5827 appData.animate = FALSE;
5828 ToNrEvent(forwardMostMove);
5829 appData.animate = saveAnimate;
5831 currentMove = forwardMostMove;
5832 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5833 ClearPremoveHighlights();
5834 DrawPosition(TRUE, boards[currentMove]);
5838 MovePV (int x, int y, int h)
5839 { // step through PV based on mouse coordinates (called on mouse move)
5840 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5842 if(activeCps) { // adjusting engine's multi-pv margin
5843 if(x > lastX) pv_margin++; else
5844 if(x < lastX) pv_margin -= (pv_margin > 0);
5847 snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5848 DisplayMessage(buf, "");
5853 // we must somehow check if right button is still down (might be released off board!)
5854 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5855 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5856 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5858 lastX = x; lastY = y;
5860 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5861 if(endPV < 0) return;
5862 if(y < margin) step = 1; else
5863 if(y > h - margin) step = -1;
5864 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5865 currentMove += step;
5866 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5867 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5868 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5869 DrawPosition(FALSE, boards[currentMove]);
5873 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5874 // All positions will have equal probability, but the current method will not provide a unique
5875 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5881 int piecesLeft[(int)BlackPawn];
5882 int seed, nrOfShuffles;
5885 GetPositionNumber ()
5886 { // sets global variable seed
5889 seed = appData.defaultFrcPosition;
5890 if(seed < 0) { // randomize based on time for negative FRC position numbers
5891 for(i=0; i<50; i++) seed += random();
5892 seed = random() ^ random() >> 8 ^ random() << 8;
5893 if(seed<0) seed = -seed;
5898 put (Board board, int pieceType, int rank, int n, int shade)
5899 // put the piece on the (n-1)-th empty squares of the given shade
5903 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5904 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5905 board[rank][i] = (ChessSquare) pieceType;
5906 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5908 piecesLeft[pieceType]--;
5917 AddOnePiece (Board board, int pieceType, int rank, int shade)
5918 // calculate where the next piece goes, (any empty square), and put it there
5922 i = seed % squaresLeft[shade];
5923 nrOfShuffles *= squaresLeft[shade];
5924 seed /= squaresLeft[shade];
5925 put(board, pieceType, rank, i, shade);
5929 AddTwoPieces (Board board, int pieceType, int rank)
5930 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5932 int i, n=squaresLeft[ANY], j=n-1, k;
5934 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5935 i = seed % k; // pick one
5938 while(i >= j) i -= j--;
5939 j = n - 1 - j; i += j;
5940 put(board, pieceType, rank, j, ANY);
5941 put(board, pieceType, rank, i, ANY);
5945 SetUpShuffle (Board board, int number)
5949 GetPositionNumber(); nrOfShuffles = 1;
5951 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5952 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5953 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5955 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5957 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5958 p = (int) board[0][i];
5959 if(p < (int) BlackPawn) piecesLeft[p] ++;
5960 board[0][i] = EmptySquare;
5963 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5964 // shuffles restricted to allow normal castling put KRR first
5965 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5966 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5967 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5968 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5969 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5970 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5971 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5972 put(board, WhiteRook, 0, 0, ANY);
5973 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5976 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5977 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5978 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5979 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5980 while(piecesLeft[p] >= 2) {
5981 AddOnePiece(board, p, 0, LITE);
5982 AddOnePiece(board, p, 0, DARK);
5984 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5987 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5988 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5989 // but we leave King and Rooks for last, to possibly obey FRC restriction
5990 if(p == (int)WhiteRook) continue;
5991 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5992 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5995 // now everything is placed, except perhaps King (Unicorn) and Rooks
5997 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5998 // Last King gets castling rights
5999 while(piecesLeft[(int)WhiteUnicorn]) {
6000 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6001 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
6004 while(piecesLeft[(int)WhiteKing]) {
6005 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6006 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
6011 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
6012 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6015 // Only Rooks can be left; simply place them all
6016 while(piecesLeft[(int)WhiteRook]) {
6017 i = put(board, WhiteRook, 0, 0, ANY);
6018 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6021 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
6023 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
6026 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6027 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6030 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6034 ptclen (const char *s, char *escapes)
6037 if(!*escapes) return strlen(s);
6038 while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6043 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6044 /* [HGM] moved here from winboard.c because of its general usefulness */
6045 /* Basically a safe strcpy that uses the last character as King */
6047 int result = FALSE; int NrPieces;
6048 unsigned char partner[EmptySquare];
6050 if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6051 && NrPieces >= 12 && !(NrPieces&1)) {
6052 int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6054 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6055 for( i=offs=0; i<NrPieces/2-1; i++ ) {
6057 if(map[j] == '/') offs = WhitePBishop - i, j++;
6058 if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6059 table[i+offs] = map[j++];
6060 if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6061 if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6062 if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6064 table[(int) WhiteKing] = map[j++];
6065 for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6067 if(map[j] == '/') offs = WhitePBishop - ii, j++;
6068 i = WHITE_TO_BLACK ii;
6069 if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6070 table[i+offs] = map[j++];
6071 if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6072 if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6073 if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6075 table[(int) BlackKing] = map[j++];
6078 if(*escapes) { // set up promotion pairing
6079 for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6080 // pieceToChar entirely filled, so we can look up specified partners
6081 for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6083 if(c == '^' || c == '-') { // has specified partner
6085 for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6086 if(c == '^') table[i] = '+';
6087 if(p < EmptySquare) {
6088 if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6089 if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6090 promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6092 } else if(c == '*') {
6093 table[i] = partner[i];
6094 promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6106 SetCharTable (unsigned char *table, const char * map)
6108 return SetCharTableEsc(table, map, "");
6112 Prelude (Board board)
6113 { // [HGM] superchess: random selection of exo-pieces
6114 int i, j, k; ChessSquare p;
6115 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6117 GetPositionNumber(); // use FRC position number
6119 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6120 SetCharTable(pieceToChar, appData.pieceToCharTable);
6121 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6122 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6125 j = seed%4; seed /= 4;
6126 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6127 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6128 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
6129 j = seed%3 + (seed%3 >= j); seed /= 3;
6130 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6131 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6132 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
6133 j = seed%3; seed /= 3;
6134 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6135 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6136 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
6137 j = seed%2 + (seed%2 >= j); seed /= 2;
6138 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6139 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6140 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
6141 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
6142 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
6143 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6144 put(board, exoPieces[0], 0, 0, ANY);
6145 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6149 InitPosition (int redraw)
6151 ChessSquare (* pieces)[BOARD_FILES];
6152 int i, j, pawnRow=1, pieceRows=1, overrule,
6153 oldx = gameInfo.boardWidth,
6154 oldy = gameInfo.boardHeight,
6155 oldh = gameInfo.holdingsWidth;
6158 if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6160 /* [AS] Initialize pv info list [HGM] and game status */
6162 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6163 pvInfoList[i].depth = 0;
6164 boards[i][EP_STATUS] = EP_NONE;
6165 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6168 initialRulePlies = 0; /* 50-move counter start */
6170 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6171 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6175 /* [HGM] logic here is completely changed. In stead of full positions */
6176 /* the initialized data only consist of the two backranks. The switch */
6177 /* selects which one we will use, which is than copied to the Board */
6178 /* initialPosition, which for the rest is initialized by Pawns and */
6179 /* empty squares. This initial position is then copied to boards[0], */
6180 /* possibly after shuffling, so that it remains available. */
6182 gameInfo.holdingsWidth = 0; /* default board sizes */
6183 gameInfo.boardWidth = 8;
6184 gameInfo.boardHeight = 8;
6185 gameInfo.holdingsSize = 0;
6186 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6187 for(i=0; i<BOARD_FILES-6; i++)
6188 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6189 initialPosition[EP_STATUS] = EP_NONE;
6190 initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6191 SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6192 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6193 SetCharTable(pieceNickName, appData.pieceNickNames);
6194 else SetCharTable(pieceNickName, "............");
6197 switch (gameInfo.variant) {
6198 case VariantFischeRandom:
6199 shuffleOpenings = TRUE;
6200 appData.fischerCastling = TRUE;
6203 case VariantShatranj:
6204 pieces = ShatranjArray;
6205 nrCastlingRights = 0;
6206 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6209 pieces = makrukArray;
6210 nrCastlingRights = 0;
6211 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6214 pieces = aseanArray;
6215 nrCastlingRights = 0;
6216 SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6218 case VariantTwoKings:
6219 pieces = twoKingsArray;
6222 pieces = GrandArray;
6223 nrCastlingRights = 0;
6224 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6225 gameInfo.boardWidth = 10;
6226 gameInfo.boardHeight = 10;
6227 gameInfo.holdingsSize = 7;
6229 case VariantCapaRandom:
6230 shuffleOpenings = TRUE;
6231 appData.fischerCastling = TRUE;
6232 case VariantCapablanca:
6233 pieces = CapablancaArray;
6234 gameInfo.boardWidth = 10;
6235 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6238 pieces = GothicArray;
6239 gameInfo.boardWidth = 10;
6240 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6243 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6244 gameInfo.holdingsSize = 7;
6245 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6248 pieces = JanusArray;
6249 gameInfo.boardWidth = 10;
6250 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6251 nrCastlingRights = 6;
6252 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6253 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6254 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6255 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6256 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6257 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6260 pieces = FalconArray;
6261 gameInfo.boardWidth = 10;
6262 SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6264 case VariantXiangqi:
6265 pieces = XiangqiArray;
6266 gameInfo.boardWidth = 9;
6267 gameInfo.boardHeight = 10;
6268 nrCastlingRights = 0;
6269 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6272 pieces = ShogiArray;
6273 gameInfo.boardWidth = 9;
6274 gameInfo.boardHeight = 9;
6275 gameInfo.holdingsSize = 7;
6276 nrCastlingRights = 0;
6277 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6280 pieces = ChuArray; pieceRows = 3;
6281 gameInfo.boardWidth = 12;
6282 gameInfo.boardHeight = 12;
6283 nrCastlingRights = 0;
6284 // SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6285 // "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6286 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"
6287 "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);
6289 case VariantCourier:
6290 pieces = CourierArray;
6291 gameInfo.boardWidth = 12;
6292 nrCastlingRights = 0;
6293 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6295 case VariantKnightmate:
6296 pieces = KnightmateArray;
6297 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6299 case VariantSpartan:
6300 pieces = SpartanArray;
6301 SetCharTable(pieceToChar, "PNBRQ.....................K......lw......g...h......ck");
6305 SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6307 case VariantChuChess:
6308 pieces = ChuChessArray;
6309 gameInfo.boardWidth = 10;
6310 gameInfo.boardHeight = 10;
6311 SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6314 pieces = fairyArray;
6315 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6318 pieces = GreatArray;
6319 gameInfo.boardWidth = 10;
6320 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6321 gameInfo.holdingsSize = 8;
6325 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6326 gameInfo.holdingsSize = 8;
6327 startedFromSetupPosition = TRUE;
6329 case VariantCrazyhouse:
6330 case VariantBughouse:
6332 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6333 gameInfo.holdingsSize = 5;
6335 case VariantWildCastle:
6337 /* !!?shuffle with kings guaranteed to be on d or e file */
6338 shuffleOpenings = 1;
6340 case VariantNoCastle:
6342 nrCastlingRights = 0;
6343 /* !!?unconstrained back-rank shuffle */
6344 shuffleOpenings = 1;
6349 if(appData.NrFiles >= 0) {
6350 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6351 gameInfo.boardWidth = appData.NrFiles;
6353 if(appData.NrRanks >= 0) {
6354 gameInfo.boardHeight = appData.NrRanks;
6356 if(appData.holdingsSize >= 0) {
6357 i = appData.holdingsSize;
6358 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6359 gameInfo.holdingsSize = i;
6361 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6362 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6363 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6365 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6366 if(pawnRow < 1) pawnRow = 1;
6367 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6368 gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6369 if(gameInfo.variant == VariantChu) pawnRow = 3;
6371 /* User pieceToChar list overrules defaults */
6372 if(appData.pieceToCharTable != NULL)
6373 SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6375 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6377 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6378 s = (ChessSquare) 0; /* account holding counts in guard band */
6379 for( i=0; i<BOARD_HEIGHT; i++ )
6380 initialPosition[i][j] = s;
6382 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6383 initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6384 initialPosition[pawnRow][j] = WhitePawn;
6385 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6386 if(gameInfo.variant == VariantXiangqi) {
6388 initialPosition[pawnRow][j] =
6389 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6390 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6391 initialPosition[2][j] = WhiteCannon;
6392 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6396 if(gameInfo.variant == VariantChu) {
6397 if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6398 initialPosition[pawnRow+1][j] = WhiteCobra,
6399 initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6400 for(i=1; i<pieceRows; i++) {
6401 initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6402 initialPosition[BOARD_HEIGHT-1-i][j] = pieces[2*i+1][j-gameInfo.holdingsWidth];
6405 if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6406 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6407 initialPosition[0][j] = WhiteRook;
6408 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6411 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] = pieces[1][j-gameInfo.holdingsWidth];
6413 if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6414 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6417 initialPosition[1][j] = WhiteBishop;
6418 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6420 initialPosition[1][j] = WhiteRook;
6421 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6424 if( nrCastlingRights == -1) {
6425 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6426 /* This sets default castling rights from none to normal corners */
6427 /* Variants with other castling rights must set them themselves above */
6428 nrCastlingRights = 6;
6430 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6431 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6432 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6433 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6434 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6435 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6438 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6439 if(gameInfo.variant == VariantGreat) { // promotion commoners
6440 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6441 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6442 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6443 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6445 if( gameInfo.variant == VariantSChess ) {
6446 initialPosition[1][0] = BlackMarshall;
6447 initialPosition[2][0] = BlackAngel;
6448 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6449 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6450 initialPosition[1][1] = initialPosition[2][1] =
6451 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6453 if (appData.debugMode) {
6454 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6456 if(shuffleOpenings) {
6457 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6458 startedFromSetupPosition = TRUE;
6460 if(startedFromPositionFile) {
6461 /* [HGM] loadPos: use PositionFile for every new game */
6462 CopyBoard(initialPosition, filePosition);
6463 for(i=0; i<nrCastlingRights; i++)
6464 initialRights[i] = filePosition[CASTLING][i];
6465 startedFromSetupPosition = TRUE;
6467 if(*appData.men) LoadPieceDesc(appData.men);
6469 CopyBoard(boards[0], initialPosition);
6471 if(oldx != gameInfo.boardWidth ||
6472 oldy != gameInfo.boardHeight ||
6473 oldv != gameInfo.variant ||
6474 oldh != gameInfo.holdingsWidth
6476 InitDrawingSizes(-2 ,0);
6478 oldv = gameInfo.variant;
6480 DrawPosition(TRUE, boards[currentMove]);
6484 SendBoard (ChessProgramState *cps, int moveNum)
6486 char message[MSG_SIZ];
6488 if (cps->useSetboard) {
6489 char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6490 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6491 SendToProgram(message, cps);
6496 int i, j, left=0, right=BOARD_WIDTH;
6497 /* Kludge to set black to move, avoiding the troublesome and now
6498 * deprecated "black" command.
6500 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6501 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6503 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6505 SendToProgram("edit\n", cps);
6506 SendToProgram("#\n", cps);
6507 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6508 bp = &boards[moveNum][i][left];
6509 for (j = left; j < right; j++, bp++) {
6510 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6511 if ((int) *bp < (int) BlackPawn) {
6512 if(j == BOARD_RGHT+1)
6513 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6514 else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6515 if(message[0] == '+' || message[0] == '~') {
6516 snprintf(message, MSG_SIZ,"%c%c%d+\n",
6517 PieceToChar((ChessSquare)(DEMOTED(*bp))),
6518 AAA + j, ONE + i - '0');
6520 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6521 message[1] = BOARD_RGHT - 1 - j + '1';
6522 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6524 SendToProgram(message, cps);
6529 SendToProgram("c\n", cps);
6530 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6531 bp = &boards[moveNum][i][left];
6532 for (j = left; j < right; j++, bp++) {
6533 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6534 if (((int) *bp != (int) EmptySquare)
6535 && ((int) *bp >= (int) BlackPawn)) {
6536 if(j == BOARD_LEFT-2)
6537 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6538 else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6539 AAA + j, ONE + i - '0');
6540 if(message[0] == '+' || message[0] == '~') {
6541 snprintf(message, MSG_SIZ,"%c%c%d+\n",
6542 PieceToChar((ChessSquare)(DEMOTED(*bp))),
6543 AAA + j, ONE + i - '0');
6545 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6546 message[1] = BOARD_RGHT - 1 - j + '1';
6547 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6549 SendToProgram(message, cps);
6554 SendToProgram(".\n", cps);
6556 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6559 char exclusionHeader[MSG_SIZ];
6560 int exCnt, excludePtr;
6561 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6562 static Exclusion excluTab[200];
6563 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6569 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6570 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6576 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6577 excludePtr = 24; exCnt = 0;
6582 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6583 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6584 char buf[2*MOVE_LEN], *p;
6585 Exclusion *e = excluTab;
6587 for(i=0; i<exCnt; i++)
6588 if(e[i].ff == fromX && e[i].fr == fromY &&
6589 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6590 if(i == exCnt) { // was not in exclude list; add it
6591 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6592 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6593 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6596 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6597 excludePtr++; e[i].mark = excludePtr++;
6598 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6601 exclusionHeader[e[i].mark] = state;
6605 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6606 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6610 if((signed char)promoChar == -1) { // kludge to indicate best move
6611 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6612 return 1; // if unparsable, abort
6614 // update exclusion map (resolving toggle by consulting existing state)
6615 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6617 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6618 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6619 excludeMap[k] |= 1<<j;
6620 else excludeMap[k] &= ~(1<<j);
6622 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6624 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6625 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6627 return (state == '+');
6631 ExcludeClick (int index)
6634 Exclusion *e = excluTab;
6635 if(index < 25) { // none, best or tail clicked
6636 if(index < 13) { // none: include all
6637 WriteMap(0); // clear map
6638 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6639 SendToBoth("include all\n"); // and inform engine
6640 } else if(index > 18) { // tail
6641 if(exclusionHeader[19] == '-') { // tail was excluded
6642 SendToBoth("include all\n");
6643 WriteMap(0); // clear map completely
6644 // now re-exclude selected moves
6645 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6646 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6647 } else { // tail was included or in mixed state
6648 SendToBoth("exclude all\n");
6649 WriteMap(0xFF); // fill map completely
6650 // now re-include selected moves
6651 j = 0; // count them
6652 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6653 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6654 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6657 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6660 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6661 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6662 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6669 DefaultPromoChoice (int white)
6672 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6673 gameInfo.variant == VariantMakruk)
6674 result = WhiteFerz; // no choice
6675 else if(gameInfo.variant == VariantASEAN)
6676 result = WhiteRook; // no choice
6677 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6678 result= WhiteKing; // in Suicide Q is the last thing we want
6679 else if(gameInfo.variant == VariantSpartan)
6680 result = white ? WhiteQueen : WhiteAngel;
6681 else result = WhiteQueen;
6682 if(!white) result = WHITE_TO_BLACK result;
6686 static int autoQueen; // [HGM] oneclick
6689 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6691 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6692 /* [HGM] add Shogi promotions */
6693 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6694 ChessSquare piece, partner;
6698 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6699 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6701 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6702 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6705 piece = boards[currentMove][fromY][fromX];
6706 if(gameInfo.variant == VariantChu) {
6707 promotionZoneSize = BOARD_HEIGHT/3;
6708 highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6709 } else if(gameInfo.variant == VariantShogi) {
6710 promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6711 highestPromotingPiece = (int)WhiteAlfil;
6712 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6713 promotionZoneSize = 3;
6716 // Treat Lance as Pawn when it is not representing Amazon or Lance
6717 if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6718 if(piece == WhiteLance) piece = WhitePawn; else
6719 if(piece == BlackLance) piece = BlackPawn;
6722 // next weed out all moves that do not touch the promotion zone at all
6723 if((int)piece >= BlackPawn) {
6724 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6726 if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6727 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6729 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6730 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6731 if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6735 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6737 // weed out mandatory Shogi promotions
6738 if(gameInfo.variant == VariantShogi) {
6739 if(piece >= BlackPawn) {
6740 if(toY == 0 && piece == BlackPawn ||
6741 toY == 0 && piece == BlackQueen ||
6742 toY <= 1 && piece == BlackKnight) {
6747 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6748 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6749 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6756 // weed out obviously illegal Pawn moves
6757 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6758 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6759 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6760 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6761 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6762 // note we are not allowed to test for valid (non-)capture, due to premove
6765 // we either have a choice what to promote to, or (in Shogi) whether to promote
6766 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6767 gameInfo.variant == VariantMakruk) {
6768 ChessSquare p=BlackFerz; // no choice
6769 while(p < EmptySquare) { //but make sure we use piece that exists
6770 *promoChoice = PieceToChar(p++);
6771 if(*promoChoice != '.') break;
6773 if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6775 // no sense asking what we must promote to if it is going to explode...
6776 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6777 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6780 // give caller the default choice even if we will not make it
6781 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6782 partner = piece; // pieces can promote if the pieceToCharTable says so
6783 if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6784 else if(Partner(&partner)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6785 if( sweepSelect && gameInfo.variant != VariantGreat
6786 && gameInfo.variant != VariantGrand
6787 && gameInfo.variant != VariantSuper) return FALSE;
6788 if(autoQueen) return FALSE; // predetermined
6790 // suppress promotion popup on illegal moves that are not premoves
6791 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6792 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6793 if(appData.testLegality && !premove) {
6794 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6795 fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6796 if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6797 if(moveType != WhitePromotion && moveType != BlackPromotion)
6805 InPalace (int row, int column)
6806 { /* [HGM] for Xiangqi */
6807 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6808 column < (BOARD_WIDTH + 4)/2 &&
6809 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6814 PieceForSquare (int x, int y)
6816 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6819 return boards[currentMove][y][x];
6823 OKToStartUserMove (int x, int y)
6825 ChessSquare from_piece;
6828 if (matchMode) return FALSE;
6829 if (gameMode == EditPosition) return TRUE;
6831 if (x >= 0 && y >= 0)
6832 from_piece = boards[currentMove][y][x];
6834 from_piece = EmptySquare;
6836 if (from_piece == EmptySquare) return FALSE;
6838 white_piece = (int)from_piece >= (int)WhitePawn &&
6839 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6843 case TwoMachinesPlay:
6851 case MachinePlaysWhite:
6852 case IcsPlayingBlack:
6853 if (appData.zippyPlay) return FALSE;
6855 DisplayMoveError(_("You are playing Black"));
6860 case MachinePlaysBlack:
6861 case IcsPlayingWhite:
6862 if (appData.zippyPlay) return FALSE;
6864 DisplayMoveError(_("You are playing White"));
6869 case PlayFromGameFile:
6870 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6873 if (!white_piece && WhiteOnMove(currentMove)) {
6874 DisplayMoveError(_("It is White's turn"));
6877 if (white_piece && !WhiteOnMove(currentMove)) {
6878 DisplayMoveError(_("It is Black's turn"));
6881 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6882 /* Editing correspondence game history */
6883 /* Could disallow this or prompt for confirmation */
6888 case BeginningOfGame:
6889 if (appData.icsActive) return FALSE;
6890 if (!appData.noChessProgram) {
6892 DisplayMoveError(_("You are playing White"));
6899 if (!white_piece && WhiteOnMove(currentMove)) {
6900 DisplayMoveError(_("It is White's turn"));
6903 if (white_piece && !WhiteOnMove(currentMove)) {
6904 DisplayMoveError(_("It is Black's turn"));
6913 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6914 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6915 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6916 && gameMode != AnalyzeFile && gameMode != Training) {
6917 DisplayMoveError(_("Displayed position is not current"));
6924 OnlyMove (int *x, int *y, Boolean captures)
6926 DisambiguateClosure cl;
6927 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6929 case MachinePlaysBlack:
6930 case IcsPlayingWhite:
6931 case BeginningOfGame:
6932 if(!WhiteOnMove(currentMove)) return FALSE;
6934 case MachinePlaysWhite:
6935 case IcsPlayingBlack:
6936 if(WhiteOnMove(currentMove)) return FALSE;
6943 cl.pieceIn = EmptySquare;
6948 cl.promoCharIn = NULLCHAR;
6949 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6950 if( cl.kind == NormalMove ||
6951 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6952 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6953 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6960 if(cl.kind != ImpossibleMove) return FALSE;
6961 cl.pieceIn = EmptySquare;
6966 cl.promoCharIn = NULLCHAR;
6967 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6968 if( cl.kind == NormalMove ||
6969 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6970 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6971 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6976 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6982 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6983 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6984 int lastLoadGameUseList = FALSE;
6985 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6986 ChessMove lastLoadGameStart = EndOfFile;
6988 Boolean addToBookFlag;
6989 static Board rightsBoard, nullBoard;
6992 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
6996 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6998 /* Check if the user is playing in turn. This is complicated because we
6999 let the user "pick up" a piece before it is his turn. So the piece he
7000 tried to pick up may have been captured by the time he puts it down!
7001 Therefore we use the color the user is supposed to be playing in this
7002 test, not the color of the piece that is currently on the starting
7003 square---except in EditGame mode, where the user is playing both
7004 sides; fortunately there the capture race can't happen. (It can
7005 now happen in IcsExamining mode, but that's just too bad. The user
7006 will get a somewhat confusing message in that case.)
7011 case TwoMachinesPlay:
7015 /* We switched into a game mode where moves are not accepted,
7016 perhaps while the mouse button was down. */
7019 case MachinePlaysWhite:
7020 /* User is moving for Black */
7021 if (WhiteOnMove(currentMove)) {
7022 DisplayMoveError(_("It is White's turn"));
7027 case MachinePlaysBlack:
7028 /* User is moving for White */
7029 if (!WhiteOnMove(currentMove)) {
7030 DisplayMoveError(_("It is Black's turn"));
7035 case PlayFromGameFile:
7036 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7039 case BeginningOfGame:
7042 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7043 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7044 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7045 /* User is moving for Black */
7046 if (WhiteOnMove(currentMove)) {
7047 DisplayMoveError(_("It is White's turn"));
7051 /* User is moving for White */
7052 if (!WhiteOnMove(currentMove)) {
7053 DisplayMoveError(_("It is Black's turn"));
7059 case IcsPlayingBlack:
7060 /* User is moving for Black */
7061 if (WhiteOnMove(currentMove)) {
7062 if (!appData.premove) {
7063 DisplayMoveError(_("It is White's turn"));
7064 } else if (toX >= 0 && toY >= 0) {
7067 premoveFromX = fromX;
7068 premoveFromY = fromY;
7069 premovePromoChar = promoChar;
7071 if (appData.debugMode)
7072 fprintf(debugFP, "Got premove: fromX %d,"
7073 "fromY %d, toX %d, toY %d\n",
7074 fromX, fromY, toX, toY);
7076 DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7081 case IcsPlayingWhite:
7082 /* User is moving for White */
7083 if (!WhiteOnMove(currentMove)) {
7084 if (!appData.premove) {
7085 DisplayMoveError(_("It is Black's turn"));
7086 } else if (toX >= 0 && toY >= 0) {
7089 premoveFromX = fromX;
7090 premoveFromY = fromY;
7091 premovePromoChar = promoChar;
7093 if (appData.debugMode)
7094 fprintf(debugFP, "Got premove: fromX %d,"
7095 "fromY %d, toX %d, toY %d\n",
7096 fromX, fromY, toX, toY);
7098 DrawPosition(TRUE, boards[currentMove]);
7107 /* EditPosition, empty square, or different color piece;
7108 click-click move is possible */
7109 if (toX == -2 || toY == -2) {
7110 boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7111 DrawPosition(FALSE, boards[currentMove]);
7113 } else if (toX >= 0 && toY >= 0) {
7114 if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7115 ChessSquare p = boards[0][rf][ff];
7116 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7117 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7118 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
7119 int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7120 DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7123 } else rightsBoard[toY][toX] = 0; // revoke rights on moving
7124 boards[0][toY][toX] = boards[0][fromY][fromX];
7125 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7126 if(boards[0][fromY][0] != EmptySquare) {
7127 if(boards[0][fromY][1]) boards[0][fromY][1]--;
7128 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
7131 if(fromX == BOARD_RGHT+1) {
7132 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7133 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7134 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7137 boards[0][fromY][fromX] = gatingPiece;
7139 DrawPosition(FALSE, boards[currentMove]);
7145 if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7146 pup = boards[currentMove][toY][toX];
7148 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7149 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7150 if( pup != EmptySquare ) return;
7151 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7152 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7153 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7154 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7155 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7156 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7157 while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7161 /* [HGM] always test for legality, to get promotion info */
7162 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7163 fromY, fromX, toY, toX, promoChar);
7165 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7167 if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7169 /* [HGM] but possibly ignore an IllegalMove result */
7170 if (appData.testLegality) {
7171 if (moveType == IllegalMove || moveType == ImpossibleMove) {
7172 DisplayMoveError(_("Illegal move"));
7177 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7178 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7179 ClearPremoveHighlights(); // was included
7180 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
7181 DrawPosition(FALSE, NULL);
7185 if(addToBookFlag) { // adding moves to book
7186 char buf[MSG_SIZ], move[MSG_SIZ];
7187 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7188 if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7189 killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7190 snprintf(buf, MSG_SIZ, " 0.0%% 1 %s\n", move);
7192 addToBookFlag = FALSE;
7197 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7200 /* Common tail of UserMoveEvent and DropMenuEvent */
7202 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7206 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7207 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7208 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7209 if(WhiteOnMove(currentMove)) {
7210 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7212 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7216 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7217 move type in caller when we know the move is a legal promotion */
7218 if(moveType == NormalMove && promoChar)
7219 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7221 /* [HGM] <popupFix> The following if has been moved here from
7222 UserMoveEvent(). Because it seemed to belong here (why not allow
7223 piece drops in training games?), and because it can only be
7224 performed after it is known to what we promote. */
7225 if (gameMode == Training) {
7226 /* compare the move played on the board to the next move in the
7227 * game. If they match, display the move and the opponent's response.
7228 * If they don't match, display an error message.
7232 CopyBoard(testBoard, boards[currentMove]);
7233 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7235 if (CompareBoards(testBoard, boards[currentMove+1])) {
7236 ForwardInner(currentMove+1);
7238 /* Autoplay the opponent's response.
7239 * if appData.animate was TRUE when Training mode was entered,
7240 * the response will be animated.
7242 saveAnimate = appData.animate;
7243 appData.animate = animateTraining;
7244 ForwardInner(currentMove+1);
7245 appData.animate = saveAnimate;
7247 /* check for the end of the game */
7248 if (currentMove >= forwardMostMove) {
7249 gameMode = PlayFromGameFile;
7251 SetTrainingModeOff();
7252 DisplayInformation(_("End of game"));
7255 DisplayError(_("Incorrect move"), 0);
7260 /* Ok, now we know that the move is good, so we can kill
7261 the previous line in Analysis Mode */
7262 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7263 && currentMove < forwardMostMove) {
7264 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7265 else forwardMostMove = currentMove;
7270 /* If we need the chess program but it's dead, restart it */
7271 ResurrectChessProgram();
7273 /* A user move restarts a paused game*/
7277 thinkOutput[0] = NULLCHAR;
7279 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7281 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7282 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7286 if (gameMode == BeginningOfGame) {
7287 if (appData.noChessProgram) {
7288 gameMode = EditGame;
7292 gameMode = MachinePlaysBlack;
7295 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7297 if (first.sendName) {
7298 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7299 SendToProgram(buf, &first);
7306 /* Relay move to ICS or chess engine */
7307 if (appData.icsActive) {
7308 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7309 gameMode == IcsExamining) {
7310 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7311 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7313 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7315 // also send plain move, in case ICS does not understand atomic claims
7316 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7320 if (first.sendTime && (gameMode == BeginningOfGame ||
7321 gameMode == MachinePlaysWhite ||
7322 gameMode == MachinePlaysBlack)) {
7323 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7325 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7326 // [HGM] book: if program might be playing, let it use book
7327 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7328 first.maybeThinking = TRUE;
7329 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7330 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7331 SendBoard(&first, currentMove+1);
7332 if(second.analyzing) {
7333 if(!second.useSetboard) SendToProgram("undo\n", &second);
7334 SendBoard(&second, currentMove+1);
7337 SendMoveToProgram(forwardMostMove-1, &first);
7338 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7340 if (currentMove == cmailOldMove + 1) {
7341 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7345 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7349 if(appData.testLegality)
7350 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7356 if (WhiteOnMove(currentMove)) {
7357 GameEnds(BlackWins, "Black mates", GE_PLAYER);
7359 GameEnds(WhiteWins, "White mates", GE_PLAYER);
7363 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7368 case MachinePlaysBlack:
7369 case MachinePlaysWhite:
7370 /* disable certain menu options while machine is thinking */
7371 SetMachineThinkingEnables();
7378 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7379 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7381 if(bookHit) { // [HGM] book: simulate book reply
7382 static char bookMove[MSG_SIZ]; // a bit generous?
7384 programStats.nodes = programStats.depth = programStats.time =
7385 programStats.score = programStats.got_only_move = 0;
7386 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7388 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7389 strcat(bookMove, bookHit);
7390 HandleMachineMove(bookMove, &first);
7396 MarkByFEN(char *fen)
7399 if(!appData.markers || !appData.highlightDragging) return;
7400 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7401 r=BOARD_HEIGHT-1-deadRanks; f=BOARD_LEFT;
7405 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7406 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7407 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7408 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7409 if(*fen == 'T') marker[r][f++] = 0; else
7410 if(*fen == 'Y') marker[r][f++] = 1; else
7411 if(*fen == 'G') marker[r][f++] = 3; else
7412 if(*fen == 'B') marker[r][f++] = 4; else
7413 if(*fen == 'C') marker[r][f++] = 5; else
7414 if(*fen == 'M') marker[r][f++] = 6; else
7415 if(*fen == 'W') marker[r][f++] = 7; else
7416 if(*fen == 'D') marker[r][f++] = 8; else
7417 if(*fen == 'R') marker[r][f++] = 2; else {
7418 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7421 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7425 DrawPosition(TRUE, NULL);
7428 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7431 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7433 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7434 Markers *m = (Markers *) closure;
7435 if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7436 kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7437 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7438 || kind == WhiteCapturesEnPassant
7439 || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7440 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7443 static int hoverSavedValid;
7446 MarkTargetSquares (int clear)
7449 if(clear) { // no reason to ever suppress clearing
7450 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7451 hoverSavedValid = 0;
7452 if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7455 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7456 !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7457 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7458 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7459 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7461 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7464 DrawPosition(FALSE, NULL);
7468 Explode (Board board, int fromX, int fromY, int toX, int toY)
7470 if(gameInfo.variant == VariantAtomic &&
7471 (board[toY][toX] != EmptySquare || // capture?
7472 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7473 board[fromY][fromX] == BlackPawn )
7475 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7481 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7484 CanPromote (ChessSquare piece, int y)
7486 int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7487 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7488 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7489 if(IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantXiangqi ||
7490 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7491 (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7492 gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7493 return (piece == BlackPawn && y <= zone ||
7494 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7495 piece == BlackLance && y <= zone ||
7496 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7500 HoverEvent (int xPix, int yPix, int x, int y)
7502 static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7504 if(!first.highlight) return;
7505 if(fromX != oldFromX || fromY != oldFromY) oldX = oldY = -1; // kludge to fake entry on from-click
7506 if(x == oldX && y == oldY) return; // only do something if we enter new square
7507 oldFromX = fromX; oldFromY = fromY;
7508 if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7509 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7510 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7511 hoverSavedValid = 1;
7512 } else if(oldX != x || oldY != y) {
7513 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7514 if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7515 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7516 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7517 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7519 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7520 SendToProgram(buf, &first);
7523 // SetHighlights(fromX, fromY, x, y);
7527 void ReportClick(char *action, int x, int y)
7529 char buf[MSG_SIZ]; // Inform engine of what user does
7531 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7532 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7533 legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7534 if(!first.highlight || gameMode == EditPosition) return;
7535 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7536 SendToProgram(buf, &first);
7539 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7542 LeftClick (ClickType clickType, int xPix, int yPix)
7545 Boolean saveAnimate;
7546 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7547 char promoChoice = NULLCHAR;
7549 static TimeMark lastClickTime, prevClickTime;
7551 if(flashing) return;
7553 x = EventToSquare(xPix, BOARD_WIDTH);
7554 y = EventToSquare(yPix, BOARD_HEIGHT);
7555 if (!flipView && y >= 0) {
7556 y = BOARD_HEIGHT - 1 - y;
7558 if (flipView && x >= 0) {
7559 x = BOARD_WIDTH - 1 - x;
7562 if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7564 RightClick(clickType, xPix, yPix, &dummy, &dummy);
7569 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7571 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7573 if (clickType == Press) ErrorPopDown();
7574 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7576 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7577 defaultPromoChoice = promoSweep;
7578 promoSweep = EmptySquare; // terminate sweep
7579 promoDefaultAltered = TRUE;
7580 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7583 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7584 if(clickType == Release) return; // ignore upclick of click-click destination
7585 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7586 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7587 if(gameInfo.holdingsWidth &&
7588 (WhiteOnMove(currentMove)
7589 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7590 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7591 // click in right holdings, for determining promotion piece
7592 ChessSquare p = boards[currentMove][y][x];
7593 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7594 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7595 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7596 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7601 DrawPosition(FALSE, boards[currentMove]);
7605 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7606 if(clickType == Press
7607 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7608 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7609 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7612 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7613 // could be static click on premove from-square: abort premove
7615 ClearPremoveHighlights();
7618 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7619 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7621 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7622 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7623 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7624 defaultPromoChoice = DefaultPromoChoice(side);
7627 autoQueen = appData.alwaysPromoteToQueen;
7631 gatingPiece = EmptySquare;
7632 if (clickType != Press) {
7633 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7634 DragPieceEnd(xPix, yPix); dragging = 0;
7635 DrawPosition(FALSE, NULL);
7639 doubleClick = FALSE;
7640 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7641 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7643 fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1;
7644 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7645 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7646 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7648 if (OKToStartUserMove(fromX, fromY)) {
7650 ReportClick("lift", x, y);
7651 MarkTargetSquares(0);
7652 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7653 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7654 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7655 promoSweep = defaultPromoChoice;
7656 selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7657 Sweep(0); // Pawn that is going to promote: preview promotion piece
7658 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7660 if (appData.highlightDragging) {
7661 SetHighlights(fromX, fromY, -1, -1);
7665 } else fromX = fromY = -1;
7671 if (clickType == Press && gameMode != EditPosition) {
7676 // ignore off-board to clicks
7677 if(y < 0 || x < 0) return;
7679 /* Check if clicking again on the same color piece */
7680 fromP = boards[currentMove][fromY][fromX];
7681 toP = boards[currentMove][y][x];
7682 frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7683 if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7684 marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7685 ((WhitePawn <= fromP && fromP <= WhiteKing &&
7686 WhitePawn <= toP && toP <= WhiteKing &&
7687 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7688 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7689 (BlackPawn <= fromP && fromP <= BlackKing &&
7690 BlackPawn <= toP && toP <= BlackKing &&
7691 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7692 !(fromP == BlackKing && toP == BlackRook && frc)))) {
7693 /* Clicked again on same color piece -- changed his mind */
7694 second = (x == fromX && y == fromY);
7695 killX = killY = kill2X = kill2Y = -1;
7696 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7697 second = FALSE; // first double-click rather than scond click
7698 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7700 promoDefaultAltered = FALSE;
7701 if(!second) MarkTargetSquares(1);
7702 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7703 if (appData.highlightDragging) {
7704 SetHighlights(x, y, -1, -1);
7708 if (OKToStartUserMove(x, y)) {
7709 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7710 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7711 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7712 gatingPiece = boards[currentMove][fromY][fromX];
7713 else gatingPiece = doubleClick ? fromP : EmptySquare;
7715 fromY = y; dragging = 1;
7716 if(!second) ReportClick("lift", x, y);
7717 MarkTargetSquares(0);
7718 DragPieceBegin(xPix, yPix, FALSE);
7719 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7720 promoSweep = defaultPromoChoice;
7721 selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7722 Sweep(0); // Pawn that is going to promote: preview promotion piece
7726 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7729 // ignore clicks on holdings
7730 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7733 if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7734 gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7735 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7739 if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7740 DragPieceEnd(xPix, yPix); dragging = 0;
7742 // a deferred attempt to click-click move an empty square on top of a piece
7743 boards[currentMove][y][x] = EmptySquare;
7745 DrawPosition(FALSE, boards[currentMove]);
7746 fromX = fromY = -1; clearFlag = 0;
7749 if (appData.animateDragging) {
7750 /* Undo animation damage if any */
7751 DrawPosition(FALSE, NULL);
7754 /* Second up/down in same square; just abort move */
7757 gatingPiece = EmptySquare;
7760 ClearPremoveHighlights();
7761 MarkTargetSquares(-1);
7762 DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7764 /* First upclick in same square; start click-click mode */
7765 SetHighlights(x, y, -1, -1);
7772 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7773 fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7774 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7775 DisplayMessage(_("only marked squares are legal"),"");
7776 DrawPosition(TRUE, NULL);
7777 return; // ignore to-click
7780 /* we now have a different from- and (possibly off-board) to-square */
7781 /* Completed move */
7782 if(!sweepSelecting) {
7787 piece = boards[currentMove][fromY][fromX];
7789 saveAnimate = appData.animate;
7790 if (clickType == Press) {
7791 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7792 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7793 // must be Edit Position mode with empty-square selected
7794 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7795 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7798 if(dragging == 2) { // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7801 if(x == killX && y == killY) { // second click on this square, which was selected as first-leg target
7802 killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1; // this informs us no second leg is coming, so treat as to-click without intermediate
7804 if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7805 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7806 if(appData.sweepSelect) {
7807 promoSweep = defaultPromoChoice;
7808 if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7809 selectFlag = 0; lastX = xPix; lastY = yPix;
7810 ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7811 saveFlash = appData.flashCount; appData.flashCount = 0;
7812 Sweep(0); // Pawn that is going to promote: preview promotion piece
7814 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7815 MarkTargetSquares(1);
7817 return; // promo popup appears on up-click
7819 /* Finish clickclick move */
7820 if (appData.animate || appData.highlightLastMove) {
7821 SetHighlights(fromX, fromY, toX, toY);
7825 MarkTargetSquares(1);
7826 } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7827 sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7828 *promoRestrict = 0; appData.flashCount = saveFlash;
7829 if (appData.animate || appData.highlightLastMove) {
7830 SetHighlights(fromX, fromY, toX, toY);
7834 MarkTargetSquares(1);
7837 // [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
7838 /* Finish drag move */
7839 if (appData.highlightLastMove) {
7840 SetHighlights(fromX, fromY, toX, toY);
7845 if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7846 defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7847 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7848 if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7849 dragging *= 2; // flag button-less dragging if we are dragging
7850 MarkTargetSquares(1);
7851 if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7853 kill2X = killX; kill2Y = killY;
7854 killX = x; killY = y; // remember this square as intermediate
7855 ReportClick("put", x, y); // and inform engine
7856 ReportClick("lift", x, y);
7857 MarkTargetSquares(0);
7861 DragPieceEnd(xPix, yPix); dragging = 0;
7862 /* Don't animate move and drag both */
7863 appData.animate = FALSE;
7864 MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7867 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7868 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7869 ChessSquare piece = boards[currentMove][fromY][fromX];
7870 if(gameMode == EditPosition && piece != EmptySquare &&
7871 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7874 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7875 n = PieceToNumber(piece - (int)BlackPawn);
7876 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7877 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7878 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7880 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7881 n = PieceToNumber(piece);
7882 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7883 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7884 boards[currentMove][n][BOARD_WIDTH-2]++;
7886 boards[currentMove][fromY][fromX] = EmptySquare;
7890 MarkTargetSquares(1);
7891 DrawPosition(TRUE, boards[currentMove]);
7895 // off-board moves should not be highlighted
7896 if(x < 0 || y < 0) ClearHighlights();
7897 else ReportClick("put", x, y);
7899 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7901 if(legal[toY][toX] == 2) { // highlight-induced promotion
7902 if(piece == defaultPromoChoice) promoChoice = NULLCHAR; // deferral
7903 else promoChoice = ToLower(PieceToChar(defaultPromoChoice));
7906 if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7907 SetHighlights(fromX, fromY, toX, toY);
7908 MarkTargetSquares(1);
7909 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7910 // [HGM] super: promotion to captured piece selected from holdings
7911 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7912 promotionChoice = TRUE;
7913 // kludge follows to temporarily execute move on display, without promoting yet
7914 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7915 boards[currentMove][toY][toX] = p;
7916 DrawPosition(FALSE, boards[currentMove]);
7917 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7918 boards[currentMove][toY][toX] = q;
7919 DisplayMessage("Click in holdings to choose piece", "");
7922 DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
7923 PromotionPopUp(promoChoice);
7925 int oldMove = currentMove;
7926 flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
7927 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7928 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7929 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
7930 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7931 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7932 DrawPosition(TRUE, boards[currentMove]);
7936 appData.animate = saveAnimate;
7937 if (appData.animate || appData.animateDragging) {
7938 /* Undo animation damage if needed */
7939 // DrawPosition(FALSE, NULL);
7944 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7945 { // front-end-free part taken out of PieceMenuPopup
7946 int whichMenu; int xSqr, ySqr;
7948 if(seekGraphUp) { // [HGM] seekgraph
7949 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7950 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7954 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7955 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7956 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7957 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7958 if(action == Press) {
7959 originalFlip = flipView;
7960 flipView = !flipView; // temporarily flip board to see game from partners perspective
7961 DrawPosition(TRUE, partnerBoard);
7962 DisplayMessage(partnerStatus, "");
7964 } else if(action == Release) {
7965 flipView = originalFlip;
7966 DrawPosition(TRUE, boards[currentMove]);
7972 xSqr = EventToSquare(x, BOARD_WIDTH);
7973 ySqr = EventToSquare(y, BOARD_HEIGHT);
7974 if (action == Release) {
7975 if(pieceSweep != EmptySquare) {
7976 EditPositionMenuEvent(pieceSweep, toX, toY);
7977 pieceSweep = EmptySquare;
7978 } else UnLoadPV(); // [HGM] pv
7980 if (action != Press) return -2; // return code to be ignored
7983 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7985 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7986 if (xSqr < 0 || ySqr < 0) return -1;
7987 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7988 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7989 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7990 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7994 if(!appData.icsEngineAnalyze) return -1;
7995 case IcsPlayingWhite:
7996 case IcsPlayingBlack:
7997 if(!appData.zippyPlay) goto noZip;
8000 case MachinePlaysWhite:
8001 case MachinePlaysBlack:
8002 case TwoMachinesPlay: // [HGM] pv: use for showing PV
8003 if (!appData.dropMenu) {
8005 return 2; // flag front-end to grab mouse events
8007 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8008 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8011 if (xSqr < 0 || ySqr < 0) return -1;
8012 if (!appData.dropMenu || appData.testLegality &&
8013 gameInfo.variant != VariantBughouse &&
8014 gameInfo.variant != VariantCrazyhouse) return -1;
8015 whichMenu = 1; // drop menu
8021 if (((*fromX = xSqr) < 0) ||
8022 ((*fromY = ySqr) < 0)) {
8023 *fromX = *fromY = -1;
8027 *fromX = BOARD_WIDTH - 1 - *fromX;
8029 *fromY = BOARD_HEIGHT - 1 - *fromY;
8035 Wheel (int dir, int x, int y)
8037 if(gameMode == EditPosition) {
8038 int xSqr = EventToSquare(x, BOARD_WIDTH);
8039 int ySqr = EventToSquare(y, BOARD_HEIGHT);
8040 if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8041 if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8043 boards[currentMove][ySqr][xSqr] += dir;
8044 if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8045 if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8046 } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8047 DrawPosition(FALSE, boards[currentMove]);
8048 } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8052 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8054 // char * hint = lastHint;
8055 FrontEndProgramStats stats;
8057 stats.which = cps == &first ? 0 : 1;
8058 stats.depth = cpstats->depth;
8059 stats.nodes = cpstats->nodes;
8060 stats.score = cpstats->score;
8061 stats.time = cpstats->time;
8062 stats.pv = cpstats->movelist;
8063 stats.hint = lastHint;
8064 stats.an_move_index = 0;
8065 stats.an_move_count = 0;
8067 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8068 stats.hint = cpstats->move_name;
8069 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8070 stats.an_move_count = cpstats->nr_moves;
8073 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
8075 if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8076 && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8078 SetProgramStats( &stats );
8082 ClearEngineOutputPane (int which)
8084 static FrontEndProgramStats dummyStats;
8085 dummyStats.which = which;
8086 dummyStats.pv = "#";
8087 SetProgramStats( &dummyStats );
8090 #define MAXPLAYERS 500
8093 TourneyStandings (int display)
8095 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8096 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8097 char result, *p, *names[MAXPLAYERS];
8099 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8100 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8101 names[0] = p = strdup(appData.participants);
8102 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8104 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8106 while(result = appData.results[nr]) {
8107 color = Pairing(nr, nPlayers, &w, &b, &dummy);
8108 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8109 wScore = bScore = 0;
8111 case '+': wScore = 2; break;
8112 case '-': bScore = 2; break;
8113 case '=': wScore = bScore = 1; break;
8115 case '*': return strdup("busy"); // tourney not finished
8123 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8124 for(w=0; w<nPlayers; w++) {
8126 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8127 ranking[w] = b; points[w] = bScore; score[b] = -2;
8129 p = malloc(nPlayers*34+1);
8130 for(w=0; w<nPlayers && w<display; w++)
8131 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8137 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8138 { // count all piece types
8140 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8141 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8142 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8145 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8146 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8147 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8148 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8149 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
8150 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8155 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8157 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8158 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8160 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8161 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8162 if(myPawns == 2 && nMine == 3) // KPP
8163 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8164 if(myPawns == 1 && nMine == 2) // KP
8165 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8166 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8167 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8168 if(myPawns) return FALSE;
8169 if(pCnt[WhiteRook+side])
8170 return pCnt[BlackRook-side] ||
8171 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8172 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8173 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8174 if(pCnt[WhiteCannon+side]) {
8175 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8176 return majorDefense || pCnt[BlackAlfil-side] >= 2;
8178 if(pCnt[WhiteKnight+side])
8179 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8184 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8186 VariantClass v = gameInfo.variant;
8188 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8189 if(v == VariantShatranj) return TRUE; // always winnable through baring
8190 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8191 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8193 if(v == VariantXiangqi) {
8194 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8196 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8197 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8198 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8199 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8200 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8201 if(stale) // we have at least one last-rank P plus perhaps C
8202 return majors // KPKX
8203 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8205 return pCnt[WhiteFerz+side] // KCAK
8206 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8207 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8208 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8210 } else if(v == VariantKnightmate) {
8211 if(nMine == 1) return FALSE;
8212 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8213 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8214 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8216 if(nMine == 1) return FALSE; // bare King
8217 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
8218 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8219 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8220 // by now we have King + 1 piece (or multiple Bishops on the same color)
8221 if(pCnt[WhiteKnight+side])
8222 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8223 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8224 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8226 return (pCnt[BlackKnight-side]); // KBKN, KFKN
8227 if(pCnt[WhiteAlfil+side])
8228 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8229 if(pCnt[WhiteWazir+side])
8230 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8237 CompareWithRights (Board b1, Board b2)
8240 if(!CompareBoards(b1, b2)) return FALSE;
8241 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8242 /* compare castling rights */
8243 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8244 rights++; /* King lost rights, while rook still had them */
8245 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8246 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8247 rights++; /* but at least one rook lost them */
8249 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8251 if( b1[CASTLING][5] != NoRights ) {
8252 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8259 Adjudicate (ChessProgramState *cps)
8260 { // [HGM] some adjudications useful with buggy engines
8261 // [HGM] adjudicate: made into separate routine, which now can be called after every move
8262 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8263 // Actually ending the game is now based on the additional internal condition canAdjudicate.
8264 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8265 int k, drop, count = 0; static int bare = 1;
8266 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8267 Boolean canAdjudicate = !appData.icsActive;
8269 // most tests only when we understand the game, i.e. legality-checking on
8270 if( appData.testLegality )
8271 { /* [HGM] Some more adjudications for obstinate engines */
8272 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8273 static int moveCount = 6;
8275 char *reason = NULL;
8277 /* Count what is on board. */
8278 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8280 /* Some material-based adjudications that have to be made before stalemate test */
8281 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8282 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8283 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8284 if(canAdjudicate && appData.checkMates) {
8286 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8287 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8288 "Xboard adjudication: King destroyed", GE_XBOARD );
8293 /* Bare King in Shatranj (loses) or Losers (wins) */
8294 if( nrW == 1 || nrB == 1) {
8295 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8296 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
8297 if(canAdjudicate && appData.checkMates) {
8299 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8300 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8301 "Xboard adjudication: Bare king", GE_XBOARD );
8305 if( gameInfo.variant == VariantShatranj && --bare < 0)
8307 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8308 if(canAdjudicate && appData.checkMates) {
8309 /* but only adjudicate if adjudication enabled */
8311 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8312 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8313 "Xboard adjudication: Bare king", GE_XBOARD );
8320 // don't wait for engine to announce game end if we can judge ourselves
8321 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8323 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8324 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
8325 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8326 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8329 reason = "Xboard adjudication: 3rd check";
8330 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8341 reason = "Xboard adjudication: Stalemate";
8342 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8343 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
8344 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8345 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
8346 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8347 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8348 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8349 EP_CHECKMATE : EP_WINS);
8350 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8351 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8355 reason = "Xboard adjudication: Checkmate";
8356 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8357 if(gameInfo.variant == VariantShogi) {
8358 if(forwardMostMove > backwardMostMove
8359 && moveList[forwardMostMove-1][1] == '@'
8360 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8361 reason = "XBoard adjudication: pawn-drop mate";
8362 boards[forwardMostMove][EP_STATUS] = EP_WINS;
8368 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8370 result = GameIsDrawn; break;
8372 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8374 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8378 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8380 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8381 GameEnds( result, reason, GE_XBOARD );
8385 /* Next absolutely insufficient mating material. */
8386 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8387 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8388 { /* includes KBK, KNK, KK of KBKB with like Bishops */
8390 /* always flag draws, for judging claims */
8391 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8393 if(canAdjudicate && appData.materialDraws) {
8394 /* but only adjudicate them if adjudication enabled */
8395 if(engineOpponent) {
8396 SendToProgram("force\n", engineOpponent); // suppress reply
8397 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8399 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8404 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8405 if(gameInfo.variant == VariantXiangqi ?
8406 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8408 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8409 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
8410 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
8411 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8413 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8414 { /* if the first 3 moves do not show a tactical win, declare draw */
8415 if(engineOpponent) {
8416 SendToProgram("force\n", engineOpponent); // suppress reply
8417 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8419 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8422 } else moveCount = 6;
8425 // Repetition draws and 50-move rule can be applied independently of legality testing
8427 /* Check for rep-draws */
8429 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8430 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8431 for(k = forwardMostMove-2;
8432 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8433 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8434 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8437 if(CompareBoards(boards[k], boards[forwardMostMove])) {
8438 /* compare castling rights */
8439 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8440 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8441 rights++; /* King lost rights, while rook still had them */
8442 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8443 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8444 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8445 rights++; /* but at least one rook lost them */
8447 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8448 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8450 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8451 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8452 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8455 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8456 && appData.drawRepeats > 1) {
8457 /* adjudicate after user-specified nr of repeats */
8458 int result = GameIsDrawn;
8459 char *details = "XBoard adjudication: repetition draw";
8460 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8461 // [HGM] xiangqi: check for forbidden perpetuals
8462 int m, ourPerpetual = 1, hisPerpetual = 1;
8463 for(m=forwardMostMove; m>k; m-=2) {
8464 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8465 ourPerpetual = 0; // the current mover did not always check
8466 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8467 hisPerpetual = 0; // the opponent did not always check
8469 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8470 ourPerpetual, hisPerpetual);
8471 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8472 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8473 details = "Xboard adjudication: perpetual checking";
8475 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8476 break; // (or we would have caught him before). Abort repetition-checking loop.
8478 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8479 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8481 details = "Xboard adjudication: repetition";
8483 } else // it must be XQ
8484 // Now check for perpetual chases
8485 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8486 hisPerpetual = PerpetualChase(k, forwardMostMove);
8487 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8488 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8489 static char resdet[MSG_SIZ];
8490 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8492 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8494 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8495 break; // Abort repetition-checking loop.
8497 // if neither of us is checking or chasing all the time, or both are, it is draw
8499 if(engineOpponent) {
8500 SendToProgram("force\n", engineOpponent); // suppress reply
8501 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8503 GameEnds( result, details, GE_XBOARD );
8506 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8507 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8511 /* Now we test for 50-move draws. Determine ply count */
8512 count = forwardMostMove;
8513 /* look for last irreversble move */
8514 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8516 /* if we hit starting position, add initial plies */
8517 if( count == backwardMostMove )
8518 count -= initialRulePlies;
8519 count = forwardMostMove - count;
8520 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8521 // adjust reversible move counter for checks in Xiangqi
8522 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8523 if(i < backwardMostMove) i = backwardMostMove;
8524 while(i <= forwardMostMove) {
8525 lastCheck = inCheck; // check evasion does not count
8526 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8527 if(inCheck || lastCheck) count--; // check does not count
8532 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8533 /* this is used to judge if draw claims are legal */
8534 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8535 if(engineOpponent) {
8536 SendToProgram("force\n", engineOpponent); // suppress reply
8537 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8539 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8543 /* if draw offer is pending, treat it as a draw claim
8544 * when draw condition present, to allow engines a way to
8545 * claim draws before making their move to avoid a race
8546 * condition occurring after their move
8548 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8550 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8551 p = "Draw claim: 50-move rule";
8552 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8553 p = "Draw claim: 3-fold repetition";
8554 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8555 p = "Draw claim: insufficient mating material";
8556 if( p != NULL && canAdjudicate) {
8557 if(engineOpponent) {
8558 SendToProgram("force\n", engineOpponent); // suppress reply
8559 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8561 GameEnds( GameIsDrawn, p, GE_XBOARD );
8566 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8567 if(engineOpponent) {
8568 SendToProgram("force\n", engineOpponent); // suppress reply
8569 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8571 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8577 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8578 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8579 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8584 int pieces[10], squares[10], cnt=0, r, f, res;
8586 static PPROBE_EGBB probeBB;
8587 if(!appData.testLegality) return 10;
8588 if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8589 if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8590 if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8591 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8592 ChessSquare piece = boards[forwardMostMove][r][f];
8593 int black = (piece >= BlackPawn);
8594 int type = piece - black*BlackPawn;
8595 if(piece == EmptySquare) continue;
8596 if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8597 if(type == WhiteKing) type = WhiteQueen + 1;
8598 type = egbbCode[type];
8599 squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8600 pieces[cnt] = type + black*6;
8601 if(++cnt > 5) return 11;
8603 pieces[cnt] = squares[cnt] = 0;
8605 if(loaded == 2) return 13; // loading failed before
8607 char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8610 loaded = 2; // prepare for failure
8611 if(!path) return 13; // no egbb installed
8612 strncpy(buf, path + 8, MSG_SIZ);
8613 if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8614 snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8615 lib = LoadLibrary(buf);
8616 if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8617 loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8618 probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8619 if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8620 p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8621 loaded = 1; // success!
8623 res = probeBB(forwardMostMove & 1, pieces, squares);
8624 return res > 0 ? 1 : res < 0 ? -1 : 0;
8628 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8629 { // [HGM] book: this routine intercepts moves to simulate book replies
8630 char *bookHit = NULL;
8632 if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8634 snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8635 SendToProgram(buf, cps);
8637 //first determine if the incoming move brings opponent into his book
8638 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8639 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8640 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8641 if(bookHit != NULL && !cps->bookSuspend) {
8642 // make sure opponent is not going to reply after receiving move to book position
8643 SendToProgram("force\n", cps);
8644 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8646 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8647 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8648 // now arrange restart after book miss
8650 // after a book hit we never send 'go', and the code after the call to this routine
8651 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8652 char buf[MSG_SIZ], *move = bookHit;
8654 int fromX, fromY, toX, toY;
8658 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8659 &fromX, &fromY, &toX, &toY, &promoChar)) {
8660 (void) CoordsToAlgebraic(boards[forwardMostMove],
8661 PosFlags(forwardMostMove),
8662 fromY, fromX, toY, toX, promoChar, move);
8664 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8668 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8669 SendToProgram(buf, cps);
8670 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8671 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8672 SendToProgram("go\n", cps);
8673 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8674 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8675 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8676 SendToProgram("go\n", cps);
8677 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8679 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8683 LoadError (char *errmess, ChessProgramState *cps)
8684 { // unloads engine and switches back to -ncp mode if it was first
8685 if(cps->initDone) return FALSE;
8686 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8687 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8690 appData.noChessProgram = TRUE;
8691 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8692 gameMode = BeginningOfGame; ModeHighlight();
8695 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8696 DisplayMessage("", ""); // erase waiting message
8697 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8702 ChessProgramState *savedState;
8704 DeferredBookMove (void)
8706 if(savedState->lastPing != savedState->lastPong)
8707 ScheduleDelayedEvent(DeferredBookMove, 10);
8709 HandleMachineMove(savedMessage, savedState);
8712 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8713 static ChessProgramState *stalledEngine;
8714 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8717 HandleMachineMove (char *message, ChessProgramState *cps)
8719 static char firstLeg[20], legs;
8720 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8721 char realname[MSG_SIZ];
8722 int fromX, fromY, toX, toY;
8724 char promoChar, roar;
8729 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8730 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8731 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8732 DisplayError(_("Invalid pairing from pairing engine"), 0);
8735 pairingReceived = 1;
8737 return; // Skim the pairing messages here.
8740 oldError = cps->userError; cps->userError = 0;
8742 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8744 * Kludge to ignore BEL characters
8746 while (*message == '\007') message++;
8749 * [HGM] engine debug message: ignore lines starting with '#' character
8751 if(cps->debug && *message == '#') return;
8754 * Look for book output
8756 if (cps == &first && bookRequested) {
8757 if (message[0] == '\t' || message[0] == ' ') {
8758 /* Part of the book output is here; append it */
8759 strcat(bookOutput, message);
8760 strcat(bookOutput, " \n");
8762 } else if (bookOutput[0] != NULLCHAR) {
8763 /* All of book output has arrived; display it */
8764 char *p = bookOutput;
8765 while (*p != NULLCHAR) {
8766 if (*p == '\t') *p = ' ';
8769 DisplayInformation(bookOutput);
8770 bookRequested = FALSE;
8771 /* Fall through to parse the current output */
8776 * Look for machine move.
8778 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8779 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8781 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8782 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8783 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8784 stalledEngine = cps;
8785 if(appData.ponderNextMove) { // bring opponent out of ponder
8786 if(gameMode == TwoMachinesPlay) {
8787 if(cps->other->pause)
8788 PauseEngine(cps->other);
8790 SendToProgram("easy\n", cps->other);
8799 /* This method is only useful on engines that support ping */
8800 if(abortEngineThink) {
8801 if (appData.debugMode) {
8802 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8804 SendToProgram("undo\n", cps);
8808 if (cps->lastPing != cps->lastPong) {
8809 /* Extra move from before last new; ignore */
8810 if (appData.debugMode) {
8811 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8818 int machineWhite = FALSE;
8821 case BeginningOfGame:
8822 /* Extra move from before last reset; ignore */
8823 if (appData.debugMode) {
8824 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8831 /* Extra move after we tried to stop. The mode test is
8832 not a reliable way of detecting this problem, but it's
8833 the best we can do on engines that don't support ping.
8835 if (appData.debugMode) {
8836 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8837 cps->which, gameMode);
8839 SendToProgram("undo\n", cps);
8842 case MachinePlaysWhite:
8843 case IcsPlayingWhite:
8844 machineWhite = TRUE;
8847 case MachinePlaysBlack:
8848 case IcsPlayingBlack:
8849 machineWhite = FALSE;
8852 case TwoMachinesPlay:
8853 machineWhite = (cps->twoMachinesColor[0] == 'w');
8856 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8857 if (appData.debugMode) {
8859 "Ignoring move out of turn by %s, gameMode %d"
8860 ", forwardMost %d\n",
8861 cps->which, gameMode, forwardMostMove);
8867 if(cps->alphaRank) AlphaRank(machineMove, 4);
8869 // [HGM] lion: (some very limited) support for Alien protocol
8870 killX = killY = kill2X = kill2Y = -1;
8871 if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8872 if(legs++) return; // middle leg contains only redundant info, ignore (but count it)
8873 safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8876 if(p = strchr(machineMove, ',')) { // we got both legs in one (happens on book move)
8877 char *q = strchr(p+1, ','); // second comma?
8878 safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8879 if(q) legs = 2, p = q; else legs = 1; // with 3-leg move we clipof first two legs!
8880 safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8882 if(firstLeg[0]) { // there was a previous leg;
8883 // only support case where same piece makes two step
8884 char buf[20], *p = machineMove+1, *q = buf+1, f;
8885 safeStrCpy(buf, machineMove, 20);
8886 while(isdigit(*q)) q++; // find start of to-square
8887 safeStrCpy(machineMove, firstLeg, 20);
8888 while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8889 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
8890 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)
8891 safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8892 sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8893 firstLeg[0] = NULLCHAR; legs = 0;
8896 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8897 &fromX, &fromY, &toX, &toY, &promoChar)) {
8898 /* Machine move could not be parsed; ignore it. */
8899 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8900 machineMove, _(cps->which));
8901 DisplayMoveError(buf1);
8902 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8903 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8904 if (gameMode == TwoMachinesPlay) {
8905 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8911 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8912 /* So we have to redo legality test with true e.p. status here, */
8913 /* to make sure an illegal e.p. capture does not slip through, */
8914 /* to cause a forfeit on a justified illegal-move complaint */
8915 /* of the opponent. */
8916 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8918 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8919 fromY, fromX, toY, toX, promoChar);
8920 if(moveType == IllegalMove) {
8921 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8922 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8923 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8926 } else if(!appData.fischerCastling)
8927 /* [HGM] Kludge to handle engines that send FRC-style castling
8928 when they shouldn't (like TSCP-Gothic) */
8930 case WhiteASideCastleFR:
8931 case BlackASideCastleFR:
8933 currentMoveString[2]++;
8935 case WhiteHSideCastleFR:
8936 case BlackHSideCastleFR:
8938 currentMoveString[2]--;
8940 default: ; // nothing to do, but suppresses warning of pedantic compilers
8943 hintRequested = FALSE;
8944 lastHint[0] = NULLCHAR;
8945 bookRequested = FALSE;
8946 /* Program may be pondering now */
8947 cps->maybeThinking = TRUE;
8948 if (cps->sendTime == 2) cps->sendTime = 1;
8949 if (cps->offeredDraw) cps->offeredDraw--;
8951 /* [AS] Save move info*/
8952 pvInfoList[ forwardMostMove ].score = programStats.score;
8953 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8954 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8956 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8958 /* Test suites abort the 'game' after one move */
8959 if(*appData.finger) {
8961 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8962 if(!f) f = fopen(appData.finger, "w");
8963 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8964 else { DisplayFatalError("Bad output file", errno, 0); return; }
8966 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8969 if(solvingTime >= 0) {
8970 snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
8971 totalTime += solvingTime; first.matchWins++; solvingTime = -1;
8973 snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
8974 if(solvingTime == -2) second.matchWins++;
8976 OutputKibitz(2, buf1);
8977 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8980 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8981 if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8984 while( count < adjudicateLossPlies ) {
8985 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8988 score = -score; /* Flip score for winning side */
8991 if( score > appData.adjudicateLossThreshold ) {
8998 if( count >= adjudicateLossPlies ) {
8999 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9001 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
9002 "Xboard adjudication",
9009 if(Adjudicate(cps)) {
9010 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9011 return; // [HGM] adjudicate: for all automatic game ends
9015 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9017 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9018 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9020 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9022 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9024 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9025 char buf[3*MSG_SIZ];
9027 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9028 programStats.score / 100.,
9030 programStats.time / 100.,
9031 (unsigned int)programStats.nodes,
9032 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9033 programStats.movelist);
9039 /* [AS] Clear stats for next move */
9040 ClearProgramStats();
9041 thinkOutput[0] = NULLCHAR;
9042 hiddenThinkOutputState = 0;
9045 if (gameMode == TwoMachinesPlay) {
9046 /* [HGM] relaying draw offers moved to after reception of move */
9047 /* and interpreting offer as claim if it brings draw condition */
9048 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9049 SendToProgram("draw\n", cps->other);
9051 if (cps->other->sendTime) {
9052 SendTimeRemaining(cps->other,
9053 cps->other->twoMachinesColor[0] == 'w');
9055 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9056 if (firstMove && !bookHit) {
9058 if (cps->other->useColors) {
9059 SendToProgram(cps->other->twoMachinesColor, cps->other);
9061 SendToProgram("go\n", cps->other);
9063 cps->other->maybeThinking = TRUE;
9066 roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9068 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9070 if (!pausing && appData.ringBellAfterMoves) {
9071 if(!roar) RingBell();
9075 * Reenable menu items that were disabled while
9076 * machine was thinking
9078 if (gameMode != TwoMachinesPlay)
9079 SetUserThinkingEnables();
9081 // [HGM] book: after book hit opponent has received move and is now in force mode
9082 // force the book reply into it, and then fake that it outputted this move by jumping
9083 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9085 static char bookMove[MSG_SIZ]; // a bit generous?
9087 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9088 strcat(bookMove, bookHit);
9091 programStats.nodes = programStats.depth = programStats.time =
9092 programStats.score = programStats.got_only_move = 0;
9093 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9095 if(cps->lastPing != cps->lastPong) {
9096 savedMessage = message; // args for deferred call
9098 ScheduleDelayedEvent(DeferredBookMove, 10);
9107 /* Set special modes for chess engines. Later something general
9108 * could be added here; for now there is just one kludge feature,
9109 * needed because Crafty 15.10 and earlier don't ignore SIGINT
9110 * when "xboard" is given as an interactive command.
9112 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9113 cps->useSigint = FALSE;
9114 cps->useSigterm = FALSE;
9116 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9117 ParseFeatures(message+8, cps);
9118 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9121 if (!strncmp(message, "setup ", 6) &&
9122 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9123 NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9124 ) { // [HGM] allow first engine to define opening position
9125 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9126 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9128 if(sscanf(message, "setup (%s", buf) == 1) {
9129 s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9130 ASSIGN(appData.pieceToCharTable, buf);
9132 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9134 while(message[s] && message[s++] != ' ');
9135 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9136 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9137 if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
9138 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9139 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
9140 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9141 if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9142 startedFromSetupPosition = FALSE;
9145 if(startedFromSetupPosition) return;
9146 ParseFEN(boards[0], &dummy, message+s, FALSE);
9147 DrawPosition(TRUE, boards[0]);
9148 CopyBoard(initialPosition, boards[0]);
9149 startedFromSetupPosition = TRUE;
9152 if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9153 ChessSquare piece = WhitePawn;
9154 char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0;
9155 if(*p == '+') promoted++, ID = *++p;
9156 if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9157 piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece);
9158 if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9159 /* always accept definition of */ && piece != WhiteFalcon && piece != BlackFalcon
9160 /* wild-card pieces. */ && piece != WhiteCobra && piece != BlackCobra
9161 /* For variants we don't have */ && gameInfo.variant != VariantBerolina
9162 /* correct rules for, we cannot */ && gameInfo.variant != VariantCylinder
9163 /* enforce legality on our own! */ && gameInfo.variant != VariantUnknown
9164 && gameInfo.variant != VariantGreat
9165 && gameInfo.variant != VariantFairy ) return;
9166 if(piece < EmptySquare) {
9168 ASSIGN(pieceDesc[piece], buf1);
9169 if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9173 if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9174 promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9178 /* [HGM] Allow engine to set up a position. Don't ask me why one would
9179 * want this, I was asked to put it in, and obliged.
9181 if (!strncmp(message, "setboard ", 9)) {
9182 Board initial_position;
9184 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9186 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9187 DisplayError(_("Bad FEN received from engine"), 0);
9191 CopyBoard(boards[0], initial_position);
9192 initialRulePlies = FENrulePlies;
9193 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9194 else gameMode = MachinePlaysBlack;
9195 DrawPosition(FALSE, boards[currentMove]);
9201 * Look for communication commands
9203 if (!strncmp(message, "telluser ", 9)) {
9204 if(message[9] == '\\' && message[10] == '\\')
9205 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9207 DisplayNote(message + 9);
9210 if (!strncmp(message, "tellusererror ", 14)) {
9212 if(message[14] == '\\' && message[15] == '\\')
9213 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9215 DisplayError(message + 14, 0);
9218 if (!strncmp(message, "tellopponent ", 13)) {
9219 if (appData.icsActive) {
9221 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9225 DisplayNote(message + 13);
9229 if (!strncmp(message, "tellothers ", 11)) {
9230 if (appData.icsActive) {
9232 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9235 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9238 if (!strncmp(message, "tellall ", 8)) {
9239 if (appData.icsActive) {
9241 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9245 DisplayNote(message + 8);
9249 if (strncmp(message, "warning", 7) == 0) {
9250 /* Undocumented feature, use tellusererror in new code */
9251 DisplayError(message, 0);
9254 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9255 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9256 strcat(realname, " query");
9257 AskQuestion(realname, buf2, buf1, cps->pr);
9260 /* Commands from the engine directly to ICS. We don't allow these to be
9261 * sent until we are logged on. Crafty kibitzes have been known to
9262 * interfere with the login process.
9265 if (!strncmp(message, "tellics ", 8)) {
9266 SendToICS(message + 8);
9270 if (!strncmp(message, "tellicsnoalias ", 15)) {
9271 SendToICS(ics_prefix);
9272 SendToICS(message + 15);
9276 /* The following are for backward compatibility only */
9277 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9278 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9279 SendToICS(ics_prefix);
9285 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9286 if(initPing == cps->lastPong) {
9287 if(gameInfo.variant == VariantUnknown) {
9288 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9289 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9290 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9294 if(cps->lastPing == cps->lastPong && abortEngineThink) {
9295 abortEngineThink = FALSE;
9296 DisplayMessage("", "");
9301 if(!strncmp(message, "highlight ", 10)) {
9302 if(appData.testLegality && !*engineVariant && appData.markers) return;
9303 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9306 if(!strncmp(message, "click ", 6)) {
9307 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9308 if(appData.testLegality || !appData.oneClick) return;
9309 sscanf(message+6, "%c%d%c", &f, &y, &c);
9310 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9311 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9312 x = x*squareSize + (x+1)*lineGap + squareSize/2;
9313 y = y*squareSize + (y+1)*lineGap + squareSize/2;
9314 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9315 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9316 LeftClick(Release, lastLeftX, lastLeftY);
9317 controlKey = (c == ',');
9318 LeftClick(Press, x, y);
9319 LeftClick(Release, x, y);
9320 first.highlight = f;
9324 * If the move is illegal, cancel it and redraw the board.
9325 * Also deal with other error cases. Matching is rather loose
9326 * here to accommodate engines written before the spec.
9328 if (strncmp(message + 1, "llegal move", 11) == 0 ||
9329 strncmp(message, "Error", 5) == 0) {
9330 if (StrStr(message, "name") ||
9331 StrStr(message, "rating") || StrStr(message, "?") ||
9332 StrStr(message, "result") || StrStr(message, "board") ||
9333 StrStr(message, "bk") || StrStr(message, "computer") ||
9334 StrStr(message, "variant") || StrStr(message, "hint") ||
9335 StrStr(message, "random") || StrStr(message, "depth") ||
9336 StrStr(message, "accepted")) {
9339 if (StrStr(message, "protover")) {
9340 /* Program is responding to input, so it's apparently done
9341 initializing, and this error message indicates it is
9342 protocol version 1. So we don't need to wait any longer
9343 for it to initialize and send feature commands. */
9344 FeatureDone(cps, 1);
9345 cps->protocolVersion = 1;
9348 cps->maybeThinking = FALSE;
9350 if (StrStr(message, "draw")) {
9351 /* Program doesn't have "draw" command */
9352 cps->sendDrawOffers = 0;
9355 if (cps->sendTime != 1 &&
9356 (StrStr(message, "time") || StrStr(message, "otim"))) {
9357 /* Program apparently doesn't have "time" or "otim" command */
9361 if (StrStr(message, "analyze")) {
9362 cps->analysisSupport = FALSE;
9363 cps->analyzing = FALSE;
9364 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9365 EditGameEvent(); // [HGM] try to preserve loaded game
9366 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9367 DisplayError(buf2, 0);
9370 if (StrStr(message, "(no matching move)st")) {
9371 /* Special kludge for GNU Chess 4 only */
9372 cps->stKludge = TRUE;
9373 SendTimeControl(cps, movesPerSession, timeControl,
9374 timeIncrement, appData.searchDepth,
9378 if (StrStr(message, "(no matching move)sd")) {
9379 /* Special kludge for GNU Chess 4 only */
9380 cps->sdKludge = TRUE;
9381 SendTimeControl(cps, movesPerSession, timeControl,
9382 timeIncrement, appData.searchDepth,
9386 if (!StrStr(message, "llegal")) {
9389 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9390 gameMode == IcsIdle) return;
9391 if (forwardMostMove <= backwardMostMove) return;
9392 if (pausing) PauseEvent();
9393 if(appData.forceIllegal) {
9394 // [HGM] illegal: machine refused move; force position after move into it
9395 SendToProgram("force\n", cps);
9396 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9397 // we have a real problem now, as SendBoard will use the a2a3 kludge
9398 // when black is to move, while there might be nothing on a2 or black
9399 // might already have the move. So send the board as if white has the move.
9400 // But first we must change the stm of the engine, as it refused the last move
9401 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9402 if(WhiteOnMove(forwardMostMove)) {
9403 SendToProgram("a7a6\n", cps); // for the engine black still had the move
9404 SendBoard(cps, forwardMostMove); // kludgeless board
9406 SendToProgram("a2a3\n", cps); // for the engine white still had the move
9407 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9408 SendBoard(cps, forwardMostMove+1); // kludgeless board
9410 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9411 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9412 gameMode == TwoMachinesPlay)
9413 SendToProgram("go\n", cps);
9416 if (gameMode == PlayFromGameFile) {
9417 /* Stop reading this game file */
9418 gameMode = EditGame;
9421 /* [HGM] illegal-move claim should forfeit game when Xboard */
9422 /* only passes fully legal moves */
9423 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9424 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9425 "False illegal-move claim", GE_XBOARD );
9426 return; // do not take back move we tested as valid
9428 currentMove = forwardMostMove-1;
9429 DisplayMove(currentMove-1); /* before DisplayMoveError */
9430 SwitchClocks(forwardMostMove-1); // [HGM] race
9431 DisplayBothClocks();
9432 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9433 parseList[currentMove], _(cps->which));
9434 DisplayMoveError(buf1);
9435 DrawPosition(FALSE, boards[currentMove]);
9437 SetUserThinkingEnables();
9440 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9441 /* Program has a broken "time" command that
9442 outputs a string not ending in newline.
9446 if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9447 if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9448 sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1 ) return;
9452 * If chess program startup fails, exit with an error message.
9453 * Attempts to recover here are futile. [HGM] Well, we try anyway
9455 if ((StrStr(message, "unknown host") != NULL)
9456 || (StrStr(message, "No remote directory") != NULL)
9457 || (StrStr(message, "not found") != NULL)
9458 || (StrStr(message, "No such file") != NULL)
9459 || (StrStr(message, "can't alloc") != NULL)
9460 || (StrStr(message, "Permission denied") != NULL)) {
9462 cps->maybeThinking = FALSE;
9463 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9464 _(cps->which), cps->program, cps->host, message);
9465 RemoveInputSource(cps->isr);
9466 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9467 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9468 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9474 * Look for hint output
9476 if (sscanf(message, "Hint: %s", buf1) == 1) {
9477 if (cps == &first && hintRequested) {
9478 hintRequested = FALSE;
9479 if (ParseOneMove(buf1, forwardMostMove, &moveType,
9480 &fromX, &fromY, &toX, &toY, &promoChar)) {
9481 (void) CoordsToAlgebraic(boards[forwardMostMove],
9482 PosFlags(forwardMostMove),
9483 fromY, fromX, toY, toX, promoChar, buf1);
9484 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9485 DisplayInformation(buf2);
9487 /* Hint move could not be parsed!? */
9488 snprintf(buf2, sizeof(buf2),
9489 _("Illegal hint move \"%s\"\nfrom %s chess program"),
9490 buf1, _(cps->which));
9491 DisplayError(buf2, 0);
9494 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9500 * Ignore other messages if game is not in progress
9502 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9503 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9506 * look for win, lose, draw, or draw offer
9508 if (strncmp(message, "1-0", 3) == 0) {
9509 char *p, *q, *r = "";
9510 p = strchr(message, '{');
9518 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9520 } else if (strncmp(message, "0-1", 3) == 0) {
9521 char *p, *q, *r = "";
9522 p = strchr(message, '{');
9530 /* Kludge for Arasan 4.1 bug */
9531 if (strcmp(r, "Black resigns") == 0) {
9532 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9535 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9537 } else if (strncmp(message, "1/2", 3) == 0) {
9538 char *p, *q, *r = "";
9539 p = strchr(message, '{');
9548 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9551 } else if (strncmp(message, "White resign", 12) == 0) {
9552 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9554 } else if (strncmp(message, "Black resign", 12) == 0) {
9555 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9557 } else if (strncmp(message, "White matches", 13) == 0 ||
9558 strncmp(message, "Black matches", 13) == 0 ) {
9559 /* [HGM] ignore GNUShogi noises */
9561 } else if (strncmp(message, "White", 5) == 0 &&
9562 message[5] != '(' &&
9563 StrStr(message, "Black") == NULL) {
9564 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9566 } else if (strncmp(message, "Black", 5) == 0 &&
9567 message[5] != '(') {
9568 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9570 } else if (strcmp(message, "resign") == 0 ||
9571 strcmp(message, "computer resigns") == 0) {
9573 case MachinePlaysBlack:
9574 case IcsPlayingBlack:
9575 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9577 case MachinePlaysWhite:
9578 case IcsPlayingWhite:
9579 GameEnds(BlackWins, "White resigns", GE_ENGINE);
9581 case TwoMachinesPlay:
9582 if (cps->twoMachinesColor[0] == 'w')
9583 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9585 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9592 } else if (strncmp(message, "opponent mates", 14) == 0) {
9594 case MachinePlaysBlack:
9595 case IcsPlayingBlack:
9596 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9598 case MachinePlaysWhite:
9599 case IcsPlayingWhite:
9600 GameEnds(BlackWins, "Black mates", GE_ENGINE);
9602 case TwoMachinesPlay:
9603 if (cps->twoMachinesColor[0] == 'w')
9604 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9606 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9613 } else if (strncmp(message, "computer mates", 14) == 0) {
9615 case MachinePlaysBlack:
9616 case IcsPlayingBlack:
9617 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9619 case MachinePlaysWhite:
9620 case IcsPlayingWhite:
9621 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9623 case TwoMachinesPlay:
9624 if (cps->twoMachinesColor[0] == 'w')
9625 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9627 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9634 } else if (strncmp(message, "checkmate", 9) == 0) {
9635 if (WhiteOnMove(forwardMostMove)) {
9636 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9638 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9641 } else if (strstr(message, "Draw") != NULL ||
9642 strstr(message, "game is a draw") != NULL) {
9643 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9645 } else if (strstr(message, "offer") != NULL &&
9646 strstr(message, "draw") != NULL) {
9648 if (appData.zippyPlay && first.initDone) {
9649 /* Relay offer to ICS */
9650 SendToICS(ics_prefix);
9651 SendToICS("draw\n");
9654 cps->offeredDraw = 2; /* valid until this engine moves twice */
9655 if (gameMode == TwoMachinesPlay) {
9656 if (cps->other->offeredDraw) {
9657 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9658 /* [HGM] in two-machine mode we delay relaying draw offer */
9659 /* until after we also have move, to see if it is really claim */
9661 } else if (gameMode == MachinePlaysWhite ||
9662 gameMode == MachinePlaysBlack) {
9663 if (userOfferedDraw) {
9664 DisplayInformation(_("Machine accepts your draw offer"));
9665 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9667 DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9674 * Look for thinking output
9676 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9677 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9679 int plylev, mvleft, mvtot, curscore, time;
9680 char mvname[MOVE_LEN];
9684 int prefixHint = FALSE;
9685 mvname[0] = NULLCHAR;
9688 case MachinePlaysBlack:
9689 case IcsPlayingBlack:
9690 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9692 case MachinePlaysWhite:
9693 case IcsPlayingWhite:
9694 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9699 case IcsObserving: /* [DM] icsEngineAnalyze */
9700 if (!appData.icsEngineAnalyze) ignore = TRUE;
9702 case TwoMachinesPlay:
9703 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9713 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9716 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9717 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9718 char score_buf[MSG_SIZ];
9720 if(nodes>>32 == u64Const(0xFFFFFFFF)) // [HGM] negative node count read
9721 nodes += u64Const(0x100000000);
9723 if (plyext != ' ' && plyext != '\t') {
9727 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9728 if( cps->scoreIsAbsolute &&
9729 ( gameMode == MachinePlaysBlack ||
9730 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9731 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9732 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9733 !WhiteOnMove(currentMove)
9736 curscore = -curscore;
9739 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9741 if(*bestMove) { // rememer time best EPD move was first found
9742 int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9743 ChessMove mt; char *p = bestMove;
9744 int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9746 while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9747 if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9748 solvingTime = (solvingTime < 0 ? time : solvingTime);
9752 while(*p && *p != ' ') p++;
9753 while(*p == ' ') p++;
9755 if(!solved) solvingTime = -1;
9757 if(*avoidMove && !solved) {
9758 int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9759 ChessMove mt; char *p = avoidMove, solved = 1;
9760 int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9761 while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9762 if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9763 solved = 0; solvingTime = -2;
9766 while(*p && *p != ' ') p++;
9767 while(*p == ' ') p++;
9769 if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9772 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9775 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9776 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9777 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9778 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9779 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9780 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9784 /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9785 DisplayError(_("failed writing PV"), 0);
9788 tempStats.depth = plylev;
9789 tempStats.nodes = nodes;
9790 tempStats.time = time;
9791 tempStats.score = curscore;
9792 tempStats.got_only_move = 0;
9794 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9797 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9798 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9799 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9800 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9801 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9802 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9803 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9804 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9807 /* Buffer overflow protection */
9808 if (pv[0] != NULLCHAR) {
9809 if (strlen(pv) >= sizeof(tempStats.movelist)
9810 && appData.debugMode) {
9812 "PV is too long; using the first %u bytes.\n",
9813 (unsigned) sizeof(tempStats.movelist) - 1);
9816 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9818 sprintf(tempStats.movelist, " no PV\n");
9821 if (tempStats.seen_stat) {
9822 tempStats.ok_to_send = 1;
9825 if (strchr(tempStats.movelist, '(') != NULL) {
9826 tempStats.line_is_book = 1;
9827 tempStats.nr_moves = 0;
9828 tempStats.moves_left = 0;
9830 tempStats.line_is_book = 0;
9833 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9834 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9836 SendProgramStatsToFrontend( cps, &tempStats );
9839 [AS] Protect the thinkOutput buffer from overflow... this
9840 is only useful if buf1 hasn't overflowed first!
9842 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9843 if(curscore >= MATE_SCORE)
9844 snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9845 else if(curscore <= -MATE_SCORE)
9846 snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9848 snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9849 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9851 (gameMode == TwoMachinesPlay ?
9852 ToUpper(cps->twoMachinesColor[0]) : ' '),
9854 prefixHint ? lastHint : "",
9855 prefixHint ? " " : "" );
9857 if( buf1[0] != NULLCHAR ) {
9858 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9860 if( strlen(pv) > max_len ) {
9861 if( appData.debugMode) {
9862 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9864 pv[max_len+1] = '\0';
9867 strcat( thinkOutput, pv);
9870 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9871 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9872 DisplayMove(currentMove - 1);
9876 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9877 /* crafty (9.25+) says "(only move) <move>"
9878 * if there is only 1 legal move
9880 sscanf(p, "(only move) %s", buf1);
9881 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9882 sprintf(programStats.movelist, "%s (only move)", buf1);
9883 programStats.depth = 1;
9884 programStats.nr_moves = 1;
9885 programStats.moves_left = 1;
9886 programStats.nodes = 1;
9887 programStats.time = 1;
9888 programStats.got_only_move = 1;
9890 /* Not really, but we also use this member to
9891 mean "line isn't going to change" (Crafty
9892 isn't searching, so stats won't change) */
9893 programStats.line_is_book = 1;
9895 SendProgramStatsToFrontend( cps, &programStats );
9897 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9898 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9899 DisplayMove(currentMove - 1);
9902 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9903 &time, &nodes, &plylev, &mvleft,
9904 &mvtot, mvname) >= 5) {
9905 /* The stat01: line is from Crafty (9.29+) in response
9906 to the "." command */
9907 programStats.seen_stat = 1;
9908 cps->maybeThinking = TRUE;
9910 if (programStats.got_only_move || !appData.periodicUpdates)
9913 programStats.depth = plylev;
9914 programStats.time = time;
9915 programStats.nodes = nodes;
9916 programStats.moves_left = mvleft;
9917 programStats.nr_moves = mvtot;
9918 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9919 programStats.ok_to_send = 1;
9920 programStats.movelist[0] = '\0';
9922 SendProgramStatsToFrontend( cps, &programStats );
9926 } else if (strncmp(message,"++",2) == 0) {
9927 /* Crafty 9.29+ outputs this */
9928 programStats.got_fail = 2;
9931 } else if (strncmp(message,"--",2) == 0) {
9932 /* Crafty 9.29+ outputs this */
9933 programStats.got_fail = 1;
9936 } else if (thinkOutput[0] != NULLCHAR &&
9937 strncmp(message, " ", 4) == 0) {
9938 unsigned message_len;
9941 while (*p && *p == ' ') p++;
9943 message_len = strlen( p );
9945 /* [AS] Avoid buffer overflow */
9946 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9947 strcat(thinkOutput, " ");
9948 strcat(thinkOutput, p);
9951 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9952 strcat(programStats.movelist, " ");
9953 strcat(programStats.movelist, p);
9956 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9957 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9958 DisplayMove(currentMove - 1);
9966 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9967 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9969 ChessProgramStats cpstats;
9971 if (plyext != ' ' && plyext != '\t') {
9975 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9976 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9977 curscore = -curscore;
9980 cpstats.depth = plylev;
9981 cpstats.nodes = nodes;
9982 cpstats.time = time;
9983 cpstats.score = curscore;
9984 cpstats.got_only_move = 0;
9985 cpstats.movelist[0] = '\0';
9987 if (buf1[0] != NULLCHAR) {
9988 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9991 cpstats.ok_to_send = 0;
9992 cpstats.line_is_book = 0;
9993 cpstats.nr_moves = 0;
9994 cpstats.moves_left = 0;
9996 SendProgramStatsToFrontend( cps, &cpstats );
10003 /* Parse a game score from the character string "game", and
10004 record it as the history of the current game. The game
10005 score is NOT assumed to start from the standard position.
10006 The display is not updated in any way.
10009 ParseGameHistory (char *game)
10011 ChessMove moveType;
10012 int fromX, fromY, toX, toY, boardIndex;
10017 if (appData.debugMode)
10018 fprintf(debugFP, "Parsing game history: %s\n", game);
10020 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10021 gameInfo.site = StrSave(appData.icsHost);
10022 gameInfo.date = PGNDate();
10023 gameInfo.round = StrSave("-");
10025 /* Parse out names of players */
10026 while (*game == ' ') game++;
10028 while (*game != ' ') *p++ = *game++;
10030 gameInfo.white = StrSave(buf);
10031 while (*game == ' ') game++;
10033 while (*game != ' ' && *game != '\n') *p++ = *game++;
10035 gameInfo.black = StrSave(buf);
10038 boardIndex = blackPlaysFirst ? 1 : 0;
10041 yyboardindex = boardIndex;
10042 moveType = (ChessMove) Myylex();
10043 switch (moveType) {
10044 case IllegalMove: /* maybe suicide chess, etc. */
10045 if (appData.debugMode) {
10046 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10047 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10048 setbuf(debugFP, NULL);
10050 case WhitePromotion:
10051 case BlackPromotion:
10052 case WhiteNonPromotion:
10053 case BlackNonPromotion:
10056 case WhiteCapturesEnPassant:
10057 case BlackCapturesEnPassant:
10058 case WhiteKingSideCastle:
10059 case WhiteQueenSideCastle:
10060 case BlackKingSideCastle:
10061 case BlackQueenSideCastle:
10062 case WhiteKingSideCastleWild:
10063 case WhiteQueenSideCastleWild:
10064 case BlackKingSideCastleWild:
10065 case BlackQueenSideCastleWild:
10067 case WhiteHSideCastleFR:
10068 case WhiteASideCastleFR:
10069 case BlackHSideCastleFR:
10070 case BlackASideCastleFR:
10072 fromX = currentMoveString[0] - AAA;
10073 fromY = currentMoveString[1] - ONE;
10074 toX = currentMoveString[2] - AAA;
10075 toY = currentMoveString[3] - ONE;
10076 promoChar = currentMoveString[4];
10080 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10081 fromX = moveType == WhiteDrop ?
10082 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10083 (int) CharToPiece(ToLower(currentMoveString[0]));
10085 toX = currentMoveString[2] - AAA;
10086 toY = currentMoveString[3] - ONE;
10087 promoChar = NULLCHAR;
10089 case AmbiguousMove:
10091 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10092 if (appData.debugMode) {
10093 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10094 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10095 setbuf(debugFP, NULL);
10097 DisplayError(buf, 0);
10099 case ImpossibleMove:
10101 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10102 if (appData.debugMode) {
10103 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10104 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10105 setbuf(debugFP, NULL);
10107 DisplayError(buf, 0);
10110 if (boardIndex < backwardMostMove) {
10111 /* Oops, gap. How did that happen? */
10112 DisplayError(_("Gap in move list"), 0);
10115 backwardMostMove = blackPlaysFirst ? 1 : 0;
10116 if (boardIndex > forwardMostMove) {
10117 forwardMostMove = boardIndex;
10121 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10122 strcat(parseList[boardIndex-1], " ");
10123 strcat(parseList[boardIndex-1], yy_text);
10135 case GameUnfinished:
10136 if (gameMode == IcsExamining) {
10137 if (boardIndex < backwardMostMove) {
10138 /* Oops, gap. How did that happen? */
10141 backwardMostMove = blackPlaysFirst ? 1 : 0;
10144 gameInfo.result = moveType;
10145 p = strchr(yy_text, '{');
10146 if (p == NULL) p = strchr(yy_text, '(');
10149 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10151 q = strchr(p, *p == '{' ? '}' : ')');
10152 if (q != NULL) *q = NULLCHAR;
10155 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10156 gameInfo.resultDetails = StrSave(p);
10159 if (boardIndex >= forwardMostMove &&
10160 !(gameMode == IcsObserving && ics_gamenum == -1)) {
10161 backwardMostMove = blackPlaysFirst ? 1 : 0;
10164 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10165 fromY, fromX, toY, toX, promoChar,
10166 parseList[boardIndex]);
10167 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10168 /* currentMoveString is set as a side-effect of yylex */
10169 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10170 strcat(moveList[boardIndex], "\n");
10172 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10173 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10179 if(!IS_SHOGI(gameInfo.variant))
10180 strcat(parseList[boardIndex - 1], "+");
10184 strcat(parseList[boardIndex - 1], "#");
10191 /* Apply a move to the given board */
10193 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10195 ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10196 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10198 /* [HGM] compute & store e.p. status and castling rights for new position */
10199 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10201 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10202 oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10203 board[EP_STATUS] = EP_NONE;
10204 board[EP_FILE] = board[EP_RANK] = 100;
10206 if (fromY == DROP_RANK) {
10207 /* must be first */
10208 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10209 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10212 piece = board[toY][toX] = (ChessSquare) fromX;
10214 // ChessSquare victim;
10217 if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10218 // victim = board[killY][killX],
10219 killed = board[killY][killX],
10220 board[killY][killX] = EmptySquare,
10221 board[EP_STATUS] = EP_CAPTURE;
10222 if( kill2X >= 0 && kill2Y >= 0)
10223 killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10226 if( board[toY][toX] != EmptySquare ) {
10227 board[EP_STATUS] = EP_CAPTURE;
10228 if( (fromX != toX || fromY != toY) && // not igui!
10229 (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10230 captured == BlackLion && board[fromY][fromX] != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules
10231 board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10235 pawn = board[fromY][fromX];
10236 if( pawn == WhiteLance || pawn == BlackLance ) {
10237 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10238 if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10239 else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10242 if( pawn == WhitePawn ) {
10243 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10244 board[EP_STATUS] = EP_PAWN_MOVE;
10245 if( toY-fromY>=2) {
10246 board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10247 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
10248 gameInfo.variant != VariantBerolina || toX < fromX)
10249 board[EP_STATUS] = toX | berolina;
10250 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10251 gameInfo.variant != VariantBerolina || toX > fromX)
10252 board[EP_STATUS] = toX;
10255 if( pawn == BlackPawn ) {
10256 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10257 board[EP_STATUS] = EP_PAWN_MOVE;
10258 if( toY-fromY<= -2) {
10259 board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10260 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
10261 gameInfo.variant != VariantBerolina || toX < fromX)
10262 board[EP_STATUS] = toX | berolina;
10263 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10264 gameInfo.variant != VariantBerolina || toX > fromX)
10265 board[EP_STATUS] = toX;
10269 if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10270 if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10271 if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10272 if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10274 for(i=0; i<nrCastlingRights; i++) {
10275 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10276 board[CASTLING][i] == toX && castlingRank[i] == toY
10277 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10280 if(gameInfo.variant == VariantSChess) { // update virginity
10281 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10282 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10283 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
10284 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
10287 if (fromX == toX && fromY == toY && killX < 0) return;
10289 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10290 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10291 if(gameInfo.variant == VariantKnightmate)
10292 king += (int) WhiteUnicorn - (int) WhiteKing;
10294 if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10295 && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) { // and tramples own
10296 board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10297 board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10298 board[EP_STATUS] = EP_NONE; // capture was fake!
10300 if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10301 board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10302 board[toY][toX] = piece;
10303 board[EP_STATUS] = EP_NONE; // capture was fake!
10305 /* Code added by Tord: */
10306 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10307 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10308 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10309 board[EP_STATUS] = EP_NONE; // capture was fake!
10310 board[fromY][fromX] = EmptySquare;
10311 board[toY][toX] = EmptySquare;
10312 if((toX > fromX) != (piece == WhiteRook)) {
10313 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10315 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10317 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10318 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10319 board[EP_STATUS] = EP_NONE;
10320 board[fromY][fromX] = EmptySquare;
10321 board[toY][toX] = EmptySquare;
10322 if((toX > fromX) != (piece == BlackRook)) {
10323 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10325 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10327 /* End of code added by Tord */
10329 } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10330 board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10331 board[toY][toX] = piece;
10332 } else if (board[fromY][fromX] == king
10333 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10334 && toY == fromY && toX > fromX+1) {
10335 for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10336 ; // castle with nearest piece
10337 board[fromY][toX-1] = board[fromY][rookX];
10338 board[fromY][rookX] = EmptySquare;
10339 board[fromY][fromX] = EmptySquare;
10340 board[toY][toX] = king;
10341 } else if (board[fromY][fromX] == king
10342 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10343 && toY == fromY && toX < fromX-1) {
10344 for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10345 ; // castle with nearest piece
10346 board[fromY][toX+1] = board[fromY][rookX];
10347 board[fromY][rookX] = EmptySquare;
10348 board[fromY][fromX] = EmptySquare;
10349 board[toY][toX] = king;
10350 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10351 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10352 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10354 /* white pawn promotion */
10355 board[toY][toX] = CharToPiece(ToUpper(promoChar));
10356 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10357 board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10358 board[fromY][fromX] = EmptySquare;
10359 } else if ((fromY >= BOARD_HEIGHT>>1)
10360 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10362 && gameInfo.variant != VariantXiangqi
10363 && gameInfo.variant != VariantBerolina
10364 && (pawn == WhitePawn)
10365 && (board[toY][toX] == EmptySquare)) {
10366 board[fromY][fromX] = EmptySquare;
10367 board[toY][toX] = piece;
10368 if(toY == epRank - 128 + 1)
10369 captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10371 captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10372 } else if ((fromY == BOARD_HEIGHT-4)
10374 && gameInfo.variant == VariantBerolina
10375 && (board[fromY][fromX] == WhitePawn)
10376 && (board[toY][toX] == EmptySquare)) {
10377 board[fromY][fromX] = EmptySquare;
10378 board[toY][toX] = WhitePawn;
10379 if(oldEP & EP_BEROLIN_A) {
10380 captured = board[fromY][fromX-1];
10381 board[fromY][fromX-1] = EmptySquare;
10382 }else{ captured = board[fromY][fromX+1];
10383 board[fromY][fromX+1] = EmptySquare;
10385 } else if (board[fromY][fromX] == king
10386 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10387 && toY == fromY && toX > fromX+1) {
10388 for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10390 board[fromY][toX-1] = board[fromY][rookX];
10391 board[fromY][rookX] = EmptySquare;
10392 board[fromY][fromX] = EmptySquare;
10393 board[toY][toX] = king;
10394 } else if (board[fromY][fromX] == king
10395 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10396 && toY == fromY && toX < fromX-1) {
10397 for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10399 board[fromY][toX+1] = board[fromY][rookX];
10400 board[fromY][rookX] = EmptySquare;
10401 board[fromY][fromX] = EmptySquare;
10402 board[toY][toX] = king;
10403 } else if (fromY == 7 && fromX == 3
10404 && board[fromY][fromX] == BlackKing
10405 && toY == 7 && toX == 5) {
10406 board[fromY][fromX] = EmptySquare;
10407 board[toY][toX] = BlackKing;
10408 board[fromY][7] = EmptySquare;
10409 board[toY][4] = BlackRook;
10410 } else if (fromY == 7 && fromX == 3
10411 && board[fromY][fromX] == BlackKing
10412 && toY == 7 && toX == 1) {
10413 board[fromY][fromX] = EmptySquare;
10414 board[toY][toX] = BlackKing;
10415 board[fromY][0] = EmptySquare;
10416 board[toY][2] = BlackRook;
10417 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10418 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10419 && toY < promoRank && promoChar
10421 /* black pawn promotion */
10422 board[toY][toX] = CharToPiece(ToLower(promoChar));
10423 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10424 board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10425 board[fromY][fromX] = EmptySquare;
10426 } else if ((fromY < BOARD_HEIGHT>>1)
10427 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10429 && gameInfo.variant != VariantXiangqi
10430 && gameInfo.variant != VariantBerolina
10431 && (pawn == BlackPawn)
10432 && (board[toY][toX] == EmptySquare)) {
10433 board[fromY][fromX] = EmptySquare;
10434 board[toY][toX] = piece;
10435 if(toY == epRank - 128 - 1)
10436 captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10438 captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10439 } else if ((fromY == 3)
10441 && gameInfo.variant == VariantBerolina
10442 && (board[fromY][fromX] == BlackPawn)
10443 && (board[toY][toX] == EmptySquare)) {
10444 board[fromY][fromX] = EmptySquare;
10445 board[toY][toX] = BlackPawn;
10446 if(oldEP & EP_BEROLIN_A) {
10447 captured = board[fromY][fromX-1];
10448 board[fromY][fromX-1] = EmptySquare;
10449 }else{ captured = board[fromY][fromX+1];
10450 board[fromY][fromX+1] = EmptySquare;
10453 ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10454 board[fromY][fromX] = EmptySquare;
10455 board[toY][toX] = piece;
10459 if (gameInfo.holdingsWidth != 0) {
10461 /* !!A lot more code needs to be written to support holdings */
10462 /* [HGM] OK, so I have written it. Holdings are stored in the */
10463 /* penultimate board files, so they are automaticlly stored */
10464 /* in the game history. */
10465 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10466 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10467 /* Delete from holdings, by decreasing count */
10468 /* and erasing image if necessary */
10469 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10470 if(p < (int) BlackPawn) { /* white drop */
10471 p -= (int)WhitePawn;
10472 p = PieceToNumber((ChessSquare)p);
10473 if(p >= gameInfo.holdingsSize) p = 0;
10474 if(--board[p][BOARD_WIDTH-2] <= 0)
10475 board[p][BOARD_WIDTH-1] = EmptySquare;
10476 if((int)board[p][BOARD_WIDTH-2] < 0)
10477 board[p][BOARD_WIDTH-2] = 0;
10478 } else { /* black drop */
10479 p -= (int)BlackPawn;
10480 p = PieceToNumber((ChessSquare)p);
10481 if(p >= gameInfo.holdingsSize) p = 0;
10482 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10483 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10484 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10485 board[BOARD_HEIGHT-1-p][1] = 0;
10488 if (captured != EmptySquare && gameInfo.holdingsSize > 0
10489 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
10490 /* [HGM] holdings: Add to holdings, if holdings exist */
10491 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10492 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10493 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10495 p = (int) captured;
10496 if (p >= (int) BlackPawn) {
10497 p -= (int)BlackPawn;
10498 if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10499 /* Restore shogi-promoted piece to its original first */
10500 captured = (ChessSquare) (DEMOTED(captured));
10503 p = PieceToNumber((ChessSquare)p);
10504 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10505 board[p][BOARD_WIDTH-2]++;
10506 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10508 p -= (int)WhitePawn;
10509 if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10510 captured = (ChessSquare) (DEMOTED(captured));
10513 p = PieceToNumber((ChessSquare)p);
10514 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10515 board[BOARD_HEIGHT-1-p][1]++;
10516 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10519 } else if (gameInfo.variant == VariantAtomic) {
10520 if (captured != EmptySquare) {
10522 for (y = toY-1; y <= toY+1; y++) {
10523 for (x = toX-1; x <= toX+1; x++) {
10524 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10525 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10526 board[y][x] = EmptySquare;
10530 board[toY][toX] = EmptySquare;
10534 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10535 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10537 if(promoChar == '+') {
10538 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10539 board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10540 if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10541 board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10542 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10543 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10544 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10545 && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10546 board[toY][toX] = newPiece;
10548 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10549 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10550 // [HGM] superchess: take promotion piece out of holdings
10551 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10552 if((int)piece < (int)BlackPawn) { // determine stm from piece color
10553 if(!--board[k][BOARD_WIDTH-2])
10554 board[k][BOARD_WIDTH-1] = EmptySquare;
10556 if(!--board[BOARD_HEIGHT-1-k][1])
10557 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10562 /* Updates forwardMostMove */
10564 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10566 int x = toX, y = toY;
10567 char *s = parseList[forwardMostMove];
10568 ChessSquare p = boards[forwardMostMove][toY][toX];
10569 // forwardMostMove++; // [HGM] bare: moved downstream
10571 if(kill2X >= 0) x = kill2X, y = kill2Y; else
10572 if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10573 (void) CoordsToAlgebraic(boards[forwardMostMove],
10574 PosFlags(forwardMostMove),
10575 fromY, fromX, y, x, (killX < 0)*promoChar,
10577 if(kill2X >= 0 && kill2Y >= 0)
10578 sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10579 if(killX >= 0 && killY >= 0)
10580 sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10581 toX + AAA, toY + ONE - '0', promoChar);
10583 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10584 int timeLeft; static int lastLoadFlag=0; int king, piece;
10585 piece = boards[forwardMostMove][fromY][fromX];
10586 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10587 if(gameInfo.variant == VariantKnightmate)
10588 king += (int) WhiteUnicorn - (int) WhiteKing;
10589 if(forwardMostMove == 0) {
10590 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10591 fprintf(serverMoves, "%s;", UserName());
10592 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10593 fprintf(serverMoves, "%s;", second.tidy);
10594 fprintf(serverMoves, "%s;", first.tidy);
10595 if(gameMode == MachinePlaysWhite)
10596 fprintf(serverMoves, "%s;", UserName());
10597 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10598 fprintf(serverMoves, "%s;", second.tidy);
10599 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10600 lastLoadFlag = loadFlag;
10602 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10603 // print castling suffix
10604 if( toY == fromY && piece == king ) {
10606 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10608 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10611 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10612 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
10613 boards[forwardMostMove][toY][toX] == EmptySquare
10614 && fromX != toX && fromY != toY)
10615 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10616 // promotion suffix
10617 if(promoChar != NULLCHAR) {
10618 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10619 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10620 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10621 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10624 char buf[MOVE_LEN*2], *p; int len;
10625 fprintf(serverMoves, "/%d/%d",
10626 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10627 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10628 else timeLeft = blackTimeRemaining/1000;
10629 fprintf(serverMoves, "/%d", timeLeft);
10630 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10631 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10632 if(p = strchr(buf, '=')) *p = NULLCHAR;
10633 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10634 fprintf(serverMoves, "/%s", buf);
10636 fflush(serverMoves);
10639 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10640 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10643 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10644 if (commentList[forwardMostMove+1] != NULL) {
10645 free(commentList[forwardMostMove+1]);
10646 commentList[forwardMostMove+1] = NULL;
10648 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10649 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10650 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10651 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10652 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10653 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10654 adjustedClock = FALSE;
10655 gameInfo.result = GameUnfinished;
10656 if (gameInfo.resultDetails != NULL) {
10657 free(gameInfo.resultDetails);
10658 gameInfo.resultDetails = NULL;
10660 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10661 moveList[forwardMostMove - 1]);
10662 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10668 if(!IS_SHOGI(gameInfo.variant))
10669 strcat(parseList[forwardMostMove - 1], "+");
10673 strcat(parseList[forwardMostMove - 1], "#");
10678 /* Updates currentMove if not pausing */
10680 ShowMove (int fromX, int fromY, int toX, int toY)
10682 int instant = (gameMode == PlayFromGameFile) ?
10683 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10684 if(appData.noGUI) return;
10685 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10687 if (forwardMostMove == currentMove + 1) {
10688 AnimateMove(boards[forwardMostMove - 1],
10689 fromX, fromY, toX, toY);
10692 currentMove = forwardMostMove;
10695 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10697 if (instant) return;
10699 DisplayMove(currentMove - 1);
10700 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10701 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10702 SetHighlights(fromX, fromY, toX, toY);
10705 DrawPosition(FALSE, boards[currentMove]);
10706 DisplayBothClocks();
10707 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10711 SendEgtPath (ChessProgramState *cps)
10712 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10713 char buf[MSG_SIZ], name[MSG_SIZ], *p;
10715 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10718 char c, *q = name+1, *r, *s;
10720 name[0] = ','; // extract next format name from feature and copy with prefixed ','
10721 while(*p && *p != ',') *q++ = *p++;
10722 *q++ = ':'; *q = 0;
10723 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10724 strcmp(name, ",nalimov:") == 0 ) {
10725 // take nalimov path from the menu-changeable option first, if it is defined
10726 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10727 SendToProgram(buf,cps); // send egtbpath command for nalimov
10729 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10730 (s = StrStr(appData.egtFormats, name)) != NULL) {
10731 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10732 s = r = StrStr(s, ":") + 1; // beginning of path info
10733 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10734 c = *r; *r = 0; // temporarily null-terminate path info
10735 *--q = 0; // strip of trailig ':' from name
10736 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10738 SendToProgram(buf,cps); // send egtbpath command for this format
10740 if(*p == ',') p++; // read away comma to position for next format name
10745 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10747 int width = 8, height = 8, holdings = 0; // most common sizes
10748 if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10749 // correct the deviations default for each variant
10750 if( v == VariantXiangqi ) width = 9, height = 10;
10751 if( v == VariantShogi ) width = 9, height = 9, holdings = 7;
10752 if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10753 if( v == VariantCapablanca || v == VariantCapaRandom ||
10754 v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10756 if( v == VariantCourier ) width = 12;
10757 if( v == VariantSuper ) holdings = 8;
10758 if( v == VariantGreat ) width = 10, holdings = 8;
10759 if( v == VariantSChess ) holdings = 7;
10760 if( v == VariantGrand ) width = 10, height = 10, holdings = 7;
10761 if( v == VariantChuChess) width = 10, height = 10;
10762 if( v == VariantChu ) width = 12, height = 12;
10763 return boardWidth >= 0 && boardWidth != width || // -1 is default,
10764 boardHeight >= 0 && boardHeight != height || // and thus by definition OK
10765 holdingsSize >= 0 && holdingsSize != holdings;
10768 char variantError[MSG_SIZ];
10771 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10772 { // returns error message (recognizable by upper-case) if engine does not support the variant
10773 char *p, *variant = VariantName(v);
10774 static char b[MSG_SIZ];
10775 if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10776 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10777 holdingsSize, variant); // cook up sized variant name
10778 /* [HGM] varsize: try first if this deviant size variant is specifically known */
10779 if(StrStr(list, b) == NULL) {
10780 // specific sized variant not known, check if general sizing allowed
10781 if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10782 snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10783 boardWidth, boardHeight, holdingsSize, engine);
10786 /* [HGM] here we really should compare with the maximum supported board size */
10788 } else snprintf(b, MSG_SIZ,"%s", variant);
10789 if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10790 p = StrStr(list, b);
10791 while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10793 // occurs not at all in list, or only as sub-string
10794 snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10795 if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10796 int l = strlen(variantError);
10798 while(p != list && p[-1] != ',') p--;
10799 q = strchr(p, ',');
10800 if(q) *q = NULLCHAR;
10801 snprintf(variantError + l, MSG_SIZ - l, _(", but %s is"), p);
10810 InitChessProgram (ChessProgramState *cps, int setup)
10811 /* setup needed to setup FRC opening position */
10813 char buf[MSG_SIZ], *b;
10814 if (appData.noChessProgram) return;
10815 hintRequested = FALSE;
10816 bookRequested = FALSE;
10818 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10819 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10820 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10821 if(cps->memSize) { /* [HGM] memory */
10822 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10823 SendToProgram(buf, cps);
10825 SendEgtPath(cps); /* [HGM] EGT */
10826 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10827 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10828 SendToProgram(buf, cps);
10831 setboardSpoiledMachineBlack = FALSE;
10832 SendToProgram(cps->initString, cps);
10833 if (gameInfo.variant != VariantNormal &&
10834 gameInfo.variant != VariantLoadable
10835 /* [HGM] also send variant if board size non-standard */
10836 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10838 b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10839 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10843 char c, *q = cps->variants, *p = strchr(q, ',');
10844 if(p) *p = NULLCHAR;
10845 v = StringToVariant(q);
10846 DisplayError(variantError, 0);
10847 if(v != VariantUnknown && cps == &first) {
10849 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10850 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10851 ASSIGN(appData.variant, q);
10852 Reset(TRUE, FALSE);
10858 snprintf(buf, MSG_SIZ, "variant %s\n", b);
10859 SendToProgram(buf, cps);
10861 currentlyInitializedVariant = gameInfo.variant;
10863 /* [HGM] send opening position in FRC to first engine */
10865 SendToProgram("force\n", cps);
10867 /* engine is now in force mode! Set flag to wake it up after first move. */
10868 setboardSpoiledMachineBlack = 1;
10871 if (cps->sendICS) {
10872 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10873 SendToProgram(buf, cps);
10875 cps->maybeThinking = FALSE;
10876 cps->offeredDraw = 0;
10877 if (!appData.icsActive) {
10878 SendTimeControl(cps, movesPerSession, timeControl,
10879 timeIncrement, appData.searchDepth,
10882 if (appData.showThinking
10883 // [HGM] thinking: four options require thinking output to be sent
10884 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10886 SendToProgram("post\n", cps);
10888 SendToProgram("hard\n", cps);
10889 if (!appData.ponderNextMove) {
10890 /* Warning: "easy" is a toggle in GNU Chess, so don't send
10891 it without being sure what state we are in first. "hard"
10892 is not a toggle, so that one is OK.
10894 SendToProgram("easy\n", cps);
10896 if (cps->usePing) {
10897 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10898 SendToProgram(buf, cps);
10900 cps->initDone = TRUE;
10901 ClearEngineOutputPane(cps == &second);
10906 ResendOptions (ChessProgramState *cps)
10907 { // send the stored value of the options
10910 Option *opt = cps->option;
10911 for(i=0; i<cps->nrOptions; i++, opt++) {
10912 switch(opt->type) {
10916 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10919 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10922 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10928 SendToProgram(buf, cps);
10933 StartChessProgram (ChessProgramState *cps)
10938 if (appData.noChessProgram) return;
10939 cps->initDone = FALSE;
10941 if (strcmp(cps->host, "localhost") == 0) {
10942 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10943 } else if (*appData.remoteShell == NULLCHAR) {
10944 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10946 if (*appData.remoteUser == NULLCHAR) {
10947 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10950 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10951 cps->host, appData.remoteUser, cps->program);
10953 err = StartChildProcess(buf, "", &cps->pr);
10957 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10958 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10959 if(cps != &first) return;
10960 appData.noChessProgram = TRUE;
10963 // DisplayFatalError(buf, err, 1);
10964 // cps->pr = NoProc;
10965 // cps->isr = NULL;
10969 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10970 if (cps->protocolVersion > 1) {
10971 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10972 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10973 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10974 cps->comboCnt = 0; // and values of combo boxes
10976 SendToProgram(buf, cps);
10977 if(cps->reload) ResendOptions(cps);
10979 SendToProgram("xboard\n", cps);
10984 TwoMachinesEventIfReady P((void))
10986 static int curMess = 0;
10987 if (first.lastPing != first.lastPong) {
10988 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10989 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10992 if (second.lastPing != second.lastPong) {
10993 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10994 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10997 DisplayMessage("", ""); curMess = 0;
10998 TwoMachinesEvent();
11002 MakeName (char *template)
11006 static char buf[MSG_SIZ];
11010 clock = time((time_t *)NULL);
11011 tm = localtime(&clock);
11013 while(*p++ = *template++) if(p[-1] == '%') {
11014 switch(*template++) {
11015 case 0: *p = 0; return buf;
11016 case 'Y': i = tm->tm_year+1900; break;
11017 case 'y': i = tm->tm_year-100; break;
11018 case 'M': i = tm->tm_mon+1; break;
11019 case 'd': i = tm->tm_mday; break;
11020 case 'h': i = tm->tm_hour; break;
11021 case 'm': i = tm->tm_min; break;
11022 case 's': i = tm->tm_sec; break;
11025 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11031 CountPlayers (char *p)
11034 while(p = strchr(p, '\n')) p++, n++; // count participants
11039 WriteTourneyFile (char *results, FILE *f)
11040 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11041 if(f == NULL) f = fopen(appData.tourneyFile, "w");
11042 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11043 // create a file with tournament description
11044 fprintf(f, "-participants {%s}\n", appData.participants);
11045 fprintf(f, "-seedBase %d\n", appData.seedBase);
11046 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11047 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11048 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11049 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11050 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11051 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11052 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11053 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11054 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11055 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11056 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11057 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11058 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11059 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11060 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11061 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11062 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11063 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11064 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11065 fprintf(f, "-smpCores %d\n", appData.smpCores);
11067 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11069 fprintf(f, "-mps %d\n", appData.movesPerSession);
11070 fprintf(f, "-tc %s\n", appData.timeControl);
11071 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11073 fprintf(f, "-results \"%s\"\n", results);
11078 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11081 Substitute (char *participants, int expunge)
11083 int i, changed, changes=0, nPlayers=0;
11084 char *p, *q, *r, buf[MSG_SIZ];
11085 if(participants == NULL) return;
11086 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11087 r = p = participants; q = appData.participants;
11088 while(*p && *p == *q) {
11089 if(*p == '\n') r = p+1, nPlayers++;
11092 if(*p) { // difference
11093 while(*p && *p++ != '\n')
11095 while(*q && *q++ != '\n')
11097 changed = nPlayers;
11098 changes = 1 + (strcmp(p, q) != 0);
11100 if(changes == 1) { // a single engine mnemonic was changed
11101 q = r; while(*q) nPlayers += (*q++ == '\n');
11102 p = buf; while(*r && (*p = *r++) != '\n') p++;
11104 NamesToList(firstChessProgramNames, command, mnemonic, "all");
11105 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11106 if(mnemonic[i]) { // The substitute is valid
11108 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11109 flock(fileno(f), LOCK_EX);
11110 ParseArgsFromFile(f);
11111 fseek(f, 0, SEEK_SET);
11112 FREE(appData.participants); appData.participants = participants;
11113 if(expunge) { // erase results of replaced engine
11114 int len = strlen(appData.results), w, b, dummy;
11115 for(i=0; i<len; i++) {
11116 Pairing(i, nPlayers, &w, &b, &dummy);
11117 if((w == changed || b == changed) && appData.results[i] == '*') {
11118 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11123 for(i=0; i<len; i++) {
11124 Pairing(i, nPlayers, &w, &b, &dummy);
11125 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11128 WriteTourneyFile(appData.results, f);
11129 fclose(f); // release lock
11132 } else DisplayError(_("No engine with the name you gave is installed"), 0);
11134 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11135 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
11136 free(participants);
11141 CheckPlayers (char *participants)
11144 char buf[MSG_SIZ], *p;
11145 NamesToList(firstChessProgramNames, command, mnemonic, "all");
11146 while(p = strchr(participants, '\n')) {
11148 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11150 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11152 DisplayError(buf, 0);
11156 participants = p + 1;
11162 CreateTourney (char *name)
11165 if(matchMode && strcmp(name, appData.tourneyFile)) {
11166 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11168 if(name[0] == NULLCHAR) {
11169 if(appData.participants[0])
11170 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11173 f = fopen(name, "r");
11174 if(f) { // file exists
11175 ASSIGN(appData.tourneyFile, name);
11176 ParseArgsFromFile(f); // parse it
11178 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11179 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11180 DisplayError(_("Not enough participants"), 0);
11183 if(CheckPlayers(appData.participants)) return 0;
11184 ASSIGN(appData.tourneyFile, name);
11185 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11186 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11189 appData.noChessProgram = FALSE;
11190 appData.clockMode = TRUE;
11196 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11198 char buf[MSG_SIZ], *p, *q;
11199 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11200 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11201 skip = !all && group[0]; // if group requested, we start in skip mode
11202 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11203 p = names; q = buf; header = 0;
11204 while(*p && *p != '\n') *q++ = *p++;
11206 if(*p == '\n') p++;
11207 if(buf[0] == '#') {
11208 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11209 depth++; // we must be entering a new group
11210 if(all) continue; // suppress printing group headers when complete list requested
11212 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11214 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11215 if(engineList[i]) free(engineList[i]);
11216 engineList[i] = strdup(buf);
11217 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11218 if(engineMnemonic[i]) free(engineMnemonic[i]);
11219 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11221 sscanf(q + 8, "%s", buf + strlen(buf));
11224 engineMnemonic[i] = strdup(buf);
11227 engineList[i] = engineMnemonic[i] = NULL;
11231 // following implemented as macro to avoid type limitations
11232 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11235 SwapEngines (int n)
11236 { // swap settings for first engine and other engine (so far only some selected options)
11241 SWAP(chessProgram, p)
11243 SWAP(hasOwnBookUCI, h)
11244 SWAP(protocolVersion, h)
11246 SWAP(scoreIsAbsolute, h)
11251 SWAP(engOptions, p)
11252 SWAP(engInitString, p)
11253 SWAP(computerString, p)
11255 SWAP(fenOverride, p)
11257 SWAP(accumulateTC, h)
11264 GetEngineLine (char *s, int n)
11268 extern char *icsNames;
11269 if(!s || !*s) return 0;
11270 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11271 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11272 if(!mnemonic[i]) return 0;
11273 if(n == 11) return 1; // just testing if there was a match
11274 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11275 if(n == 1) SwapEngines(n);
11276 ParseArgsFromString(buf);
11277 if(n == 1) SwapEngines(n);
11278 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11279 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11280 ParseArgsFromString(buf);
11286 SetPlayer (int player, char *p)
11287 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
11289 char buf[MSG_SIZ], *engineName;
11290 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11291 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11292 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11294 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11295 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11296 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11297 ParseArgsFromString(buf);
11298 } else { // no engine with this nickname is installed!
11299 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11300 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11301 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11303 DisplayError(buf, 0);
11310 char *recentEngines;
11313 RecentEngineEvent (int nr)
11316 // SwapEngines(1); // bump first to second
11317 // ReplaceEngine(&second, 1); // and load it there
11318 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11319 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11320 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11321 ReplaceEngine(&first, 0);
11322 FloatToFront(&appData.recentEngineList, command[n]);
11327 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11328 { // determine players from game number
11329 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11331 if(appData.tourneyType == 0) {
11332 roundsPerCycle = (nPlayers - 1) | 1;
11333 pairingsPerRound = nPlayers / 2;
11334 } else if(appData.tourneyType > 0) {
11335 roundsPerCycle = nPlayers - appData.tourneyType;
11336 pairingsPerRound = appData.tourneyType;
11338 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11339 gamesPerCycle = gamesPerRound * roundsPerCycle;
11340 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11341 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11342 curRound = nr / gamesPerRound; nr %= gamesPerRound;
11343 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11344 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11345 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11347 if(appData.cycleSync) *syncInterval = gamesPerCycle;
11348 if(appData.roundSync) *syncInterval = gamesPerRound;
11350 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11352 if(appData.tourneyType == 0) {
11353 if(curPairing == (nPlayers-1)/2 ) {
11354 *whitePlayer = curRound;
11355 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11357 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11358 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11359 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11360 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11362 } else if(appData.tourneyType > 1) {
11363 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11364 *whitePlayer = curRound + appData.tourneyType;
11365 } else if(appData.tourneyType > 0) {
11366 *whitePlayer = curPairing;
11367 *blackPlayer = curRound + appData.tourneyType;
11370 // take care of white/black alternation per round.
11371 // For cycles and games this is already taken care of by default, derived from matchGame!
11372 return curRound & 1;
11376 NextTourneyGame (int nr, int *swapColors)
11377 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11379 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11381 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11382 tf = fopen(appData.tourneyFile, "r");
11383 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11384 ParseArgsFromFile(tf); fclose(tf);
11385 InitTimeControls(); // TC might be altered from tourney file
11387 nPlayers = CountPlayers(appData.participants); // count participants
11388 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11389 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11392 p = q = appData.results;
11393 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11394 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11395 DisplayMessage(_("Waiting for other game(s)"),"");
11396 waitingForGame = TRUE;
11397 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11400 waitingForGame = FALSE;
11403 if(appData.tourneyType < 0) {
11404 if(nr>=0 && !pairingReceived) {
11406 if(pairing.pr == NoProc) {
11407 if(!appData.pairingEngine[0]) {
11408 DisplayFatalError(_("No pairing engine specified"), 0, 1);
11411 StartChessProgram(&pairing); // starts the pairing engine
11413 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11414 SendToProgram(buf, &pairing);
11415 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11416 SendToProgram(buf, &pairing);
11417 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11419 pairingReceived = 0; // ... so we continue here
11421 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11422 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11423 matchGame = 1; roundNr = nr / syncInterval + 1;
11426 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11428 // redefine engines, engine dir, etc.
11429 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11430 if(first.pr == NoProc) {
11431 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11432 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
11434 if(second.pr == NoProc) {
11436 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11437 SwapEngines(1); // and make that valid for second engine by swapping
11438 InitEngine(&second, 1);
11440 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
11441 UpdateLogos(FALSE); // leave display to ModeHiglight()
11447 { // performs game initialization that does not invoke engines, and then tries to start the game
11448 int res, firstWhite, swapColors = 0;
11449 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11450 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
11452 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11453 if(strcmp(buf, currentDebugFile)) { // name has changed
11454 FILE *f = fopen(buf, "w");
11455 if(f) { // if opening the new file failed, just keep using the old one
11456 ASSIGN(currentDebugFile, buf);
11460 if(appData.serverFileName) {
11461 if(serverFP) fclose(serverFP);
11462 serverFP = fopen(appData.serverFileName, "w");
11463 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11464 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11468 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11469 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11470 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
11471 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11472 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11473 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11474 Reset(FALSE, first.pr != NoProc);
11475 res = LoadGameOrPosition(matchGame); // setup game
11476 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11477 if(!res) return; // abort when bad game/pos file
11478 if(appData.epd) {// in EPD mode we make sure first engine is to move
11479 firstWhite = !(forwardMostMove & 1);
11480 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
11481 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11483 TwoMachinesEvent();
11487 UserAdjudicationEvent (int result)
11489 ChessMove gameResult = GameIsDrawn;
11492 gameResult = WhiteWins;
11494 else if( result < 0 ) {
11495 gameResult = BlackWins;
11498 if( gameMode == TwoMachinesPlay ) {
11499 GameEnds( gameResult, "User adjudication", GE_XBOARD );
11504 // [HGM] save: calculate checksum of game to make games easily identifiable
11506 StringCheckSum (char *s)
11509 if(s==NULL) return 0;
11510 while(*s) i = i*259 + *s++;
11518 for(i=backwardMostMove; i<forwardMostMove; i++) {
11519 sum += pvInfoList[i].depth;
11520 sum += StringCheckSum(parseList[i]);
11521 sum += StringCheckSum(commentList[i]);
11524 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11525 return sum + StringCheckSum(commentList[i]);
11526 } // end of save patch
11529 GameEnds (ChessMove result, char *resultDetails, int whosays)
11531 GameMode nextGameMode;
11533 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11535 if(endingGame) return; /* [HGM] crash: forbid recursion */
11537 if(twoBoards) { // [HGM] dual: switch back to one board
11538 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11539 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11541 if (appData.debugMode) {
11542 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11543 result, resultDetails ? resultDetails : "(null)", whosays);
11546 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11548 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11550 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11551 /* If we are playing on ICS, the server decides when the
11552 game is over, but the engine can offer to draw, claim
11556 if (appData.zippyPlay && first.initDone) {
11557 if (result == GameIsDrawn) {
11558 /* In case draw still needs to be claimed */
11559 SendToICS(ics_prefix);
11560 SendToICS("draw\n");
11561 } else if (StrCaseStr(resultDetails, "resign")) {
11562 SendToICS(ics_prefix);
11563 SendToICS("resign\n");
11567 endingGame = 0; /* [HGM] crash */
11571 /* If we're loading the game from a file, stop */
11572 if (whosays == GE_FILE) {
11573 (void) StopLoadGameTimer();
11577 /* Cancel draw offers */
11578 first.offeredDraw = second.offeredDraw = 0;
11580 /* If this is an ICS game, only ICS can really say it's done;
11581 if not, anyone can. */
11582 isIcsGame = (gameMode == IcsPlayingWhite ||
11583 gameMode == IcsPlayingBlack ||
11584 gameMode == IcsObserving ||
11585 gameMode == IcsExamining);
11587 if (!isIcsGame || whosays == GE_ICS) {
11588 /* OK -- not an ICS game, or ICS said it was done */
11590 if (!isIcsGame && !appData.noChessProgram)
11591 SetUserThinkingEnables();
11593 /* [HGM] if a machine claims the game end we verify this claim */
11594 if(gameMode == TwoMachinesPlay && appData.testClaims) {
11595 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11597 ChessMove trueResult = (ChessMove) -1;
11599 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
11600 first.twoMachinesColor[0] :
11601 second.twoMachinesColor[0] ;
11603 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11604 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11605 /* [HGM] verify: engine mate claims accepted if they were flagged */
11606 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11608 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11609 /* [HGM] verify: engine mate claims accepted if they were flagged */
11610 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11612 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11613 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11616 // now verify win claims, but not in drop games, as we don't understand those yet
11617 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11618 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11619 (result == WhiteWins && claimer == 'w' ||
11620 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
11621 if (appData.debugMode) {
11622 fprintf(debugFP, "result=%d sp=%d move=%d\n",
11623 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11625 if(result != trueResult) {
11626 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11627 result = claimer == 'w' ? BlackWins : WhiteWins;
11628 resultDetails = buf;
11631 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11632 && (forwardMostMove <= backwardMostMove ||
11633 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11634 (claimer=='b')==(forwardMostMove&1))
11636 /* [HGM] verify: draws that were not flagged are false claims */
11637 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11638 result = claimer == 'w' ? BlackWins : WhiteWins;
11639 resultDetails = buf;
11641 /* (Claiming a loss is accepted no questions asked!) */
11642 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11643 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11644 result = GameUnfinished;
11645 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11647 /* [HGM] bare: don't allow bare King to win */
11648 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11649 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11650 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11651 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11652 && result != GameIsDrawn)
11653 { int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11654 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11655 int p = (signed char)boards[forwardMostMove][i][j] - color;
11656 if(p >= 0 && p <= (int)WhiteKing) k++;
11657 oppoKings += (p + color == WhiteKing + BlackPawn - color);
11659 if (appData.debugMode) {
11660 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11661 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11663 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11664 result = GameIsDrawn;
11665 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11666 resultDetails = buf;
11672 if(serverMoves != NULL && !loadFlag) { char c = '=';
11673 if(result==WhiteWins) c = '+';
11674 if(result==BlackWins) c = '-';
11675 if(resultDetails != NULL)
11676 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11678 if (resultDetails != NULL) {
11679 gameInfo.result = result;
11680 gameInfo.resultDetails = StrSave(resultDetails);
11682 /* display last move only if game was not loaded from file */
11683 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11684 DisplayMove(currentMove - 1);
11686 if (forwardMostMove != 0) {
11687 if (gameMode != PlayFromGameFile && gameMode != EditGame
11688 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11690 if (*appData.saveGameFile != NULLCHAR) {
11691 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11692 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11694 SaveGameToFile(appData.saveGameFile, TRUE);
11695 } else if (appData.autoSaveGames) {
11696 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11698 if (*appData.savePositionFile != NULLCHAR) {
11699 SavePositionToFile(appData.savePositionFile);
11701 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11705 /* Tell program how game ended in case it is learning */
11706 /* [HGM] Moved this to after saving the PGN, just in case */
11707 /* engine died and we got here through time loss. In that */
11708 /* case we will get a fatal error writing the pipe, which */
11709 /* would otherwise lose us the PGN. */
11710 /* [HGM] crash: not needed anymore, but doesn't hurt; */
11711 /* output during GameEnds should never be fatal anymore */
11712 if (gameMode == MachinePlaysWhite ||
11713 gameMode == MachinePlaysBlack ||
11714 gameMode == TwoMachinesPlay ||
11715 gameMode == IcsPlayingWhite ||
11716 gameMode == IcsPlayingBlack ||
11717 gameMode == BeginningOfGame) {
11719 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11721 if (first.pr != NoProc) {
11722 SendToProgram(buf, &first);
11724 if (second.pr != NoProc &&
11725 gameMode == TwoMachinesPlay) {
11726 SendToProgram(buf, &second);
11731 if (appData.icsActive) {
11732 if (appData.quietPlay &&
11733 (gameMode == IcsPlayingWhite ||
11734 gameMode == IcsPlayingBlack)) {
11735 SendToICS(ics_prefix);
11736 SendToICS("set shout 1\n");
11738 nextGameMode = IcsIdle;
11739 ics_user_moved = FALSE;
11740 /* clean up premove. It's ugly when the game has ended and the
11741 * premove highlights are still on the board.
11744 gotPremove = FALSE;
11745 ClearPremoveHighlights();
11746 DrawPosition(FALSE, boards[currentMove]);
11748 if (whosays == GE_ICS) {
11751 if (gameMode == IcsPlayingWhite)
11753 else if(gameMode == IcsPlayingBlack)
11754 PlayIcsLossSound();
11757 if (gameMode == IcsPlayingBlack)
11759 else if(gameMode == IcsPlayingWhite)
11760 PlayIcsLossSound();
11763 PlayIcsDrawSound();
11766 PlayIcsUnfinishedSound();
11769 if(appData.quitNext) { ExitEvent(0); return; }
11770 } else if (gameMode == EditGame ||
11771 gameMode == PlayFromGameFile ||
11772 gameMode == AnalyzeMode ||
11773 gameMode == AnalyzeFile) {
11774 nextGameMode = gameMode;
11776 nextGameMode = EndOfGame;
11781 nextGameMode = gameMode;
11784 if (appData.noChessProgram) {
11785 gameMode = nextGameMode;
11787 endingGame = 0; /* [HGM] crash */
11792 /* Put first chess program into idle state */
11793 if (first.pr != NoProc &&
11794 (gameMode == MachinePlaysWhite ||
11795 gameMode == MachinePlaysBlack ||
11796 gameMode == TwoMachinesPlay ||
11797 gameMode == IcsPlayingWhite ||
11798 gameMode == IcsPlayingBlack ||
11799 gameMode == BeginningOfGame)) {
11800 SendToProgram("force\n", &first);
11801 if (first.usePing) {
11803 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11804 SendToProgram(buf, &first);
11807 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11808 /* Kill off first chess program */
11809 if (first.isr != NULL)
11810 RemoveInputSource(first.isr);
11813 if (first.pr != NoProc) {
11815 DoSleep( appData.delayBeforeQuit );
11816 SendToProgram("quit\n", &first);
11817 DestroyChildProcess(first.pr, 4 + first.useSigterm);
11818 first.reload = TRUE;
11822 if (second.reuse) {
11823 /* Put second chess program into idle state */
11824 if (second.pr != NoProc &&
11825 gameMode == TwoMachinesPlay) {
11826 SendToProgram("force\n", &second);
11827 if (second.usePing) {
11829 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11830 SendToProgram(buf, &second);
11833 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11834 /* Kill off second chess program */
11835 if (second.isr != NULL)
11836 RemoveInputSource(second.isr);
11839 if (second.pr != NoProc) {
11840 DoSleep( appData.delayBeforeQuit );
11841 SendToProgram("quit\n", &second);
11842 DestroyChildProcess(second.pr, 4 + second.useSigterm);
11843 second.reload = TRUE;
11845 second.pr = NoProc;
11848 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11849 char resChar = '=';
11853 if (first.twoMachinesColor[0] == 'w') {
11856 second.matchWins++;
11861 if (first.twoMachinesColor[0] == 'b') {
11864 second.matchWins++;
11867 case GameUnfinished:
11873 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11874 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11875 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11876 ReserveGame(nextGame, resChar); // sets nextGame
11877 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11878 else ranking = strdup("busy"); //suppress popup when aborted but not finished
11879 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11881 if (nextGame <= appData.matchGames && !abortMatch) {
11882 gameMode = nextGameMode;
11883 matchGame = nextGame; // this will be overruled in tourney mode!
11884 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11885 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11886 endingGame = 0; /* [HGM] crash */
11889 gameMode = nextGameMode;
11891 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
11892 OutputKibitz(2, buf);
11893 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
11894 OutputKibitz(2, buf);
11895 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
11896 if(second.matchWins) OutputKibitz(2, buf);
11897 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
11898 OutputKibitz(2, buf);
11900 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11901 first.tidy, second.tidy,
11902 first.matchWins, second.matchWins,
11903 appData.matchGames - (first.matchWins + second.matchWins));
11904 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11905 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11906 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11907 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11908 first.twoMachinesColor = "black\n";
11909 second.twoMachinesColor = "white\n";
11911 first.twoMachinesColor = "white\n";
11912 second.twoMachinesColor = "black\n";
11916 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11917 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11919 gameMode = nextGameMode;
11921 endingGame = 0; /* [HGM] crash */
11922 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11923 if(matchMode == TRUE) { // match through command line: exit with or without popup
11925 ToNrEvent(forwardMostMove);
11926 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11928 } else DisplayFatalError(buf, 0, 0);
11929 } else { // match through menu; just stop, with or without popup
11930 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11933 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11934 } else DisplayNote(buf);
11936 if(ranking) free(ranking);
11940 /* Assumes program was just initialized (initString sent).
11941 Leaves program in force mode. */
11943 FeedMovesToProgram (ChessProgramState *cps, int upto)
11947 if (appData.debugMode)
11948 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11949 startedFromSetupPosition ? "position and " : "",
11950 backwardMostMove, upto, cps->which);
11951 if(currentlyInitializedVariant != gameInfo.variant) {
11953 // [HGM] variantswitch: make engine aware of new variant
11954 if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11955 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11956 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11957 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11958 SendToProgram(buf, cps);
11959 currentlyInitializedVariant = gameInfo.variant;
11961 SendToProgram("force\n", cps);
11962 if (startedFromSetupPosition) {
11963 SendBoard(cps, backwardMostMove);
11964 if (appData.debugMode) {
11965 fprintf(debugFP, "feedMoves\n");
11968 for (i = backwardMostMove; i < upto; i++) {
11969 SendMoveToProgram(i, cps);
11975 ResurrectChessProgram ()
11977 /* The chess program may have exited.
11978 If so, restart it and feed it all the moves made so far. */
11979 static int doInit = 0;
11981 if (appData.noChessProgram) return 1;
11983 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11984 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11985 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11986 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11988 if (first.pr != NoProc) return 1;
11989 StartChessProgram(&first);
11991 InitChessProgram(&first, FALSE);
11992 FeedMovesToProgram(&first, currentMove);
11994 if (!first.sendTime) {
11995 /* can't tell gnuchess what its clock should read,
11996 so we bow to its notion. */
11998 timeRemaining[0][currentMove] = whiteTimeRemaining;
11999 timeRemaining[1][currentMove] = blackTimeRemaining;
12002 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
12003 appData.icsEngineAnalyze) && first.analysisSupport) {
12004 SendToProgram("analyze\n", &first);
12005 first.analyzing = TRUE;
12011 * Button procedures
12014 Reset (int redraw, int init)
12018 if (appData.debugMode) {
12019 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12020 redraw, init, gameMode);
12022 pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12023 deadRanks = 0; // assume entire board is used
12024 for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12025 CleanupTail(); // [HGM] vari: delete any stored variations
12026 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12027 pausing = pauseExamInvalid = FALSE;
12028 startedFromSetupPosition = blackPlaysFirst = FALSE;
12030 whiteFlag = blackFlag = FALSE;
12031 userOfferedDraw = FALSE;
12032 hintRequested = bookRequested = FALSE;
12033 first.maybeThinking = FALSE;
12034 second.maybeThinking = FALSE;
12035 first.bookSuspend = FALSE; // [HGM] book
12036 second.bookSuspend = FALSE;
12037 thinkOutput[0] = NULLCHAR;
12038 lastHint[0] = NULLCHAR;
12039 ClearGameInfo(&gameInfo);
12040 gameInfo.variant = StringToVariant(appData.variant);
12041 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12042 gameInfo.variant = VariantUnknown;
12043 strncpy(engineVariant, appData.variant, MSG_SIZ);
12045 ics_user_moved = ics_clock_paused = FALSE;
12046 ics_getting_history = H_FALSE;
12048 white_holding[0] = black_holding[0] = NULLCHAR;
12049 ClearProgramStats();
12050 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12054 flipView = appData.flipView;
12055 ClearPremoveHighlights();
12056 gotPremove = FALSE;
12057 alarmSounded = FALSE;
12058 killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12060 GameEnds(EndOfFile, NULL, GE_PLAYER);
12061 if(appData.serverMovesName != NULL) {
12062 /* [HGM] prepare to make moves file for broadcasting */
12063 clock_t t = clock();
12064 if(serverMoves != NULL) fclose(serverMoves);
12065 serverMoves = fopen(appData.serverMovesName, "r");
12066 if(serverMoves != NULL) {
12067 fclose(serverMoves);
12068 /* delay 15 sec before overwriting, so all clients can see end */
12069 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12071 serverMoves = fopen(appData.serverMovesName, "w");
12075 gameMode = BeginningOfGame;
12077 if(appData.icsActive) gameInfo.variant = VariantNormal;
12078 currentMove = forwardMostMove = backwardMostMove = 0;
12079 MarkTargetSquares(1);
12080 InitPosition(redraw);
12081 for (i = 0; i < MAX_MOVES; i++) {
12082 if (commentList[i] != NULL) {
12083 free(commentList[i]);
12084 commentList[i] = NULL;
12088 timeRemaining[0][0] = whiteTimeRemaining;
12089 timeRemaining[1][0] = blackTimeRemaining;
12091 if (first.pr == NoProc) {
12092 StartChessProgram(&first);
12095 InitChessProgram(&first, startedFromSetupPosition);
12098 DisplayMessage("", "");
12099 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12100 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12101 ClearMap(); // [HGM] exclude: invalidate map
12105 AutoPlayGameLoop ()
12108 if (!AutoPlayOneMove())
12110 if (matchMode || appData.timeDelay == 0)
12112 if (appData.timeDelay < 0)
12114 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12122 ReloadGame(1); // next game
12128 int fromX, fromY, toX, toY;
12130 if (appData.debugMode) {
12131 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12134 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12137 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12138 pvInfoList[currentMove].depth = programStats.depth;
12139 pvInfoList[currentMove].score = programStats.score;
12140 pvInfoList[currentMove].time = 0;
12141 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12142 else { // append analysis of final position as comment
12144 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12145 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12147 programStats.depth = 0;
12150 if (currentMove >= forwardMostMove) {
12151 if(gameMode == AnalyzeFile) {
12152 if(appData.loadGameIndex == -1) {
12153 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12154 ScheduleDelayedEvent(AnalyzeNextGame, 10);
12156 ExitAnalyzeMode(); SendToProgram("force\n", &first);
12159 // gameMode = EndOfGame;
12160 // ModeHighlight();
12162 /* [AS] Clear current move marker at the end of a game */
12163 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12168 toX = moveList[currentMove][2] - AAA;
12169 toY = moveList[currentMove][3] - ONE;
12171 if (moveList[currentMove][1] == '@') {
12172 if (appData.highlightLastMove) {
12173 SetHighlights(-1, -1, toX, toY);
12176 fromX = moveList[currentMove][0] - AAA;
12177 fromY = moveList[currentMove][1] - ONE;
12179 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12181 if(moveList[currentMove][4] == ';') { // multi-leg
12182 killX = moveList[currentMove][5] - AAA;
12183 killY = moveList[currentMove][6] - ONE;
12185 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12186 killX = killY = -1;
12188 if (appData.highlightLastMove) {
12189 SetHighlights(fromX, fromY, toX, toY);
12192 DisplayMove(currentMove);
12193 SendMoveToProgram(currentMove++, &first);
12194 DisplayBothClocks();
12195 DrawPosition(FALSE, boards[currentMove]);
12196 // [HGM] PV info: always display, routine tests if empty
12197 DisplayComment(currentMove - 1, commentList[currentMove]);
12203 LoadGameOneMove (ChessMove readAhead)
12205 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12206 char promoChar = NULLCHAR;
12207 ChessMove moveType;
12208 char move[MSG_SIZ];
12211 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12212 gameMode != AnalyzeMode && gameMode != Training) {
12217 yyboardindex = forwardMostMove;
12218 if (readAhead != EndOfFile) {
12219 moveType = readAhead;
12221 if (gameFileFP == NULL)
12223 moveType = (ChessMove) Myylex();
12227 switch (moveType) {
12229 if (appData.debugMode)
12230 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12233 /* append the comment but don't display it */
12234 AppendComment(currentMove, p, FALSE);
12237 case WhiteCapturesEnPassant:
12238 case BlackCapturesEnPassant:
12239 case WhitePromotion:
12240 case BlackPromotion:
12241 case WhiteNonPromotion:
12242 case BlackNonPromotion:
12245 case WhiteKingSideCastle:
12246 case WhiteQueenSideCastle:
12247 case BlackKingSideCastle:
12248 case BlackQueenSideCastle:
12249 case WhiteKingSideCastleWild:
12250 case WhiteQueenSideCastleWild:
12251 case BlackKingSideCastleWild:
12252 case BlackQueenSideCastleWild:
12254 case WhiteHSideCastleFR:
12255 case WhiteASideCastleFR:
12256 case BlackHSideCastleFR:
12257 case BlackASideCastleFR:
12259 if (appData.debugMode)
12260 fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12261 fromX = currentMoveString[0] - AAA;
12262 fromY = currentMoveString[1] - ONE;
12263 toX = currentMoveString[2] - AAA;
12264 toY = currentMoveString[3] - ONE;
12265 promoChar = currentMoveString[4];
12266 if(promoChar == ';') promoChar = currentMoveString[7];
12271 if (appData.debugMode)
12272 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12273 fromX = moveType == WhiteDrop ?
12274 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12275 (int) CharToPiece(ToLower(currentMoveString[0]));
12277 toX = currentMoveString[2] - AAA;
12278 toY = currentMoveString[3] - ONE;
12284 case GameUnfinished:
12285 if (appData.debugMode)
12286 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12287 p = strchr(yy_text, '{');
12288 if (p == NULL) p = strchr(yy_text, '(');
12291 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12293 q = strchr(p, *p == '{' ? '}' : ')');
12294 if (q != NULL) *q = NULLCHAR;
12297 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12298 GameEnds(moveType, p, GE_FILE);
12300 if (cmailMsgLoaded) {
12302 flipView = WhiteOnMove(currentMove);
12303 if (moveType == GameUnfinished) flipView = !flipView;
12304 if (appData.debugMode)
12305 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12310 if (appData.debugMode)
12311 fprintf(debugFP, "Parser hit end of file\n");
12312 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12318 if (WhiteOnMove(currentMove)) {
12319 GameEnds(BlackWins, "Black mates", GE_FILE);
12321 GameEnds(WhiteWins, "White mates", GE_FILE);
12325 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12331 case MoveNumberOne:
12332 if (lastLoadGameStart == GNUChessGame) {
12333 /* GNUChessGames have numbers, but they aren't move numbers */
12334 if (appData.debugMode)
12335 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12336 yy_text, (int) moveType);
12337 return LoadGameOneMove(EndOfFile); /* tail recursion */
12339 /* else fall thru */
12344 /* Reached start of next game in file */
12345 if (appData.debugMode)
12346 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12347 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12353 if (WhiteOnMove(currentMove)) {
12354 GameEnds(BlackWins, "Black mates", GE_FILE);
12356 GameEnds(WhiteWins, "White mates", GE_FILE);
12360 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12366 case PositionDiagram: /* should not happen; ignore */
12367 case ElapsedTime: /* ignore */
12368 case NAG: /* ignore */
12369 if (appData.debugMode)
12370 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12371 yy_text, (int) moveType);
12372 return LoadGameOneMove(EndOfFile); /* tail recursion */
12375 if (appData.testLegality) {
12376 if (appData.debugMode)
12377 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12378 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12379 (forwardMostMove / 2) + 1,
12380 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12381 DisplayError(move, 0);
12384 if (appData.debugMode)
12385 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12386 yy_text, currentMoveString);
12387 if(currentMoveString[1] == '@') {
12388 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12391 fromX = currentMoveString[0] - AAA;
12392 fromY = currentMoveString[1] - ONE;
12394 toX = currentMoveString[2] - AAA;
12395 toY = currentMoveString[3] - ONE;
12396 promoChar = currentMoveString[4];
12400 case AmbiguousMove:
12401 if (appData.debugMode)
12402 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12403 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12404 (forwardMostMove / 2) + 1,
12405 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12406 DisplayError(move, 0);
12411 case ImpossibleMove:
12412 if (appData.debugMode)
12413 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12414 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12415 (forwardMostMove / 2) + 1,
12416 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12417 DisplayError(move, 0);
12423 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12424 DrawPosition(FALSE, boards[currentMove]);
12425 DisplayBothClocks();
12426 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12427 DisplayComment(currentMove - 1, commentList[currentMove]);
12429 (void) StopLoadGameTimer();
12431 cmailOldMove = forwardMostMove;
12434 /* currentMoveString is set as a side-effect of yylex */
12436 thinkOutput[0] = NULLCHAR;
12437 MakeMove(fromX, fromY, toX, toY, promoChar);
12438 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12439 currentMove = forwardMostMove;
12444 /* Load the nth game from the given file */
12446 LoadGameFromFile (char *filename, int n, char *title, int useList)
12451 if (strcmp(filename, "-") == 0) {
12455 f = fopen(filename, "rb");
12457 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12458 DisplayError(buf, errno);
12462 if (fseek(f, 0, 0) == -1) {
12463 /* f is not seekable; probably a pipe */
12466 if (useList && n == 0) {
12467 int error = GameListBuild(f);
12469 DisplayError(_("Cannot build game list"), error);
12470 } else if (!ListEmpty(&gameList) &&
12471 ((ListGame *) gameList.tailPred)->number > 1) {
12472 GameListPopUp(f, title);
12479 return LoadGame(f, n, title, FALSE);
12484 MakeRegisteredMove ()
12486 int fromX, fromY, toX, toY;
12488 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12489 switch (cmailMoveType[lastLoadGameNumber - 1]) {
12492 if (appData.debugMode)
12493 fprintf(debugFP, "Restoring %s for game %d\n",
12494 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12496 thinkOutput[0] = NULLCHAR;
12497 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12498 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12499 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12500 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12501 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12502 promoChar = cmailMove[lastLoadGameNumber - 1][4];
12503 MakeMove(fromX, fromY, toX, toY, promoChar);
12504 ShowMove(fromX, fromY, toX, toY);
12506 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12513 if (WhiteOnMove(currentMove)) {
12514 GameEnds(BlackWins, "Black mates", GE_PLAYER);
12516 GameEnds(WhiteWins, "White mates", GE_PLAYER);
12521 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12528 if (WhiteOnMove(currentMove)) {
12529 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12531 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12536 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12547 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12549 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12553 if (gameNumber > nCmailGames) {
12554 DisplayError(_("No more games in this message"), 0);
12557 if (f == lastLoadGameFP) {
12558 int offset = gameNumber - lastLoadGameNumber;
12560 cmailMsg[0] = NULLCHAR;
12561 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12562 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12563 nCmailMovesRegistered--;
12565 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12566 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12567 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12570 if (! RegisterMove()) return FALSE;
12574 retVal = LoadGame(f, gameNumber, title, useList);
12576 /* Make move registered during previous look at this game, if any */
12577 MakeRegisteredMove();
12579 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12580 commentList[currentMove]
12581 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12582 DisplayComment(currentMove - 1, commentList[currentMove]);
12588 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12590 ReloadGame (int offset)
12592 int gameNumber = lastLoadGameNumber + offset;
12593 if (lastLoadGameFP == NULL) {
12594 DisplayError(_("No game has been loaded yet"), 0);
12597 if (gameNumber <= 0) {
12598 DisplayError(_("Can't back up any further"), 0);
12601 if (cmailMsgLoaded) {
12602 return CmailLoadGame(lastLoadGameFP, gameNumber,
12603 lastLoadGameTitle, lastLoadGameUseList);
12605 return LoadGame(lastLoadGameFP, gameNumber,
12606 lastLoadGameTitle, lastLoadGameUseList);
12610 int keys[EmptySquare+1];
12613 PositionMatches (Board b1, Board b2)
12616 switch(appData.searchMode) {
12617 case 1: return CompareWithRights(b1, b2);
12619 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12620 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12624 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12625 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12626 sum += keys[b1[r][f]] - keys[b2[r][f]];
12630 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12631 sum += keys[b1[r][f]] - keys[b2[r][f]];
12643 int pieceList[256], quickBoard[256];
12644 ChessSquare pieceType[256] = { EmptySquare };
12645 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12646 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12647 int soughtTotal, turn;
12648 Boolean epOK, flipSearch;
12651 unsigned char piece, to;
12654 #define DSIZE (250000)
12656 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12657 Move *moveDatabase = initialSpace;
12658 unsigned int movePtr, dataSize = DSIZE;
12661 MakePieceList (Board board, int *counts)
12663 int r, f, n=Q_PROMO, total=0;
12664 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12665 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12666 int sq = f + (r<<4);
12667 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12668 quickBoard[sq] = ++n;
12670 pieceType[n] = board[r][f];
12671 counts[board[r][f]]++;
12672 if(board[r][f] == WhiteKing) pieceList[1] = n; else
12673 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12677 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12682 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12684 int sq = fromX + (fromY<<4);
12685 int piece = quickBoard[sq], rook;
12686 quickBoard[sq] = 0;
12687 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12688 if(piece == pieceList[1] && fromY == toY) {
12689 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12690 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12691 moveDatabase[movePtr++].piece = Q_WCASTL;
12692 quickBoard[sq] = piece;
12693 piece = quickBoard[from]; quickBoard[from] = 0;
12694 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12695 } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12696 quickBoard[sq] = 0; // remove Rook
12697 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12698 moveDatabase[movePtr++].piece = Q_WCASTL;
12699 quickBoard[sq] = pieceList[1]; // put King
12701 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12704 if(piece == pieceList[2] && fromY == toY) {
12705 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12706 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12707 moveDatabase[movePtr++].piece = Q_BCASTL;
12708 quickBoard[sq] = piece;
12709 piece = quickBoard[from]; quickBoard[from] = 0;
12710 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12711 } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12712 quickBoard[sq] = 0; // remove Rook
12713 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12714 moveDatabase[movePtr++].piece = Q_BCASTL;
12715 quickBoard[sq] = pieceList[2]; // put King
12717 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12720 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12721 quickBoard[(fromY<<4)+toX] = 0;
12722 moveDatabase[movePtr].piece = Q_EP;
12723 moveDatabase[movePtr++].to = (fromY<<4)+toX;
12724 moveDatabase[movePtr].to = sq;
12726 if(promoPiece != pieceType[piece]) {
12727 moveDatabase[movePtr++].piece = Q_PROMO;
12728 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12730 moveDatabase[movePtr].piece = piece;
12731 quickBoard[sq] = piece;
12736 PackGame (Board board)
12738 Move *newSpace = NULL;
12739 moveDatabase[movePtr].piece = 0; // terminate previous game
12740 if(movePtr > dataSize) {
12741 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12742 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12743 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12746 Move *p = moveDatabase, *q = newSpace;
12747 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
12748 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12749 moveDatabase = newSpace;
12750 } else { // calloc failed, we must be out of memory. Too bad...
12751 dataSize = 0; // prevent calloc events for all subsequent games
12752 return 0; // and signal this one isn't cached
12756 MakePieceList(board, counts);
12761 QuickCompare (Board board, int *minCounts, int *maxCounts)
12762 { // compare according to search mode
12764 switch(appData.searchMode)
12766 case 1: // exact position match
12767 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12768 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12769 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12772 case 2: // can have extra material on empty squares
12773 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12774 if(board[r][f] == EmptySquare) continue;
12775 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12778 case 3: // material with exact Pawn structure
12779 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12780 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12781 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12782 } // fall through to material comparison
12783 case 4: // exact material
12784 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12786 case 6: // material range with given imbalance
12787 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12788 // fall through to range comparison
12789 case 5: // material range
12790 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12796 QuickScan (Board board, Move *move)
12797 { // reconstruct game,and compare all positions in it
12798 int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12800 int piece = move->piece;
12801 int to = move->to, from = pieceList[piece];
12802 if(found < 0) { // if already found just scan to game end for final piece count
12803 if(QuickCompare(soughtBoard, minSought, maxSought) ||
12804 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12805 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12806 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12808 static int lastCounts[EmptySquare+1];
12810 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12811 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12812 } else stretch = 0;
12813 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12814 if(found >= 0 && !appData.minPieces) return found;
12816 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12817 if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12818 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12819 piece = (++move)->piece;
12820 from = pieceList[piece];
12821 counts[pieceType[piece]]--;
12822 pieceType[piece] = (ChessSquare) move->to;
12823 counts[move->to]++;
12824 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12825 counts[pieceType[quickBoard[to]]]--;
12826 quickBoard[to] = 0; total--;
12829 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12830 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12831 from = pieceList[piece]; // so this must be King
12832 quickBoard[from] = 0;
12833 pieceList[piece] = to;
12834 from = pieceList[(++move)->piece]; // for FRC this has to be done here
12835 quickBoard[from] = 0; // rook
12836 quickBoard[to] = piece;
12837 to = move->to; piece = move->piece;
12841 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12842 if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12843 quickBoard[from] = 0;
12845 quickBoard[to] = piece;
12846 pieceList[piece] = to;
12856 flipSearch = FALSE;
12857 CopyBoard(soughtBoard, boards[currentMove]);
12858 soughtTotal = MakePieceList(soughtBoard, maxSought);
12859 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12860 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12861 CopyBoard(reverseBoard, boards[currentMove]);
12862 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12863 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12864 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12865 reverseBoard[r][f] = piece;
12867 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12868 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12869 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12870 || (boards[currentMove][CASTLING][2] == NoRights ||
12871 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12872 && (boards[currentMove][CASTLING][5] == NoRights ||
12873 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12876 CopyBoard(flipBoard, soughtBoard);
12877 CopyBoard(rotateBoard, reverseBoard);
12878 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12879 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
12880 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12883 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12884 if(appData.searchMode >= 5) {
12885 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12886 MakePieceList(soughtBoard, minSought);
12887 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12889 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12890 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12893 GameInfo dummyInfo;
12894 static int creatingBook;
12897 GameContainsPosition (FILE *f, ListGame *lg)
12899 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12900 int fromX, fromY, toX, toY;
12902 static int initDone=FALSE;
12904 // weed out games based on numerical tag comparison
12905 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12906 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12907 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12908 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12910 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12913 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12914 else CopyBoard(boards[scratch], initialPosition); // default start position
12917 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12918 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12921 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12922 fseek(f, lg->offset, 0);
12925 yyboardindex = scratch;
12926 quickFlag = plyNr+1;
12931 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12937 if(plyNr) return -1; // after we have seen moves, this is for new game
12940 case AmbiguousMove: // we cannot reconstruct the game beyond these two
12941 case ImpossibleMove:
12942 case WhiteWins: // game ends here with these four
12945 case GameUnfinished:
12949 if(appData.testLegality) return -1;
12950 case WhiteCapturesEnPassant:
12951 case BlackCapturesEnPassant:
12952 case WhitePromotion:
12953 case BlackPromotion:
12954 case WhiteNonPromotion:
12955 case BlackNonPromotion:
12958 case WhiteKingSideCastle:
12959 case WhiteQueenSideCastle:
12960 case BlackKingSideCastle:
12961 case BlackQueenSideCastle:
12962 case WhiteKingSideCastleWild:
12963 case WhiteQueenSideCastleWild:
12964 case BlackKingSideCastleWild:
12965 case BlackQueenSideCastleWild:
12966 case WhiteHSideCastleFR:
12967 case WhiteASideCastleFR:
12968 case BlackHSideCastleFR:
12969 case BlackASideCastleFR:
12970 fromX = currentMoveString[0] - AAA;
12971 fromY = currentMoveString[1] - ONE;
12972 toX = currentMoveString[2] - AAA;
12973 toY = currentMoveString[3] - ONE;
12974 promoChar = currentMoveString[4];
12978 fromX = next == WhiteDrop ?
12979 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12980 (int) CharToPiece(ToLower(currentMoveString[0]));
12982 toX = currentMoveString[2] - AAA;
12983 toY = currentMoveString[3] - ONE;
12987 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12989 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12990 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12991 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12992 if(appData.findMirror) {
12993 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12994 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12999 /* Load the nth game from open file f */
13001 LoadGame (FILE *f, int gameNumber, char *title, int useList)
13005 int gn = gameNumber;
13006 ListGame *lg = NULL;
13007 int numPGNTags = 0, i;
13009 GameMode oldGameMode;
13010 VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13011 char oldName[MSG_SIZ];
13013 safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13015 if (appData.debugMode)
13016 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13018 if (gameMode == Training )
13019 SetTrainingModeOff();
13021 oldGameMode = gameMode;
13022 if (gameMode != BeginningOfGame) {
13023 Reset(FALSE, TRUE);
13025 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13028 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13029 fclose(lastLoadGameFP);
13033 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13036 fseek(f, lg->offset, 0);
13037 GameListHighlight(gameNumber);
13038 pos = lg->position;
13042 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13043 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13045 DisplayError(_("Game number out of range"), 0);
13050 if (fseek(f, 0, 0) == -1) {
13051 if (f == lastLoadGameFP ?
13052 gameNumber == lastLoadGameNumber + 1 :
13056 DisplayError(_("Can't seek on game file"), 0);
13061 lastLoadGameFP = f;
13062 lastLoadGameNumber = gameNumber;
13063 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13064 lastLoadGameUseList = useList;
13068 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13069 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13070 lg->gameInfo.black);
13072 } else if (*title != NULLCHAR) {
13073 if (gameNumber > 1) {
13074 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13077 DisplayTitle(title);
13081 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13082 gameMode = PlayFromGameFile;
13086 currentMove = forwardMostMove = backwardMostMove = 0;
13087 CopyBoard(boards[0], initialPosition);
13091 * Skip the first gn-1 games in the file.
13092 * Also skip over anything that precedes an identifiable
13093 * start of game marker, to avoid being confused by
13094 * garbage at the start of the file. Currently
13095 * recognized start of game markers are the move number "1",
13096 * the pattern "gnuchess .* game", the pattern
13097 * "^[#;%] [^ ]* game file", and a PGN tag block.
13098 * A game that starts with one of the latter two patterns
13099 * will also have a move number 1, possibly
13100 * following a position diagram.
13101 * 5-4-02: Let's try being more lenient and allowing a game to
13102 * start with an unnumbered move. Does that break anything?
13104 cm = lastLoadGameStart = EndOfFile;
13106 yyboardindex = forwardMostMove;
13107 cm = (ChessMove) Myylex();
13110 if (cmailMsgLoaded) {
13111 nCmailGames = CMAIL_MAX_GAMES - gn;
13114 DisplayError(_("Game not found in file"), 0);
13121 lastLoadGameStart = cm;
13124 case MoveNumberOne:
13125 switch (lastLoadGameStart) {
13130 case MoveNumberOne:
13132 gn--; /* count this game */
13133 lastLoadGameStart = cm;
13142 switch (lastLoadGameStart) {
13145 case MoveNumberOne:
13147 gn--; /* count this game */
13148 lastLoadGameStart = cm;
13151 lastLoadGameStart = cm; /* game counted already */
13159 yyboardindex = forwardMostMove;
13160 cm = (ChessMove) Myylex();
13161 } while (cm == PGNTag || cm == Comment);
13168 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13169 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
13170 != CMAIL_OLD_RESULT) {
13172 cmailResult[ CMAIL_MAX_GAMES
13173 - gn - 1] = CMAIL_OLD_RESULT;
13180 /* Only a NormalMove can be at the start of a game
13181 * without a position diagram. */
13182 if (lastLoadGameStart == EndOfFile ) {
13184 lastLoadGameStart = MoveNumberOne;
13193 if (appData.debugMode)
13194 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13196 for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13198 if (cm == XBoardGame) {
13199 /* Skip any header junk before position diagram and/or move 1 */
13201 yyboardindex = forwardMostMove;
13202 cm = (ChessMove) Myylex();
13204 if (cm == EndOfFile ||
13205 cm == GNUChessGame || cm == XBoardGame) {
13206 /* Empty game; pretend end-of-file and handle later */
13211 if (cm == MoveNumberOne || cm == PositionDiagram ||
13212 cm == PGNTag || cm == Comment)
13215 } else if (cm == GNUChessGame) {
13216 if (gameInfo.event != NULL) {
13217 free(gameInfo.event);
13219 gameInfo.event = StrSave(yy_text);
13222 startedFromSetupPosition = startedFromPositionFile; // [HGM]
13223 while (cm == PGNTag) {
13224 if (appData.debugMode)
13225 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13226 err = ParsePGNTag(yy_text, &gameInfo);
13227 if (!err) numPGNTags++;
13229 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13230 if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13231 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13232 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13233 InitPosition(TRUE);
13234 oldVariant = gameInfo.variant;
13235 if (appData.debugMode)
13236 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13240 if (gameInfo.fen != NULL) {
13241 Board initial_position;
13242 startedFromSetupPosition = TRUE;
13243 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13245 DisplayError(_("Bad FEN position in file"), 0);
13248 CopyBoard(boards[0], initial_position);
13249 if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13250 CopyBoard(initialPosition, initial_position);
13251 if (blackPlaysFirst) {
13252 currentMove = forwardMostMove = backwardMostMove = 1;
13253 CopyBoard(boards[1], initial_position);
13254 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13255 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13256 timeRemaining[0][1] = whiteTimeRemaining;
13257 timeRemaining[1][1] = blackTimeRemaining;
13258 if (commentList[0] != NULL) {
13259 commentList[1] = commentList[0];
13260 commentList[0] = NULL;
13263 currentMove = forwardMostMove = backwardMostMove = 0;
13265 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13267 initialRulePlies = FENrulePlies;
13268 for( i=0; i< nrCastlingRights; i++ )
13269 initialRights[i] = initial_position[CASTLING][i];
13271 yyboardindex = forwardMostMove;
13272 free(gameInfo.fen);
13273 gameInfo.fen = NULL;
13276 yyboardindex = forwardMostMove;
13277 cm = (ChessMove) Myylex();
13279 /* Handle comments interspersed among the tags */
13280 while (cm == Comment) {
13282 if (appData.debugMode)
13283 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13285 AppendComment(currentMove, p, FALSE);
13286 yyboardindex = forwardMostMove;
13287 cm = (ChessMove) Myylex();
13291 /* don't rely on existence of Event tag since if game was
13292 * pasted from clipboard the Event tag may not exist
13294 if (numPGNTags > 0){
13296 if (gameInfo.variant == VariantNormal) {
13297 VariantClass v = StringToVariant(gameInfo.event);
13298 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13299 if(v < VariantShogi) gameInfo.variant = v;
13302 if( appData.autoDisplayTags ) {
13303 tags = PGNTags(&gameInfo);
13304 TagsPopUp(tags, CmailMsg());
13309 /* Make something up, but don't display it now */
13314 if (cm == PositionDiagram) {
13317 Board initial_position;
13319 if (appData.debugMode)
13320 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13322 if (!startedFromSetupPosition) {
13324 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13325 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13336 initial_position[i][j++] = CharToPiece(*p);
13339 while (*p == ' ' || *p == '\t' ||
13340 *p == '\n' || *p == '\r') p++;
13342 if (strncmp(p, "black", strlen("black"))==0)
13343 blackPlaysFirst = TRUE;
13345 blackPlaysFirst = FALSE;
13346 startedFromSetupPosition = TRUE;
13348 CopyBoard(boards[0], initial_position);
13349 if (blackPlaysFirst) {
13350 currentMove = forwardMostMove = backwardMostMove = 1;
13351 CopyBoard(boards[1], initial_position);
13352 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13353 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13354 timeRemaining[0][1] = whiteTimeRemaining;
13355 timeRemaining[1][1] = blackTimeRemaining;
13356 if (commentList[0] != NULL) {
13357 commentList[1] = commentList[0];
13358 commentList[0] = NULL;
13361 currentMove = forwardMostMove = backwardMostMove = 0;
13364 yyboardindex = forwardMostMove;
13365 cm = (ChessMove) Myylex();
13368 if(!creatingBook) {
13369 if (first.pr == NoProc) {
13370 StartChessProgram(&first);
13372 InitChessProgram(&first, FALSE);
13373 if(gameInfo.variant == VariantUnknown && *oldName) {
13374 safeStrCpy(engineVariant, oldName, MSG_SIZ);
13375 gameInfo.variant = v;
13377 SendToProgram("force\n", &first);
13378 if (startedFromSetupPosition) {
13379 SendBoard(&first, forwardMostMove);
13380 if (appData.debugMode) {
13381 fprintf(debugFP, "Load Game\n");
13383 DisplayBothClocks();
13387 /* [HGM] server: flag to write setup moves in broadcast file as one */
13388 loadFlag = appData.suppressLoadMoves;
13390 while (cm == Comment) {
13392 if (appData.debugMode)
13393 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13395 AppendComment(currentMove, p, FALSE);
13396 yyboardindex = forwardMostMove;
13397 cm = (ChessMove) Myylex();
13400 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13401 cm == WhiteWins || cm == BlackWins ||
13402 cm == GameIsDrawn || cm == GameUnfinished) {
13403 DisplayMessage("", _("No moves in game"));
13404 if (cmailMsgLoaded) {
13405 if (appData.debugMode)
13406 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13410 DrawPosition(FALSE, boards[currentMove]);
13411 DisplayBothClocks();
13412 gameMode = EditGame;
13419 // [HGM] PV info: routine tests if comment empty
13420 if (!matchMode && (pausing || appData.timeDelay != 0)) {
13421 DisplayComment(currentMove - 1, commentList[currentMove]);
13423 if (!matchMode && appData.timeDelay != 0)
13424 DrawPosition(FALSE, boards[currentMove]);
13426 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13427 programStats.ok_to_send = 1;
13430 /* if the first token after the PGN tags is a move
13431 * and not move number 1, retrieve it from the parser
13433 if (cm != MoveNumberOne)
13434 LoadGameOneMove(cm);
13436 /* load the remaining moves from the file */
13437 while (LoadGameOneMove(EndOfFile)) {
13438 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13439 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13442 /* rewind to the start of the game */
13443 currentMove = backwardMostMove;
13445 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13447 if (oldGameMode == AnalyzeFile) {
13448 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13449 AnalyzeFileEvent();
13451 if (oldGameMode == AnalyzeMode) {
13452 AnalyzeFileEvent();
13455 if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13456 long int w, b; // [HGM] adjourn: restore saved clock times
13457 char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13458 if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13459 timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13460 timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13464 if(creatingBook) return TRUE;
13465 if (!matchMode && pos > 0) {
13466 ToNrEvent(pos); // [HGM] no autoplay if selected on position
13468 if (matchMode || appData.timeDelay == 0) {
13470 } else if (appData.timeDelay > 0) {
13471 AutoPlayGameLoop();
13474 if (appData.debugMode)
13475 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13477 loadFlag = 0; /* [HGM] true game starts */
13481 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13483 ReloadPosition (int offset)
13485 int positionNumber = lastLoadPositionNumber + offset;
13486 if (lastLoadPositionFP == NULL) {
13487 DisplayError(_("No position has been loaded yet"), 0);
13490 if (positionNumber <= 0) {
13491 DisplayError(_("Can't back up any further"), 0);
13494 return LoadPosition(lastLoadPositionFP, positionNumber,
13495 lastLoadPositionTitle);
13498 /* Load the nth position from the given file */
13500 LoadPositionFromFile (char *filename, int n, char *title)
13505 if (strcmp(filename, "-") == 0) {
13506 return LoadPosition(stdin, n, "stdin");
13508 f = fopen(filename, "rb");
13510 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13511 DisplayError(buf, errno);
13514 return LoadPosition(f, n, title);
13519 /* Load the nth position from the given open file, and close it */
13521 LoadPosition (FILE *f, int positionNumber, char *title)
13523 char *p, line[MSG_SIZ];
13524 Board initial_position;
13525 int i, j, fenMode, pn;
13527 if (gameMode == Training )
13528 SetTrainingModeOff();
13530 if (gameMode != BeginningOfGame) {
13531 Reset(FALSE, TRUE);
13533 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13534 fclose(lastLoadPositionFP);
13536 if (positionNumber == 0) positionNumber = 1;
13537 lastLoadPositionFP = f;
13538 lastLoadPositionNumber = positionNumber;
13539 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13540 if (first.pr == NoProc && !appData.noChessProgram) {
13541 StartChessProgram(&first);
13542 InitChessProgram(&first, FALSE);
13544 pn = positionNumber;
13545 if (positionNumber < 0) {
13546 /* Negative position number means to seek to that byte offset */
13547 if (fseek(f, -positionNumber, 0) == -1) {
13548 DisplayError(_("Can't seek on position file"), 0);
13553 if (fseek(f, 0, 0) == -1) {
13554 if (f == lastLoadPositionFP ?
13555 positionNumber == lastLoadPositionNumber + 1 :
13556 positionNumber == 1) {
13559 DisplayError(_("Can't seek on position file"), 0);
13564 /* See if this file is FEN or old-style xboard */
13565 if (fgets(line, MSG_SIZ, f) == NULL) {
13566 DisplayError(_("Position not found in file"), 0);
13569 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13570 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13573 if (fenMode || line[0] == '#') pn--;
13575 /* skip positions before number pn */
13576 if (fgets(line, MSG_SIZ, f) == NULL) {
13578 DisplayError(_("Position not found in file"), 0);
13581 if (fenMode || line[0] == '#') pn--;
13587 if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13588 DisplayError(_("Bad FEN position in file"), 0);
13591 if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13592 sscanf(p+4, "%[^;]", bestMove);
13593 } else *bestMove = NULLCHAR;
13594 if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13595 sscanf(p+4, "%[^;]", avoidMove);
13596 } else *avoidMove = NULLCHAR;
13598 (void) fgets(line, MSG_SIZ, f);
13599 (void) fgets(line, MSG_SIZ, f);
13601 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13602 (void) fgets(line, MSG_SIZ, f);
13603 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13606 initial_position[i][j++] = CharToPiece(*p);
13610 blackPlaysFirst = FALSE;
13612 (void) fgets(line, MSG_SIZ, f);
13613 if (strncmp(line, "black", strlen("black"))==0)
13614 blackPlaysFirst = TRUE;
13617 startedFromSetupPosition = TRUE;
13619 CopyBoard(boards[0], initial_position);
13620 if (blackPlaysFirst) {
13621 currentMove = forwardMostMove = backwardMostMove = 1;
13622 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13623 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13624 CopyBoard(boards[1], initial_position);
13625 DisplayMessage("", _("Black to play"));
13627 currentMove = forwardMostMove = backwardMostMove = 0;
13628 DisplayMessage("", _("White to play"));
13630 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13631 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13632 SendToProgram("force\n", &first);
13633 SendBoard(&first, forwardMostMove);
13635 if (appData.debugMode) {
13637 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13638 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13639 fprintf(debugFP, "Load Position\n");
13642 if (positionNumber > 1) {
13643 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13644 DisplayTitle(line);
13646 DisplayTitle(title);
13648 gameMode = EditGame;
13651 timeRemaining[0][1] = whiteTimeRemaining;
13652 timeRemaining[1][1] = blackTimeRemaining;
13653 DrawPosition(FALSE, boards[currentMove]);
13660 CopyPlayerNameIntoFileName (char **dest, char *src)
13662 while (*src != NULLCHAR && *src != ',') {
13667 *(*dest)++ = *src++;
13673 DefaultFileName (char *ext)
13675 static char def[MSG_SIZ];
13678 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13680 CopyPlayerNameIntoFileName(&p, gameInfo.white);
13682 CopyPlayerNameIntoFileName(&p, gameInfo.black);
13684 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13691 /* Save the current game to the given file */
13693 SaveGameToFile (char *filename, int append)
13697 int result, i, t,tot=0;
13699 if (strcmp(filename, "-") == 0) {
13700 return SaveGame(stdout, 0, NULL);
13702 for(i=0; i<10; i++) { // upto 10 tries
13703 f = fopen(filename, append ? "a" : "w");
13704 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13705 if(f || errno != 13) break;
13706 DoSleep(t = 5 + random()%11); // wait 5-15 msec
13710 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13711 DisplayError(buf, errno);
13714 safeStrCpy(buf, lastMsg, MSG_SIZ);
13715 DisplayMessage(_("Waiting for access to save file"), "");
13716 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13717 DisplayMessage(_("Saving game"), "");
13718 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
13719 result = SaveGame(f, 0, NULL);
13720 DisplayMessage(buf, "");
13727 SavePart (char *str)
13729 static char buf[MSG_SIZ];
13732 p = strchr(str, ' ');
13733 if (p == NULL) return str;
13734 strncpy(buf, str, p - str);
13735 buf[p - str] = NULLCHAR;
13739 #define PGN_MAX_LINE 75
13741 #define PGN_SIDE_WHITE 0
13742 #define PGN_SIDE_BLACK 1
13745 FindFirstMoveOutOfBook (int side)
13749 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13750 int index = backwardMostMove;
13751 int has_book_hit = 0;
13753 if( (index % 2) != side ) {
13757 while( index < forwardMostMove ) {
13758 /* Check to see if engine is in book */
13759 int depth = pvInfoList[index].depth;
13760 int score = pvInfoList[index].score;
13766 else if( score == 0 && depth == 63 ) {
13767 in_book = 1; /* Zappa */
13769 else if( score == 2 && depth == 99 ) {
13770 in_book = 1; /* Abrok */
13773 has_book_hit += in_book;
13789 GetOutOfBookInfo (char * buf)
13793 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13795 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13796 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13800 if( oob[0] >= 0 || oob[1] >= 0 ) {
13801 for( i=0; i<2; i++ ) {
13805 if( i > 0 && oob[0] >= 0 ) {
13806 strcat( buf, " " );
13809 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13810 sprintf( buf+strlen(buf), "%s%.2f",
13811 pvInfoList[idx].score >= 0 ? "+" : "",
13812 pvInfoList[idx].score / 100.0 );
13818 /* Save game in PGN style */
13820 SaveGamePGN2 (FILE *f)
13822 int i, offset, linelen, newblock;
13825 int movelen, numlen, blank;
13826 char move_buffer[100]; /* [AS] Buffer for move+PV info */
13828 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13830 PrintPGNTags(f, &gameInfo);
13832 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13834 if (backwardMostMove > 0 || startedFromSetupPosition) {
13835 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13836 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13837 fprintf(f, "\n{--------------\n");
13838 PrintPosition(f, backwardMostMove);
13839 fprintf(f, "--------------}\n");
13843 /* [AS] Out of book annotation */
13844 if( appData.saveOutOfBookInfo ) {
13847 GetOutOfBookInfo( buf );
13849 if( buf[0] != '\0' ) {
13850 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13857 i = backwardMostMove;
13861 while (i < forwardMostMove) {
13862 /* Print comments preceding this move */
13863 if (commentList[i] != NULL) {
13864 if (linelen > 0) fprintf(f, "\n");
13865 fprintf(f, "%s", commentList[i]);
13870 /* Format move number */
13872 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13875 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13877 numtext[0] = NULLCHAR;
13879 numlen = strlen(numtext);
13882 /* Print move number */
13883 blank = linelen > 0 && numlen > 0;
13884 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13893 fprintf(f, "%s", numtext);
13897 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13898 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13901 blank = linelen > 0 && movelen > 0;
13902 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13911 fprintf(f, "%s", move_buffer);
13912 linelen += movelen;
13914 /* [AS] Add PV info if present */
13915 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13916 /* [HGM] add time */
13917 char buf[MSG_SIZ]; int seconds;
13919 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13925 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13928 seconds = (seconds + 4)/10; // round to full seconds
13930 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13932 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13935 if(appData.cumulativeTimePGN) {
13936 snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
13939 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13940 pvInfoList[i].score >= 0 ? "+" : "",
13941 pvInfoList[i].score / 100.0,
13942 pvInfoList[i].depth,
13945 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13947 /* Print score/depth */
13948 blank = linelen > 0 && movelen > 0;
13949 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13958 fprintf(f, "%s", move_buffer);
13959 linelen += movelen;
13965 /* Start a new line */
13966 if (linelen > 0) fprintf(f, "\n");
13968 /* Print comments after last move */
13969 if (commentList[i] != NULL) {
13970 fprintf(f, "%s\n", commentList[i]);
13974 if (gameInfo.resultDetails != NULL &&
13975 gameInfo.resultDetails[0] != NULLCHAR) {
13976 char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13977 if(gameInfo.result == GameUnfinished && appData.clockMode &&
13978 (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13979 snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13980 fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13982 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13986 /* Save game in PGN style and close the file */
13988 SaveGamePGN (FILE *f)
13992 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13996 /* Save game in old style and close the file */
13998 SaveGameOldStyle (FILE *f)
14003 tm = time((time_t *) NULL);
14005 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
14008 if (backwardMostMove > 0 || startedFromSetupPosition) {
14009 fprintf(f, "\n[--------------\n");
14010 PrintPosition(f, backwardMostMove);
14011 fprintf(f, "--------------]\n");
14016 i = backwardMostMove;
14017 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14019 while (i < forwardMostMove) {
14020 if (commentList[i] != NULL) {
14021 fprintf(f, "[%s]\n", commentList[i]);
14024 if ((i % 2) == 1) {
14025 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
14028 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
14030 if (commentList[i] != NULL) {
14034 if (i >= forwardMostMove) {
14038 fprintf(f, "%s\n", parseList[i]);
14043 if (commentList[i] != NULL) {
14044 fprintf(f, "[%s]\n", commentList[i]);
14047 /* This isn't really the old style, but it's close enough */
14048 if (gameInfo.resultDetails != NULL &&
14049 gameInfo.resultDetails[0] != NULLCHAR) {
14050 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14051 gameInfo.resultDetails);
14053 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14060 /* Save the current game to open file f and close the file */
14062 SaveGame (FILE *f, int dummy, char *dummy2)
14064 if (gameMode == EditPosition) EditPositionDone(TRUE);
14065 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14066 if (appData.oldSaveStyle)
14067 return SaveGameOldStyle(f);
14069 return SaveGamePGN(f);
14072 /* Save the current position to the given file */
14074 SavePositionToFile (char *filename)
14079 if (strcmp(filename, "-") == 0) {
14080 return SavePosition(stdout, 0, NULL);
14082 f = fopen(filename, "a");
14084 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14085 DisplayError(buf, errno);
14088 safeStrCpy(buf, lastMsg, MSG_SIZ);
14089 DisplayMessage(_("Waiting for access to save file"), "");
14090 flock(fileno(f), LOCK_EX); // [HGM] lock
14091 DisplayMessage(_("Saving position"), "");
14092 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
14093 SavePosition(f, 0, NULL);
14094 DisplayMessage(buf, "");
14100 /* Save the current position to the given open file and close the file */
14102 SavePosition (FILE *f, int dummy, char *dummy2)
14107 if (gameMode == EditPosition) EditPositionDone(TRUE);
14108 if (appData.oldSaveStyle) {
14109 tm = time((time_t *) NULL);
14111 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14113 fprintf(f, "[--------------\n");
14114 PrintPosition(f, currentMove);
14115 fprintf(f, "--------------]\n");
14117 fen = PositionToFEN(currentMove, NULL, 1);
14118 fprintf(f, "%s\n", fen);
14126 ReloadCmailMsgEvent (int unregister)
14129 static char *inFilename = NULL;
14130 static char *outFilename;
14132 struct stat inbuf, outbuf;
14135 /* Any registered moves are unregistered if unregister is set, */
14136 /* i.e. invoked by the signal handler */
14138 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14139 cmailMoveRegistered[i] = FALSE;
14140 if (cmailCommentList[i] != NULL) {
14141 free(cmailCommentList[i]);
14142 cmailCommentList[i] = NULL;
14145 nCmailMovesRegistered = 0;
14148 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14149 cmailResult[i] = CMAIL_NOT_RESULT;
14153 if (inFilename == NULL) {
14154 /* Because the filenames are static they only get malloced once */
14155 /* and they never get freed */
14156 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14157 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14159 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14160 sprintf(outFilename, "%s.out", appData.cmailGameName);
14163 status = stat(outFilename, &outbuf);
14165 cmailMailedMove = FALSE;
14167 status = stat(inFilename, &inbuf);
14168 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14171 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14172 counts the games, notes how each one terminated, etc.
14174 It would be nice to remove this kludge and instead gather all
14175 the information while building the game list. (And to keep it
14176 in the game list nodes instead of having a bunch of fixed-size
14177 parallel arrays.) Note this will require getting each game's
14178 termination from the PGN tags, as the game list builder does
14179 not process the game moves. --mann
14181 cmailMsgLoaded = TRUE;
14182 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14184 /* Load first game in the file or popup game menu */
14185 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14187 #endif /* !WIN32 */
14195 char string[MSG_SIZ];
14197 if ( cmailMailedMove
14198 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14199 return TRUE; /* Allow free viewing */
14202 /* Unregister move to ensure that we don't leave RegisterMove */
14203 /* with the move registered when the conditions for registering no */
14205 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14206 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14207 nCmailMovesRegistered --;
14209 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14211 free(cmailCommentList[lastLoadGameNumber - 1]);
14212 cmailCommentList[lastLoadGameNumber - 1] = NULL;
14216 if (cmailOldMove == -1) {
14217 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14221 if (currentMove > cmailOldMove + 1) {
14222 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14226 if (currentMove < cmailOldMove) {
14227 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14231 if (forwardMostMove > currentMove) {
14232 /* Silently truncate extra moves */
14236 if ( (currentMove == cmailOldMove + 1)
14237 || ( (currentMove == cmailOldMove)
14238 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14239 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14240 if (gameInfo.result != GameUnfinished) {
14241 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14244 if (commentList[currentMove] != NULL) {
14245 cmailCommentList[lastLoadGameNumber - 1]
14246 = StrSave(commentList[currentMove]);
14248 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14250 if (appData.debugMode)
14251 fprintf(debugFP, "Saving %s for game %d\n",
14252 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14254 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14256 f = fopen(string, "w");
14257 if (appData.oldSaveStyle) {
14258 SaveGameOldStyle(f); /* also closes the file */
14260 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14261 f = fopen(string, "w");
14262 SavePosition(f, 0, NULL); /* also closes the file */
14264 fprintf(f, "{--------------\n");
14265 PrintPosition(f, currentMove);
14266 fprintf(f, "--------------}\n\n");
14268 SaveGame(f, 0, NULL); /* also closes the file*/
14271 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14272 nCmailMovesRegistered ++;
14273 } else if (nCmailGames == 1) {
14274 DisplayError(_("You have not made a move yet"), 0);
14285 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14286 FILE *commandOutput;
14287 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14288 int nBytes = 0; /* Suppress warnings on uninitialized variables */
14294 if (! cmailMsgLoaded) {
14295 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14299 if (nCmailGames == nCmailResults) {
14300 DisplayError(_("No unfinished games"), 0);
14304 #if CMAIL_PROHIBIT_REMAIL
14305 if (cmailMailedMove) {
14306 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);
14307 DisplayError(msg, 0);
14312 if (! (cmailMailedMove || RegisterMove())) return;
14314 if ( cmailMailedMove
14315 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14316 snprintf(string, MSG_SIZ, partCommandString,
14317 appData.debugMode ? " -v" : "", appData.cmailGameName);
14318 commandOutput = popen(string, "r");
14320 if (commandOutput == NULL) {
14321 DisplayError(_("Failed to invoke cmail"), 0);
14323 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14324 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14326 if (nBuffers > 1) {
14327 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14328 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14329 nBytes = MSG_SIZ - 1;
14331 (void) memcpy(msg, buffer, nBytes);
14333 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14335 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14336 cmailMailedMove = TRUE; /* Prevent >1 moves */
14339 for (i = 0; i < nCmailGames; i ++) {
14340 if (cmailResult[i] == CMAIL_NOT_RESULT) {
14345 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14347 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14349 appData.cmailGameName,
14351 LoadGameFromFile(buffer, 1, buffer, FALSE);
14352 cmailMsgLoaded = FALSE;
14356 DisplayInformation(msg);
14357 pclose(commandOutput);
14360 if ((*cmailMsg) != '\0') {
14361 DisplayInformation(cmailMsg);
14366 #endif /* !WIN32 */
14375 int prependComma = 0;
14377 char string[MSG_SIZ]; /* Space for game-list */
14380 if (!cmailMsgLoaded) return "";
14382 if (cmailMailedMove) {
14383 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14385 /* Create a list of games left */
14386 snprintf(string, MSG_SIZ, "[");
14387 for (i = 0; i < nCmailGames; i ++) {
14388 if (! ( cmailMoveRegistered[i]
14389 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14390 if (prependComma) {
14391 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14393 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14397 strcat(string, number);
14400 strcat(string, "]");
14402 if (nCmailMovesRegistered + nCmailResults == 0) {
14403 switch (nCmailGames) {
14405 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14409 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14413 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14418 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14420 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14425 if (nCmailResults == nCmailGames) {
14426 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14428 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14433 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14445 if (gameMode == Training)
14446 SetTrainingModeOff();
14449 cmailMsgLoaded = FALSE;
14450 if (appData.icsActive) {
14451 SendToICS(ics_prefix);
14452 SendToICS("refresh\n");
14457 ExitEvent (int status)
14461 /* Give up on clean exit */
14465 /* Keep trying for clean exit */
14469 if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14470 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14472 if (telnetISR != NULL) {
14473 RemoveInputSource(telnetISR);
14475 if (icsPR != NoProc) {
14476 DestroyChildProcess(icsPR, TRUE);
14479 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14480 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14482 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14483 /* make sure this other one finishes before killing it! */
14484 if(endingGame) { int count = 0;
14485 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14486 while(endingGame && count++ < 10) DoSleep(1);
14487 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14490 /* Kill off chess programs */
14491 if (first.pr != NoProc) {
14494 DoSleep( appData.delayBeforeQuit );
14495 SendToProgram("quit\n", &first);
14496 DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14498 if (second.pr != NoProc) {
14499 DoSleep( appData.delayBeforeQuit );
14500 SendToProgram("quit\n", &second);
14501 DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14503 if (first.isr != NULL) {
14504 RemoveInputSource(first.isr);
14506 if (second.isr != NULL) {
14507 RemoveInputSource(second.isr);
14510 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14511 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14513 ShutDownFrontEnd();
14518 PauseEngine (ChessProgramState *cps)
14520 SendToProgram("pause\n", cps);
14525 UnPauseEngine (ChessProgramState *cps)
14527 SendToProgram("resume\n", cps);
14534 if (appData.debugMode)
14535 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14539 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14541 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14542 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14543 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14545 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14546 HandleMachineMove(stashedInputMove, stalledEngine);
14547 stalledEngine = NULL;
14550 if (gameMode == MachinePlaysWhite ||
14551 gameMode == TwoMachinesPlay ||
14552 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14553 if(first.pause) UnPauseEngine(&first);
14554 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14555 if(second.pause) UnPauseEngine(&second);
14556 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14559 DisplayBothClocks();
14561 if (gameMode == PlayFromGameFile) {
14562 if (appData.timeDelay >= 0)
14563 AutoPlayGameLoop();
14564 } else if (gameMode == IcsExamining && pauseExamInvalid) {
14565 Reset(FALSE, TRUE);
14566 SendToICS(ics_prefix);
14567 SendToICS("refresh\n");
14568 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14569 ForwardInner(forwardMostMove);
14571 pauseExamInvalid = FALSE;
14573 switch (gameMode) {
14577 pauseExamForwardMostMove = forwardMostMove;
14578 pauseExamInvalid = FALSE;
14581 case IcsPlayingWhite:
14582 case IcsPlayingBlack:
14586 case PlayFromGameFile:
14587 (void) StopLoadGameTimer();
14591 case BeginningOfGame:
14592 if (appData.icsActive) return;
14593 /* else fall through */
14594 case MachinePlaysWhite:
14595 case MachinePlaysBlack:
14596 case TwoMachinesPlay:
14597 if (forwardMostMove == 0)
14598 return; /* don't pause if no one has moved */
14599 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14600 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14601 if(onMove->pause) { // thinking engine can be paused
14602 PauseEngine(onMove); // do it
14603 if(onMove->other->pause) // pondering opponent can always be paused immediately
14604 PauseEngine(onMove->other);
14606 SendToProgram("easy\n", onMove->other);
14608 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14609 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14611 PauseEngine(&first);
14613 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14614 } else { // human on move, pause pondering by either method
14616 PauseEngine(&first);
14617 else if(appData.ponderNextMove)
14618 SendToProgram("easy\n", &first);
14621 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14631 EditCommentEvent ()
14633 char title[MSG_SIZ];
14635 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14636 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14638 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14639 WhiteOnMove(currentMove - 1) ? " " : ".. ",
14640 parseList[currentMove - 1]);
14643 EditCommentPopUp(currentMove, title, commentList[currentMove]);
14650 char *tags = PGNTags(&gameInfo);
14652 EditTagsPopUp(tags, NULL);
14659 if(second.analyzing) {
14660 SendToProgram("exit\n", &second);
14661 second.analyzing = FALSE;
14663 if (second.pr == NoProc) StartChessProgram(&second);
14664 InitChessProgram(&second, FALSE);
14665 FeedMovesToProgram(&second, currentMove);
14667 SendToProgram("analyze\n", &second);
14668 second.analyzing = TRUE;
14672 /* Toggle ShowThinking */
14674 ToggleShowThinking()
14676 appData.showThinking = !appData.showThinking;
14677 ShowThinkingEvent();
14681 AnalyzeModeEvent ()
14685 if (!first.analysisSupport) {
14686 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14687 DisplayError(buf, 0);
14690 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14691 if (appData.icsActive) {
14692 if (gameMode != IcsObserving) {
14693 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14694 DisplayError(buf, 0);
14696 if (appData.icsEngineAnalyze) {
14697 if (appData.debugMode)
14698 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14704 /* if enable, user wants to disable icsEngineAnalyze */
14705 if (appData.icsEngineAnalyze) {
14710 appData.icsEngineAnalyze = TRUE;
14711 if (appData.debugMode)
14712 fprintf(debugFP, "ICS engine analyze starting... \n");
14715 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14716 if (appData.noChessProgram || gameMode == AnalyzeMode)
14719 if (gameMode != AnalyzeFile) {
14720 if (!appData.icsEngineAnalyze) {
14722 if (gameMode != EditGame) return 0;
14724 if (!appData.showThinking) ToggleShowThinking();
14725 ResurrectChessProgram();
14726 SendToProgram("analyze\n", &first);
14727 first.analyzing = TRUE;
14728 /*first.maybeThinking = TRUE;*/
14729 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14730 EngineOutputPopUp();
14732 if (!appData.icsEngineAnalyze) {
14733 gameMode = AnalyzeMode;
14734 ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14740 StartAnalysisClock();
14741 GetTimeMark(&lastNodeCountTime);
14747 AnalyzeFileEvent ()
14749 if (appData.noChessProgram || gameMode == AnalyzeFile)
14752 if (!first.analysisSupport) {
14754 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14755 DisplayError(buf, 0);
14759 if (gameMode != AnalyzeMode) {
14760 keepInfo = 1; // mere annotating should not alter PGN tags
14763 if (gameMode != EditGame) return;
14764 if (!appData.showThinking) ToggleShowThinking();
14765 ResurrectChessProgram();
14766 SendToProgram("analyze\n", &first);
14767 first.analyzing = TRUE;
14768 /*first.maybeThinking = TRUE;*/
14769 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14770 EngineOutputPopUp();
14772 gameMode = AnalyzeFile;
14776 StartAnalysisClock();
14777 GetTimeMark(&lastNodeCountTime);
14779 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14780 AnalysisPeriodicEvent(1);
14784 MachineWhiteEvent ()
14787 char *bookHit = NULL;
14789 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14793 if (gameMode == PlayFromGameFile ||
14794 gameMode == TwoMachinesPlay ||
14795 gameMode == Training ||
14796 gameMode == AnalyzeMode ||
14797 gameMode == EndOfGame)
14800 if (gameMode == EditPosition)
14801 EditPositionDone(TRUE);
14803 if (!WhiteOnMove(currentMove)) {
14804 DisplayError(_("It is not White's turn"), 0);
14808 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14811 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14812 gameMode == AnalyzeFile)
14815 ResurrectChessProgram(); /* in case it isn't running */
14816 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14817 gameMode = MachinePlaysWhite;
14820 gameMode = MachinePlaysWhite;
14824 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14826 if (first.sendName) {
14827 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14828 SendToProgram(buf, &first);
14830 if (first.sendTime) {
14831 if (first.useColors) {
14832 SendToProgram("black\n", &first); /*gnu kludge*/
14834 SendTimeRemaining(&first, TRUE);
14836 if (first.useColors) {
14837 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14839 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14840 SetMachineThinkingEnables();
14841 first.maybeThinking = TRUE;
14845 if (appData.autoFlipView && !flipView) {
14846 flipView = !flipView;
14847 DrawPosition(FALSE, NULL);
14848 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14851 if(bookHit) { // [HGM] book: simulate book reply
14852 static char bookMove[MSG_SIZ]; // a bit generous?
14854 programStats.nodes = programStats.depth = programStats.time =
14855 programStats.score = programStats.got_only_move = 0;
14856 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14858 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14859 strcat(bookMove, bookHit);
14860 HandleMachineMove(bookMove, &first);
14865 MachineBlackEvent ()
14868 char *bookHit = NULL;
14870 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14874 if (gameMode == PlayFromGameFile ||
14875 gameMode == TwoMachinesPlay ||
14876 gameMode == Training ||
14877 gameMode == AnalyzeMode ||
14878 gameMode == EndOfGame)
14881 if (gameMode == EditPosition)
14882 EditPositionDone(TRUE);
14884 if (WhiteOnMove(currentMove)) {
14885 DisplayError(_("It is not Black's turn"), 0);
14889 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14892 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14893 gameMode == AnalyzeFile)
14896 ResurrectChessProgram(); /* in case it isn't running */
14897 gameMode = MachinePlaysBlack;
14901 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14903 if (first.sendName) {
14904 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14905 SendToProgram(buf, &first);
14907 if (first.sendTime) {
14908 if (first.useColors) {
14909 SendToProgram("white\n", &first); /*gnu kludge*/
14911 SendTimeRemaining(&first, FALSE);
14913 if (first.useColors) {
14914 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14916 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14917 SetMachineThinkingEnables();
14918 first.maybeThinking = TRUE;
14921 if (appData.autoFlipView && flipView) {
14922 flipView = !flipView;
14923 DrawPosition(FALSE, NULL);
14924 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14926 if(bookHit) { // [HGM] book: simulate book reply
14927 static char bookMove[MSG_SIZ]; // a bit generous?
14929 programStats.nodes = programStats.depth = programStats.time =
14930 programStats.score = programStats.got_only_move = 0;
14931 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14933 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14934 strcat(bookMove, bookHit);
14935 HandleMachineMove(bookMove, &first);
14941 DisplayTwoMachinesTitle ()
14944 if (appData.matchGames > 0) {
14945 if(appData.tourneyFile[0]) {
14946 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14947 gameInfo.white, _("vs."), gameInfo.black,
14948 nextGame+1, appData.matchGames+1,
14949 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14951 if (first.twoMachinesColor[0] == 'w') {
14952 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14953 gameInfo.white, _("vs."), gameInfo.black,
14954 first.matchWins, second.matchWins,
14955 matchGame - 1 - (first.matchWins + second.matchWins));
14957 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14958 gameInfo.white, _("vs."), gameInfo.black,
14959 second.matchWins, first.matchWins,
14960 matchGame - 1 - (first.matchWins + second.matchWins));
14963 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14969 SettingsMenuIfReady ()
14971 if (second.lastPing != second.lastPong) {
14972 DisplayMessage("", _("Waiting for second chess program"));
14973 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14977 DisplayMessage("", "");
14978 SettingsPopUp(&second);
14982 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14985 if (cps->pr == NoProc) {
14986 StartChessProgram(cps);
14987 if (cps->protocolVersion == 1) {
14989 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14991 /* kludge: allow timeout for initial "feature" command */
14992 if(retry != TwoMachinesEventIfReady) FreezeUI();
14993 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14994 DisplayMessage("", buf);
14995 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
15003 TwoMachinesEvent P((void))
15007 ChessProgramState *onmove;
15008 char *bookHit = NULL;
15009 static int stalling = 0;
15013 if (appData.noChessProgram) return;
15015 switch (gameMode) {
15016 case TwoMachinesPlay:
15018 case MachinePlaysWhite:
15019 case MachinePlaysBlack:
15020 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15021 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15025 case BeginningOfGame:
15026 case PlayFromGameFile:
15029 if (gameMode != EditGame) return;
15032 EditPositionDone(TRUE);
15043 // forwardMostMove = currentMove;
15044 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15045 startingEngine = TRUE;
15047 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15049 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15050 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15051 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15055 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15057 if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15058 gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15059 startingEngine = matchMode = FALSE;
15060 DisplayError("second engine does not play this", 0);
15061 gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15062 EditGameEvent(); // switch back to EditGame mode
15067 InitChessProgram(&second, FALSE); // unbalances ping of second engine
15068 SendToProgram("force\n", &second);
15070 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15074 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15075 if(appData.matchPause>10000 || appData.matchPause<10)
15076 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15077 wait = SubtractTimeMarks(&now, &pauseStart);
15078 if(wait < appData.matchPause) {
15079 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15082 // we are now committed to starting the game
15084 DisplayMessage("", "");
15086 if (startedFromSetupPosition) {
15087 SendBoard(&second, backwardMostMove);
15088 if (appData.debugMode) {
15089 fprintf(debugFP, "Two Machines\n");
15092 for (i = backwardMostMove; i < forwardMostMove; i++) {
15093 SendMoveToProgram(i, &second);
15097 gameMode = TwoMachinesPlay;
15098 pausing = startingEngine = FALSE;
15099 ModeHighlight(); // [HGM] logo: this triggers display update of logos
15101 DisplayTwoMachinesTitle();
15103 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15108 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15109 SendToProgram(first.computerString, &first);
15110 if (first.sendName) {
15111 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15112 SendToProgram(buf, &first);
15115 SendToProgram(second.computerString, &second);
15116 if (second.sendName) {
15117 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15118 SendToProgram(buf, &second);
15123 if (!first.sendTime || !second.sendTime) {
15124 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15125 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15127 if (onmove->sendTime) {
15128 if (onmove->useColors) {
15129 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15131 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15133 if (onmove->useColors) {
15134 SendToProgram(onmove->twoMachinesColor, onmove);
15136 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15137 // SendToProgram("go\n", onmove);
15138 onmove->maybeThinking = TRUE;
15139 SetMachineThinkingEnables();
15143 if(bookHit) { // [HGM] book: simulate book reply
15144 static char bookMove[MSG_SIZ]; // a bit generous?
15146 programStats.nodes = programStats.depth = programStats.time =
15147 programStats.score = programStats.got_only_move = 0;
15148 sprintf(programStats.movelist, "%s (xbook)", bookHit);
15150 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15151 strcat(bookMove, bookHit);
15152 savedMessage = bookMove; // args for deferred call
15153 savedState = onmove;
15154 ScheduleDelayedEvent(DeferredBookMove, 1);
15161 if (gameMode == Training) {
15162 SetTrainingModeOff();
15163 gameMode = PlayFromGameFile;
15164 DisplayMessage("", _("Training mode off"));
15166 gameMode = Training;
15167 animateTraining = appData.animate;
15169 /* make sure we are not already at the end of the game */
15170 if (currentMove < forwardMostMove) {
15171 SetTrainingModeOn();
15172 DisplayMessage("", _("Training mode on"));
15174 gameMode = PlayFromGameFile;
15175 DisplayError(_("Already at end of game"), 0);
15184 if (!appData.icsActive) return;
15185 switch (gameMode) {
15186 case IcsPlayingWhite:
15187 case IcsPlayingBlack:
15190 case BeginningOfGame:
15198 EditPositionDone(TRUE);
15211 gameMode = IcsIdle;
15221 switch (gameMode) {
15223 SetTrainingModeOff();
15225 case MachinePlaysWhite:
15226 case MachinePlaysBlack:
15227 case BeginningOfGame:
15228 SendToProgram("force\n", &first);
15229 if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15230 if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15232 abortEngineThink = TRUE;
15233 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15234 SendToProgram(buf, &first);
15235 DisplayMessage("Aborting engine think", "");
15239 SetUserThinkingEnables();
15241 case PlayFromGameFile:
15242 (void) StopLoadGameTimer();
15243 if (gameFileFP != NULL) {
15248 EditPositionDone(TRUE);
15253 SendToProgram("force\n", &first);
15255 case TwoMachinesPlay:
15256 GameEnds(EndOfFile, NULL, GE_PLAYER);
15257 ResurrectChessProgram();
15258 SetUserThinkingEnables();
15261 ResurrectChessProgram();
15263 case IcsPlayingBlack:
15264 case IcsPlayingWhite:
15265 DisplayError(_("Warning: You are still playing a game"), 0);
15268 DisplayError(_("Warning: You are still observing a game"), 0);
15271 DisplayError(_("Warning: You are still examining a game"), 0);
15282 first.offeredDraw = second.offeredDraw = 0;
15284 if (gameMode == PlayFromGameFile) {
15285 whiteTimeRemaining = timeRemaining[0][currentMove];
15286 blackTimeRemaining = timeRemaining[1][currentMove];
15290 if (gameMode == MachinePlaysWhite ||
15291 gameMode == MachinePlaysBlack ||
15292 gameMode == TwoMachinesPlay ||
15293 gameMode == EndOfGame) {
15294 i = forwardMostMove;
15295 while (i > currentMove) {
15296 SendToProgram("undo\n", &first);
15299 if(!adjustedClock) {
15300 whiteTimeRemaining = timeRemaining[0][currentMove];
15301 blackTimeRemaining = timeRemaining[1][currentMove];
15302 DisplayBothClocks();
15304 if (whiteFlag || blackFlag) {
15305 whiteFlag = blackFlag = 0;
15310 gameMode = EditGame;
15316 EditPositionEvent ()
15319 if (gameMode == EditPosition) {
15325 if (gameMode != EditGame) return;
15327 gameMode = EditPosition;
15330 CopyBoard(rightsBoard, nullBoard);
15331 if (currentMove > 0)
15332 CopyBoard(boards[0], boards[currentMove]);
15333 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15334 rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15336 blackPlaysFirst = !WhiteOnMove(currentMove);
15338 currentMove = forwardMostMove = backwardMostMove = 0;
15339 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15341 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15347 /* [DM] icsEngineAnalyze - possible call from other functions */
15348 if (appData.icsEngineAnalyze) {
15349 appData.icsEngineAnalyze = FALSE;
15351 DisplayMessage("",_("Close ICS engine analyze..."));
15353 if (first.analysisSupport && first.analyzing) {
15354 SendToBoth("exit\n");
15355 first.analyzing = second.analyzing = FALSE;
15357 thinkOutput[0] = NULLCHAR;
15361 EditPositionDone (Boolean fakeRights)
15363 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15365 startedFromSetupPosition = TRUE;
15366 InitChessProgram(&first, FALSE);
15367 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15369 boards[0][EP_STATUS] = EP_NONE;
15370 for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
15371 for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
15372 if(rightsBoard[r][f]) {
15373 ChessSquare p = boards[0][r][f];
15374 if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
15375 else if(p == king) boards[0][CASTLING][2] = f;
15376 else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
15377 else rightsBoard[r][f] = 2; // mark for second pass
15380 for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
15381 if(rightsBoard[r][f] == 2) {
15382 ChessSquare p = boards[0][r][f];
15383 if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
15384 if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
15388 SendToProgram("force\n", &first);
15389 if (blackPlaysFirst) {
15390 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15391 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15392 currentMove = forwardMostMove = backwardMostMove = 1;
15393 CopyBoard(boards[1], boards[0]);
15395 currentMove = forwardMostMove = backwardMostMove = 0;
15397 SendBoard(&first, forwardMostMove);
15398 if (appData.debugMode) {
15399 fprintf(debugFP, "EditPosDone\n");
15402 DisplayMessage("", "");
15403 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15404 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15405 gameMode = EditGame;
15407 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15408 ClearHighlights(); /* [AS] */
15411 /* Pause for `ms' milliseconds */
15412 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15414 TimeDelay (long ms)
15421 } while (SubtractTimeMarks(&m2, &m1) < ms);
15424 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15426 SendMultiLineToICS (char *buf)
15428 char temp[MSG_SIZ+1], *p;
15435 strncpy(temp, buf, len);
15440 if (*p == '\n' || *p == '\r')
15445 strcat(temp, "\n");
15447 SendToPlayer(temp, strlen(temp));
15451 SetWhiteToPlayEvent ()
15453 if (gameMode == EditPosition) {
15454 blackPlaysFirst = FALSE;
15455 DisplayBothClocks(); /* works because currentMove is 0 */
15456 } else if (gameMode == IcsExamining) {
15457 SendToICS(ics_prefix);
15458 SendToICS("tomove white\n");
15463 SetBlackToPlayEvent ()
15465 if (gameMode == EditPosition) {
15466 blackPlaysFirst = TRUE;
15467 currentMove = 1; /* kludge */
15468 DisplayBothClocks();
15470 } else if (gameMode == IcsExamining) {
15471 SendToICS(ics_prefix);
15472 SendToICS("tomove black\n");
15477 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15480 ChessSquare piece = boards[0][y][x];
15481 static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15482 static int lastVariant;
15483 int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15485 if (gameMode != EditPosition && gameMode != IcsExamining) return;
15487 switch (selection) {
15489 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15490 MarkTargetSquares(1);
15491 CopyBoard(currentBoard, boards[0]);
15492 CopyBoard(menuBoard, initialPosition);
15493 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15494 SendToICS(ics_prefix);
15495 SendToICS("bsetup clear\n");
15496 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15497 SendToICS(ics_prefix);
15498 SendToICS("clearboard\n");
15501 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15502 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15503 for (y = 0; y < BOARD_HEIGHT; y++) {
15504 if (gameMode == IcsExamining) {
15505 if (boards[currentMove][y][x] != EmptySquare) {
15506 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15510 } else if(boards[0][y][x] != DarkSquare) {
15511 if(boards[0][y][x] != p) nonEmpty++;
15512 boards[0][y][x] = p;
15516 CopyBoard(rightsBoard, nullBoard);
15517 if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15519 for(r = 0; r < BOARD_HEIGHT; r++) {
15520 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
15521 ChessSquare p = menuBoard[r][x];
15522 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15525 menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
15526 DisplayMessage("Clicking clock again restores position", "");
15527 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15528 if(!nonEmpty) { // asked to clear an empty board
15529 CopyBoard(boards[0], menuBoard);
15531 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15532 CopyBoard(boards[0], initialPosition);
15534 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15535 && !CompareBoards(nullBoard, erasedBoard)) {
15536 CopyBoard(boards[0], erasedBoard);
15538 CopyBoard(erasedBoard, currentBoard);
15540 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15541 rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15544 if (gameMode == EditPosition) {
15545 DrawPosition(FALSE, boards[0]);
15550 SetWhiteToPlayEvent();
15554 SetBlackToPlayEvent();
15558 if (gameMode == IcsExamining) {
15559 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15560 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15563 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15564 if(x == BOARD_LEFT-2) {
15565 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15566 boards[0][y][1] = 0;
15568 if(x == BOARD_RGHT+1) {
15569 if(y >= gameInfo.holdingsSize) break;
15570 boards[0][y][BOARD_WIDTH-2] = 0;
15573 boards[0][y][x] = EmptySquare;
15574 DrawPosition(FALSE, boards[0]);
15579 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15580 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
15581 selection = (ChessSquare) (PROMOTED(piece));
15582 } else if(piece == EmptySquare) selection = WhiteSilver;
15583 else selection = (ChessSquare)((int)piece - 1);
15587 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15588 piece > (int)BlackMan && piece <= (int)BlackKing ) {
15589 selection = (ChessSquare) (DEMOTED(piece));
15590 } else if(piece == EmptySquare) selection = BlackSilver;
15591 else selection = (ChessSquare)((int)piece + 1);
15596 if(gameInfo.variant == VariantShatranj ||
15597 gameInfo.variant == VariantXiangqi ||
15598 gameInfo.variant == VariantCourier ||
15599 gameInfo.variant == VariantASEAN ||
15600 gameInfo.variant == VariantMakruk )
15601 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15607 if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15608 if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15614 if(gameInfo.variant == VariantXiangqi)
15615 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15616 if(gameInfo.variant == VariantKnightmate)
15617 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15618 if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15621 if (gameMode == IcsExamining) {
15622 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15623 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15624 PieceToChar(selection), AAA + x, ONE + y);
15627 rightsBoard[y][x] = hasRights;
15628 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15630 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15631 n = PieceToNumber(selection - BlackPawn);
15632 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15633 boards[0][BOARD_HEIGHT-1-n][0] = selection;
15634 boards[0][BOARD_HEIGHT-1-n][1]++;
15636 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15637 n = PieceToNumber(selection);
15638 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15639 boards[0][n][BOARD_WIDTH-1] = selection;
15640 boards[0][n][BOARD_WIDTH-2]++;
15643 boards[0][y][x] = selection;
15644 DrawPosition(TRUE, boards[0]);
15646 fromX = fromY = -1;
15654 DropMenuEvent (ChessSquare selection, int x, int y)
15656 ChessMove moveType;
15658 switch (gameMode) {
15659 case IcsPlayingWhite:
15660 case MachinePlaysBlack:
15661 if (!WhiteOnMove(currentMove)) {
15662 DisplayMoveError(_("It is Black's turn"));
15665 moveType = WhiteDrop;
15667 case IcsPlayingBlack:
15668 case MachinePlaysWhite:
15669 if (WhiteOnMove(currentMove)) {
15670 DisplayMoveError(_("It is White's turn"));
15673 moveType = BlackDrop;
15676 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15682 if (moveType == BlackDrop && selection < BlackPawn) {
15683 selection = (ChessSquare) ((int) selection
15684 + (int) BlackPawn - (int) WhitePawn);
15686 if (boards[currentMove][y][x] != EmptySquare) {
15687 DisplayMoveError(_("That square is occupied"));
15691 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15697 /* Accept a pending offer of any kind from opponent */
15699 if (appData.icsActive) {
15700 SendToICS(ics_prefix);
15701 SendToICS("accept\n");
15702 } else if (cmailMsgLoaded) {
15703 if (currentMove == cmailOldMove &&
15704 commentList[cmailOldMove] != NULL &&
15705 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15706 "Black offers a draw" : "White offers a draw")) {
15708 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15709 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15711 DisplayError(_("There is no pending offer on this move"), 0);
15712 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15715 /* Not used for offers from chess program */
15722 /* Decline a pending offer of any kind from opponent */
15724 if (appData.icsActive) {
15725 SendToICS(ics_prefix);
15726 SendToICS("decline\n");
15727 } else if (cmailMsgLoaded) {
15728 if (currentMove == cmailOldMove &&
15729 commentList[cmailOldMove] != NULL &&
15730 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15731 "Black offers a draw" : "White offers a draw")) {
15733 AppendComment(cmailOldMove, "Draw declined", TRUE);
15734 DisplayComment(cmailOldMove - 1, "Draw declined");
15737 DisplayError(_("There is no pending offer on this move"), 0);
15740 /* Not used for offers from chess program */
15747 /* Issue ICS rematch command */
15748 if (appData.icsActive) {
15749 SendToICS(ics_prefix);
15750 SendToICS("rematch\n");
15757 /* Call your opponent's flag (claim a win on time) */
15758 if (appData.icsActive) {
15759 SendToICS(ics_prefix);
15760 SendToICS("flag\n");
15762 switch (gameMode) {
15765 case MachinePlaysWhite:
15768 GameEnds(GameIsDrawn, "Both players ran out of time",
15771 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15773 DisplayError(_("Your opponent is not out of time"), 0);
15776 case MachinePlaysBlack:
15779 GameEnds(GameIsDrawn, "Both players ran out of time",
15782 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15784 DisplayError(_("Your opponent is not out of time"), 0);
15792 ClockClick (int which)
15793 { // [HGM] code moved to back-end from winboard.c
15794 if(which) { // black clock
15795 if (gameMode == EditPosition || gameMode == IcsExamining) {
15796 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15797 SetBlackToPlayEvent();
15798 } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15799 gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15800 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15801 } else if (shiftKey) {
15802 AdjustClock(which, -1);
15803 } else if (gameMode == IcsPlayingWhite ||
15804 gameMode == MachinePlaysBlack) {
15807 } else { // white clock
15808 if (gameMode == EditPosition || gameMode == IcsExamining) {
15809 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15810 SetWhiteToPlayEvent();
15811 } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15812 gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15813 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15814 } else if (shiftKey) {
15815 AdjustClock(which, -1);
15816 } else if (gameMode == IcsPlayingBlack ||
15817 gameMode == MachinePlaysWhite) {
15826 /* Offer draw or accept pending draw offer from opponent */
15828 if (appData.icsActive) {
15829 /* Note: tournament rules require draw offers to be
15830 made after you make your move but before you punch
15831 your clock. Currently ICS doesn't let you do that;
15832 instead, you immediately punch your clock after making
15833 a move, but you can offer a draw at any time. */
15835 SendToICS(ics_prefix);
15836 SendToICS("draw\n");
15837 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15838 } else if (cmailMsgLoaded) {
15839 if (currentMove == cmailOldMove &&
15840 commentList[cmailOldMove] != NULL &&
15841 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15842 "Black offers a draw" : "White offers a draw")) {
15843 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15844 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15845 } else if (currentMove == cmailOldMove + 1) {
15846 char *offer = WhiteOnMove(cmailOldMove) ?
15847 "White offers a draw" : "Black offers a draw";
15848 AppendComment(currentMove, offer, TRUE);
15849 DisplayComment(currentMove - 1, offer);
15850 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15852 DisplayError(_("You must make your move before offering a draw"), 0);
15853 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15855 } else if (first.offeredDraw) {
15856 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15858 if (first.sendDrawOffers) {
15859 SendToProgram("draw\n", &first);
15860 userOfferedDraw = TRUE;
15868 /* Offer Adjourn or accept pending Adjourn offer from opponent */
15870 if (appData.icsActive) {
15871 SendToICS(ics_prefix);
15872 SendToICS("adjourn\n");
15874 /* Currently GNU Chess doesn't offer or accept Adjourns */
15882 /* Offer Abort or accept pending Abort offer from opponent */
15884 if (appData.icsActive) {
15885 SendToICS(ics_prefix);
15886 SendToICS("abort\n");
15888 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15895 /* Resign. You can do this even if it's not your turn. */
15897 if (appData.icsActive) {
15898 SendToICS(ics_prefix);
15899 SendToICS("resign\n");
15901 switch (gameMode) {
15902 case MachinePlaysWhite:
15903 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15905 case MachinePlaysBlack:
15906 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15909 if (cmailMsgLoaded) {
15911 if (WhiteOnMove(cmailOldMove)) {
15912 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15914 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15916 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15927 StopObservingEvent ()
15929 /* Stop observing current games */
15930 SendToICS(ics_prefix);
15931 SendToICS("unobserve\n");
15935 StopExaminingEvent ()
15937 /* Stop observing current game */
15938 SendToICS(ics_prefix);
15939 SendToICS("unexamine\n");
15943 ForwardInner (int target)
15945 int limit; int oldSeekGraphUp = seekGraphUp;
15947 if (appData.debugMode)
15948 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15949 target, currentMove, forwardMostMove);
15951 if (gameMode == EditPosition)
15954 seekGraphUp = FALSE;
15955 MarkTargetSquares(1);
15956 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15958 if (gameMode == PlayFromGameFile && !pausing)
15961 if (gameMode == IcsExamining && pausing)
15962 limit = pauseExamForwardMostMove;
15964 limit = forwardMostMove;
15966 if (target > limit) target = limit;
15968 if (target > 0 && moveList[target - 1][0]) {
15969 int fromX, fromY, toX, toY;
15970 toX = moveList[target - 1][2] - AAA;
15971 toY = moveList[target - 1][3] - ONE;
15972 if (moveList[target - 1][1] == '@') {
15973 if (appData.highlightLastMove) {
15974 SetHighlights(-1, -1, toX, toY);
15977 fromX = moveList[target - 1][0] - AAA;
15978 fromY = moveList[target - 1][1] - ONE;
15979 if (target == currentMove + 1) {
15980 if(moveList[target - 1][4] == ';') { // multi-leg
15981 killX = moveList[target - 1][5] - AAA;
15982 killY = moveList[target - 1][6] - ONE;
15984 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15985 killX = killY = -1;
15987 if (appData.highlightLastMove) {
15988 SetHighlights(fromX, fromY, toX, toY);
15992 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15993 gameMode == Training || gameMode == PlayFromGameFile ||
15994 gameMode == AnalyzeFile) {
15995 while (currentMove < target) {
15996 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15997 SendMoveToProgram(currentMove++, &first);
16000 currentMove = target;
16003 if (gameMode == EditGame || gameMode == EndOfGame) {
16004 whiteTimeRemaining = timeRemaining[0][currentMove];
16005 blackTimeRemaining = timeRemaining[1][currentMove];
16007 DisplayBothClocks();
16008 DisplayMove(currentMove - 1);
16009 DrawPosition(oldSeekGraphUp, boards[currentMove]);
16010 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16011 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
16012 DisplayComment(currentMove - 1, commentList[currentMove]);
16014 ClearMap(); // [HGM] exclude: invalidate map
16021 if (gameMode == IcsExamining && !pausing) {
16022 SendToICS(ics_prefix);
16023 SendToICS("forward\n");
16025 ForwardInner(currentMove + 1);
16032 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16033 /* to optimze, we temporarily turn off analysis mode while we feed
16034 * the remaining moves to the engine. Otherwise we get analysis output
16037 if (first.analysisSupport) {
16038 SendToProgram("exit\nforce\n", &first);
16039 first.analyzing = FALSE;
16043 if (gameMode == IcsExamining && !pausing) {
16044 SendToICS(ics_prefix);
16045 SendToICS("forward 999999\n");
16047 ForwardInner(forwardMostMove);
16050 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16051 /* we have fed all the moves, so reactivate analysis mode */
16052 SendToProgram("analyze\n", &first);
16053 first.analyzing = TRUE;
16054 /*first.maybeThinking = TRUE;*/
16055 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16060 BackwardInner (int target)
16062 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16064 if (appData.debugMode)
16065 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16066 target, currentMove, forwardMostMove);
16068 if (gameMode == EditPosition) return;
16069 seekGraphUp = FALSE;
16070 MarkTargetSquares(1);
16071 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16072 if (currentMove <= backwardMostMove) {
16074 DrawPosition(full_redraw, boards[currentMove]);
16077 if (gameMode == PlayFromGameFile && !pausing)
16080 if (moveList[target][0]) {
16081 int fromX, fromY, toX, toY;
16082 toX = moveList[target][2] - AAA;
16083 toY = moveList[target][3] - ONE;
16084 if (moveList[target][1] == '@') {
16085 if (appData.highlightLastMove) {
16086 SetHighlights(-1, -1, toX, toY);
16089 fromX = moveList[target][0] - AAA;
16090 fromY = moveList[target][1] - ONE;
16091 if (target == currentMove - 1) {
16092 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16094 if (appData.highlightLastMove) {
16095 SetHighlights(fromX, fromY, toX, toY);
16099 if (gameMode == EditGame || gameMode==AnalyzeMode ||
16100 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16101 while (currentMove > target) {
16102 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16103 // null move cannot be undone. Reload program with move history before it.
16105 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16106 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16108 SendBoard(&first, i);
16109 if(second.analyzing) SendBoard(&second, i);
16110 for(currentMove=i; currentMove<target; currentMove++) {
16111 SendMoveToProgram(currentMove, &first);
16112 if(second.analyzing) SendMoveToProgram(currentMove, &second);
16116 SendToBoth("undo\n");
16120 currentMove = target;
16123 if (gameMode == EditGame || gameMode == EndOfGame) {
16124 whiteTimeRemaining = timeRemaining[0][currentMove];
16125 blackTimeRemaining = timeRemaining[1][currentMove];
16127 DisplayBothClocks();
16128 DisplayMove(currentMove - 1);
16129 DrawPosition(full_redraw, boards[currentMove]);
16130 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16131 // [HGM] PV info: routine tests if comment empty
16132 DisplayComment(currentMove - 1, commentList[currentMove]);
16133 ClearMap(); // [HGM] exclude: invalidate map
16139 if (gameMode == IcsExamining && !pausing) {
16140 SendToICS(ics_prefix);
16141 SendToICS("backward\n");
16143 BackwardInner(currentMove - 1);
16150 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16151 /* to optimize, we temporarily turn off analysis mode while we undo
16152 * all the moves. Otherwise we get analysis output after each undo.
16154 if (first.analysisSupport) {
16155 SendToProgram("exit\nforce\n", &first);
16156 first.analyzing = FALSE;
16160 if (gameMode == IcsExamining && !pausing) {
16161 SendToICS(ics_prefix);
16162 SendToICS("backward 999999\n");
16164 BackwardInner(backwardMostMove);
16167 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16168 /* we have fed all the moves, so reactivate analysis mode */
16169 SendToProgram("analyze\n", &first);
16170 first.analyzing = TRUE;
16171 /*first.maybeThinking = TRUE;*/
16172 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16179 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16180 if (to >= forwardMostMove) to = forwardMostMove;
16181 if (to <= backwardMostMove) to = backwardMostMove;
16182 if (to < currentMove) {
16190 RevertEvent (Boolean annotate)
16192 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16195 if (gameMode != IcsExamining) {
16196 DisplayError(_("You are not examining a game"), 0);
16200 DisplayError(_("You can't revert while pausing"), 0);
16203 SendToICS(ics_prefix);
16204 SendToICS("revert\n");
16208 RetractMoveEvent ()
16210 switch (gameMode) {
16211 case MachinePlaysWhite:
16212 case MachinePlaysBlack:
16213 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16214 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16217 if (forwardMostMove < 2) return;
16218 currentMove = forwardMostMove = forwardMostMove - 2;
16219 whiteTimeRemaining = timeRemaining[0][currentMove];
16220 blackTimeRemaining = timeRemaining[1][currentMove];
16221 DisplayBothClocks();
16222 DisplayMove(currentMove - 1);
16223 ClearHighlights();/*!! could figure this out*/
16224 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16225 SendToProgram("remove\n", &first);
16226 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16229 case BeginningOfGame:
16233 case IcsPlayingWhite:
16234 case IcsPlayingBlack:
16235 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16236 SendToICS(ics_prefix);
16237 SendToICS("takeback 2\n");
16239 SendToICS(ics_prefix);
16240 SendToICS("takeback 1\n");
16249 ChessProgramState *cps;
16251 switch (gameMode) {
16252 case MachinePlaysWhite:
16253 if (!WhiteOnMove(forwardMostMove)) {
16254 DisplayError(_("It is your turn"), 0);
16259 case MachinePlaysBlack:
16260 if (WhiteOnMove(forwardMostMove)) {
16261 DisplayError(_("It is your turn"), 0);
16266 case TwoMachinesPlay:
16267 if (WhiteOnMove(forwardMostMove) ==
16268 (first.twoMachinesColor[0] == 'w')) {
16274 case BeginningOfGame:
16278 SendToProgram("?\n", cps);
16282 TruncateGameEvent ()
16285 if (gameMode != EditGame) return;
16292 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16293 if (forwardMostMove > currentMove) {
16294 if (gameInfo.resultDetails != NULL) {
16295 free(gameInfo.resultDetails);
16296 gameInfo.resultDetails = NULL;
16297 gameInfo.result = GameUnfinished;
16299 forwardMostMove = currentMove;
16300 HistorySet(parseList, backwardMostMove, forwardMostMove,
16308 if (appData.noChessProgram) return;
16309 switch (gameMode) {
16310 case MachinePlaysWhite:
16311 if (WhiteOnMove(forwardMostMove)) {
16312 DisplayError(_("Wait until your turn."), 0);
16316 case BeginningOfGame:
16317 case MachinePlaysBlack:
16318 if (!WhiteOnMove(forwardMostMove)) {
16319 DisplayError(_("Wait until your turn."), 0);
16324 DisplayError(_("No hint available"), 0);
16327 SendToProgram("hint\n", &first);
16328 hintRequested = TRUE;
16332 SaveSelected (FILE *g, int dummy, char *dummy2)
16334 ListGame * lg = (ListGame *) gameList.head;
16338 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16339 DisplayError(_("Game list not loaded or empty"), 0);
16343 creatingBook = TRUE; // suppresses stuff during load game
16345 /* Get list size */
16346 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16347 if(lg->position >= 0) { // selected?
16348 LoadGame(f, nItem, "", TRUE);
16349 SaveGamePGN2(g); // leaves g open
16352 lg = (ListGame *) lg->node.succ;
16356 creatingBook = FALSE;
16364 ListGame * lg = (ListGame *) gameList.head;
16367 static int secondTime = FALSE;
16369 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16370 DisplayError(_("Game list not loaded or empty"), 0);
16374 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16377 DisplayNote(_("Book file exists! Try again for overwrite."));
16381 creatingBook = TRUE;
16382 secondTime = FALSE;
16384 /* Get list size */
16385 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16386 if(lg->position >= 0) {
16387 LoadGame(f, nItem, "", TRUE);
16388 AddGameToBook(TRUE);
16391 lg = (ListGame *) lg->node.succ;
16394 creatingBook = FALSE;
16401 if (appData.noChessProgram) return;
16402 switch (gameMode) {
16403 case MachinePlaysWhite:
16404 if (WhiteOnMove(forwardMostMove)) {
16405 DisplayError(_("Wait until your turn."), 0);
16409 case BeginningOfGame:
16410 case MachinePlaysBlack:
16411 if (!WhiteOnMove(forwardMostMove)) {
16412 DisplayError(_("Wait until your turn."), 0);
16417 EditPositionDone(TRUE);
16419 case TwoMachinesPlay:
16424 SendToProgram("bk\n", &first);
16425 bookOutput[0] = NULLCHAR;
16426 bookRequested = TRUE;
16432 char *tags = PGNTags(&gameInfo);
16433 TagsPopUp(tags, CmailMsg());
16437 /* end button procedures */
16440 PrintPosition (FILE *fp, int move)
16444 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16445 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16446 char c = PieceToChar(boards[move][i][j]);
16447 fputc(c == '?' ? '.' : c, fp);
16448 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16451 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16452 fprintf(fp, "white to play\n");
16454 fprintf(fp, "black to play\n");
16458 PrintOpponents (FILE *fp)
16460 if (gameInfo.white != NULL) {
16461 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16467 /* Find last component of program's own name, using some heuristics */
16469 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16472 int local = (strcmp(host, "localhost") == 0);
16473 while (!local && (p = strchr(prog, ';')) != NULL) {
16475 while (*p == ' ') p++;
16478 if (*prog == '"' || *prog == '\'') {
16479 q = strchr(prog + 1, *prog);
16481 q = strchr(prog, ' ');
16483 if (q == NULL) q = prog + strlen(prog);
16485 while (p >= prog && *p != '/' && *p != '\\') p--;
16487 if(p == prog && *p == '"') p++;
16489 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16490 memcpy(buf, p, q - p);
16491 buf[q - p] = NULLCHAR;
16499 TimeControlTagValue ()
16502 if (!appData.clockMode) {
16503 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16504 } else if (movesPerSession > 0) {
16505 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16506 } else if (timeIncrement == 0) {
16507 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16509 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16511 return StrSave(buf);
16517 /* This routine is used only for certain modes */
16518 VariantClass v = gameInfo.variant;
16519 ChessMove r = GameUnfinished;
16522 if(keepInfo) return;
16524 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16525 r = gameInfo.result;
16526 p = gameInfo.resultDetails;
16527 gameInfo.resultDetails = NULL;
16529 ClearGameInfo(&gameInfo);
16530 gameInfo.variant = v;
16532 switch (gameMode) {
16533 case MachinePlaysWhite:
16534 gameInfo.event = StrSave( appData.pgnEventHeader );
16535 gameInfo.site = StrSave(HostName());
16536 gameInfo.date = PGNDate();
16537 gameInfo.round = StrSave("-");
16538 gameInfo.white = StrSave(first.tidy);
16539 gameInfo.black = StrSave(UserName());
16540 gameInfo.timeControl = TimeControlTagValue();
16543 case MachinePlaysBlack:
16544 gameInfo.event = StrSave( appData.pgnEventHeader );
16545 gameInfo.site = StrSave(HostName());
16546 gameInfo.date = PGNDate();
16547 gameInfo.round = StrSave("-");
16548 gameInfo.white = StrSave(UserName());
16549 gameInfo.black = StrSave(first.tidy);
16550 gameInfo.timeControl = TimeControlTagValue();
16553 case TwoMachinesPlay:
16554 gameInfo.event = StrSave( appData.pgnEventHeader );
16555 gameInfo.site = StrSave(HostName());
16556 gameInfo.date = PGNDate();
16559 snprintf(buf, MSG_SIZ, "%d", roundNr);
16560 gameInfo.round = StrSave(buf);
16562 gameInfo.round = StrSave("-");
16564 if (first.twoMachinesColor[0] == 'w') {
16565 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16566 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16568 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16569 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16571 gameInfo.timeControl = TimeControlTagValue();
16575 gameInfo.event = StrSave("Edited game");
16576 gameInfo.site = StrSave(HostName());
16577 gameInfo.date = PGNDate();
16578 gameInfo.round = StrSave("-");
16579 gameInfo.white = StrSave("-");
16580 gameInfo.black = StrSave("-");
16581 gameInfo.result = r;
16582 gameInfo.resultDetails = p;
16586 gameInfo.event = StrSave("Edited position");
16587 gameInfo.site = StrSave(HostName());
16588 gameInfo.date = PGNDate();
16589 gameInfo.round = StrSave("-");
16590 gameInfo.white = StrSave("-");
16591 gameInfo.black = StrSave("-");
16594 case IcsPlayingWhite:
16595 case IcsPlayingBlack:
16600 case PlayFromGameFile:
16601 gameInfo.event = StrSave("Game from non-PGN file");
16602 gameInfo.site = StrSave(HostName());
16603 gameInfo.date = PGNDate();
16604 gameInfo.round = StrSave("-");
16605 gameInfo.white = StrSave("?");
16606 gameInfo.black = StrSave("?");
16615 ReplaceComment (int index, char *text)
16621 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16622 pvInfoList[index-1].depth == len &&
16623 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16624 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16625 while (*text == '\n') text++;
16626 len = strlen(text);
16627 while (len > 0 && text[len - 1] == '\n') len--;
16629 if (commentList[index] != NULL)
16630 free(commentList[index]);
16633 commentList[index] = NULL;
16636 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16637 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16638 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16639 commentList[index] = (char *) malloc(len + 2);
16640 strncpy(commentList[index], text, len);
16641 commentList[index][len] = '\n';
16642 commentList[index][len + 1] = NULLCHAR;
16644 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16646 commentList[index] = (char *) malloc(len + 7);
16647 safeStrCpy(commentList[index], "{\n", 3);
16648 safeStrCpy(commentList[index]+2, text, len+1);
16649 commentList[index][len+2] = NULLCHAR;
16650 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16651 strcat(commentList[index], "\n}\n");
16656 CrushCRs (char *text)
16664 if (ch == '\r') continue;
16666 } while (ch != '\0');
16670 AppendComment (int index, char *text, Boolean addBraces)
16671 /* addBraces tells if we should add {} */
16676 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16677 if(addBraces == 3) addBraces = 0; else // force appending literally
16678 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16681 while (*text == '\n') text++;
16682 len = strlen(text);
16683 while (len > 0 && text[len - 1] == '\n') len--;
16684 text[len] = NULLCHAR;
16686 if (len == 0) return;
16688 if (commentList[index] != NULL) {
16689 Boolean addClosingBrace = addBraces;
16690 old = commentList[index];
16691 oldlen = strlen(old);
16692 while(commentList[index][oldlen-1] == '\n')
16693 commentList[index][--oldlen] = NULLCHAR;
16694 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16695 safeStrCpy(commentList[index], old, oldlen + len + 6);
16697 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16698 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16699 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16700 while (*text == '\n') { text++; len--; }
16701 commentList[index][--oldlen] = NULLCHAR;
16703 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16704 else strcat(commentList[index], "\n");
16705 strcat(commentList[index], text);
16706 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16707 else strcat(commentList[index], "\n");
16709 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16711 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16712 else commentList[index][0] = NULLCHAR;
16713 strcat(commentList[index], text);
16714 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16715 if(addBraces == TRUE) strcat(commentList[index], "}\n");
16720 FindStr (char * text, char * sub_text)
16722 char * result = strstr( text, sub_text );
16724 if( result != NULL ) {
16725 result += strlen( sub_text );
16731 /* [AS] Try to extract PV info from PGN comment */
16732 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16734 GetInfoFromComment (int index, char * text)
16736 char * sep = text, *p;
16738 if( text != NULL && index > 0 ) {
16741 int time = -1, sec = 0, deci;
16742 char * s_eval = FindStr( text, "[%eval " );
16743 char * s_emt = FindStr( text, "[%emt " );
16745 if( s_eval != NULL || s_emt != NULL ) {
16747 if(0) { // [HGM] this code is not finished, and could actually be detrimental
16752 if( s_eval != NULL ) {
16753 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16757 if( delim != ']' ) {
16762 if( s_emt != NULL ) {
16767 /* We expect something like: [+|-]nnn.nn/dd */
16770 if(*text != '{') return text; // [HGM] braces: must be normal comment
16772 sep = strchr( text, '/' );
16773 if( sep == NULL || sep < (text+4) ) {
16778 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16779 if(p[1] == '(') { // comment starts with PV
16780 p = strchr(p, ')'); // locate end of PV
16781 if(p == NULL || sep < p+5) return text;
16782 // at this point we have something like "{(.*) +0.23/6 ..."
16783 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16784 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16785 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16787 time = -1; sec = -1; deci = -1;
16788 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16789 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16790 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16791 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
16795 if( score_lo < 0 || score_lo >= 100 ) {
16799 if(sec >= 0) time = 600*time + 10*sec; else
16800 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16802 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16804 /* [HGM] PV time: now locate end of PV info */
16805 while( *++sep >= '0' && *sep <= '9'); // strip depth
16807 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16809 while( *++sep >= '0' && *sep <= '9'); // strip seconds
16811 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16812 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16823 pvInfoList[index-1].depth = depth;
16824 pvInfoList[index-1].score = score;
16825 pvInfoList[index-1].time = 10*time; // centi-sec
16826 if(*sep == '}') *sep = 0; else *--sep = '{';
16828 while(*p++ = *sep++)
16831 } // squeeze out space between PV and comment, and return both
16837 SendToProgram (char *message, ChessProgramState *cps)
16839 int count, outCount, error;
16842 if (cps->pr == NoProc) return;
16845 if (appData.debugMode) {
16848 fprintf(debugFP, "%ld >%-6s: %s",
16849 SubtractTimeMarks(&now, &programStartTime),
16850 cps->which, message);
16852 fprintf(serverFP, "%ld >%-6s: %s",
16853 SubtractTimeMarks(&now, &programStartTime),
16854 cps->which, message), fflush(serverFP);
16857 count = strlen(message);
16858 outCount = OutputToProcess(cps->pr, message, count, &error);
16859 if (outCount < count && !exiting
16860 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16861 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16862 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16863 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16864 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16865 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16866 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16867 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16869 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16870 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16871 gameInfo.result = res;
16873 gameInfo.resultDetails = StrSave(buf);
16875 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16876 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16881 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16885 ChessProgramState *cps = (ChessProgramState *)closure;
16887 if (isr != cps->isr) return; /* Killed intentionally */
16890 RemoveInputSource(cps->isr);
16891 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16892 _(cps->which), cps->program);
16893 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16894 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16895 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16896 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16897 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16898 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16900 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16901 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16902 gameInfo.result = res;
16904 gameInfo.resultDetails = StrSave(buf);
16906 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16907 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16909 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16910 _(cps->which), cps->program);
16911 RemoveInputSource(cps->isr);
16913 /* [AS] Program is misbehaving badly... kill it */
16914 if( count == -2 ) {
16915 DestroyChildProcess( cps->pr, 9 );
16919 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16924 if ((end_str = strchr(message, '\r')) != NULL)
16925 *end_str = NULLCHAR;
16926 if ((end_str = strchr(message, '\n')) != NULL)
16927 *end_str = NULLCHAR;
16929 if (appData.debugMode) {
16930 TimeMark now; int print = 1;
16931 char *quote = ""; char c; int i;
16933 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16934 char start = message[0];
16935 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16936 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16937 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
16938 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16939 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16940 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
16941 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16942 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
16943 sscanf(message, "hint: %c", &c)!=1 &&
16944 sscanf(message, "pong %c", &c)!=1 && start != '#') {
16945 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16946 print = (appData.engineComments >= 2);
16948 message[0] = start; // restore original message
16952 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16953 SubtractTimeMarks(&now, &programStartTime), cps->which,
16957 fprintf(serverFP, "%ld <%-6s: %s%s\n",
16958 SubtractTimeMarks(&now, &programStartTime), cps->which,
16960 message), fflush(serverFP);
16964 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16965 if (appData.icsEngineAnalyze) {
16966 if (strstr(message, "whisper") != NULL ||
16967 strstr(message, "kibitz") != NULL ||
16968 strstr(message, "tellics") != NULL) return;
16971 HandleMachineMove(message, cps);
16976 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16981 if( timeControl_2 > 0 ) {
16982 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16983 tc = timeControl_2;
16986 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16987 inc /= cps->timeOdds;
16988 st /= cps->timeOdds;
16990 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16993 /* Set exact time per move, normally using st command */
16994 if (cps->stKludge) {
16995 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16997 if (seconds == 0) {
16998 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
17000 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
17003 snprintf(buf, MSG_SIZ, "st %d\n", st);
17006 /* Set conventional or incremental time control, using level command */
17007 if (seconds == 0) {
17008 /* Note old gnuchess bug -- minutes:seconds used to not work.
17009 Fixed in later versions, but still avoid :seconds
17010 when seconds is 0. */
17011 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
17013 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
17014 seconds, inc/1000.);
17017 SendToProgram(buf, cps);
17019 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17020 /* Orthogonally, limit search to given depth */
17022 if (cps->sdKludge) {
17023 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17025 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17027 SendToProgram(buf, cps);
17030 if(cps->nps >= 0) { /* [HGM] nps */
17031 if(cps->supportsNPS == FALSE)
17032 cps->nps = -1; // don't use if engine explicitly says not supported!
17034 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17035 SendToProgram(buf, cps);
17040 ChessProgramState *
17042 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17044 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17045 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17051 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17053 char message[MSG_SIZ];
17056 /* Note: this routine must be called when the clocks are stopped
17057 or when they have *just* been set or switched; otherwise
17058 it will be off by the time since the current tick started.
17060 if (machineWhite) {
17061 time = whiteTimeRemaining / 10;
17062 otime = blackTimeRemaining / 10;
17064 time = blackTimeRemaining / 10;
17065 otime = whiteTimeRemaining / 10;
17067 /* [HGM] translate opponent's time by time-odds factor */
17068 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17070 if (time <= 0) time = 1;
17071 if (otime <= 0) otime = 1;
17073 snprintf(message, MSG_SIZ, "time %ld\n", time);
17074 SendToProgram(message, cps);
17076 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17077 SendToProgram(message, cps);
17081 EngineDefinedVariant (ChessProgramState *cps, int n)
17082 { // return name of n-th unknown variant that engine supports
17083 static char buf[MSG_SIZ];
17084 char *p, *s = cps->variants;
17085 if(!s) return NULL;
17086 do { // parse string from variants feature
17088 p = strchr(s, ',');
17089 if(p) *p = NULLCHAR;
17090 v = StringToVariant(s);
17091 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17092 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17093 if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17094 !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17095 !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17096 !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17097 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17100 if(n < 0) return buf;
17106 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17109 int len = strlen(name);
17112 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17114 sscanf(*p, "%d", &val);
17116 while (**p && **p != ' ')
17118 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17119 SendToProgram(buf, cps);
17126 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17129 int len = strlen(name);
17130 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17132 sscanf(*p, "%d", loc);
17133 while (**p && **p != ' ') (*p)++;
17134 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17135 SendToProgram(buf, cps);
17142 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17145 int len = strlen(name);
17146 if (strncmp((*p), name, len) == 0
17147 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17149 ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
17150 sscanf(*p, "%[^\"]", *loc);
17151 while (**p && **p != '\"') (*p)++;
17152 if (**p == '\"') (*p)++;
17153 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17154 SendToProgram(buf, cps);
17161 ParseOption (Option *opt, ChessProgramState *cps)
17162 // [HGM] options: process the string that defines an engine option, and determine
17163 // name, type, default value, and allowed value range
17165 char *p, *q, buf[MSG_SIZ];
17166 int n, min = (-1)<<31, max = 1<<31, def;
17168 opt->target = &opt->value; // OK for spin/slider and checkbox
17169 if(p = strstr(opt->name, " -spin ")) {
17170 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17171 if(max < min) max = min; // enforce consistency
17172 if(def < min) def = min;
17173 if(def > max) def = max;
17178 } else if((p = strstr(opt->name, " -slider "))) {
17179 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17180 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17181 if(max < min) max = min; // enforce consistency
17182 if(def < min) def = min;
17183 if(def > max) def = max;
17187 opt->type = Spin; // Slider;
17188 } else if((p = strstr(opt->name, " -string "))) {
17189 opt->textValue = p+9;
17190 opt->type = TextBox;
17191 opt->target = &opt->textValue;
17192 } else if((p = strstr(opt->name, " -file "))) {
17193 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17194 opt->target = opt->textValue = p+7;
17195 opt->type = FileName; // FileName;
17196 opt->target = &opt->textValue;
17197 } else if((p = strstr(opt->name, " -path "))) {
17198 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17199 opt->target = opt->textValue = p+7;
17200 opt->type = PathName; // PathName;
17201 opt->target = &opt->textValue;
17202 } else if(p = strstr(opt->name, " -check ")) {
17203 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17204 opt->value = (def != 0);
17205 opt->type = CheckBox;
17206 } else if(p = strstr(opt->name, " -combo ")) {
17207 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17208 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17209 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17210 opt->value = n = 0;
17211 while(q = StrStr(q, " /// ")) {
17212 n++; *q = 0; // count choices, and null-terminate each of them
17214 if(*q == '*') { // remember default, which is marked with * prefix
17218 cps->comboList[cps->comboCnt++] = q;
17220 cps->comboList[cps->comboCnt++] = NULL;
17222 opt->type = ComboBox;
17223 } else if(p = strstr(opt->name, " -button")) {
17224 opt->type = Button;
17225 } else if(p = strstr(opt->name, " -save")) {
17226 opt->type = SaveButton;
17227 } else return FALSE;
17228 *p = 0; // terminate option name
17229 // now look if the command-line options define a setting for this engine option.
17230 if(cps->optionSettings && cps->optionSettings[0])
17231 p = strstr(cps->optionSettings, opt->name); else p = NULL;
17232 if(p && (p == cps->optionSettings || p[-1] == ',')) {
17233 snprintf(buf, MSG_SIZ, "option %s", p);
17234 if(p = strstr(buf, ",")) *p = 0;
17235 if(q = strchr(buf, '=')) switch(opt->type) {
17237 for(n=0; n<opt->max; n++)
17238 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17241 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17245 opt->value = atoi(q+1);
17250 SendToProgram(buf, cps);
17256 FeatureDone (ChessProgramState *cps, int val)
17258 DelayedEventCallback cb = GetDelayedEvent();
17259 if ((cb == InitBackEnd3 && cps == &first) ||
17260 (cb == SettingsMenuIfReady && cps == &second) ||
17261 (cb == LoadEngine) ||
17262 (cb == TwoMachinesEventIfReady)) {
17263 CancelDelayedEvent();
17264 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17265 } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17266 cps->initDone = val;
17267 if(val) cps->reload = FALSE, RefreshSettingsDialog(cps, val);
17270 /* Parse feature command from engine */
17272 ParseFeatures (char *args, ChessProgramState *cps)
17280 while (*p == ' ') p++;
17281 if (*p == NULLCHAR) return;
17283 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17284 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17285 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17286 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17287 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17288 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17289 if (BoolFeature(&p, "reuse", &val, cps)) {
17290 /* Engine can disable reuse, but can't enable it if user said no */
17291 if (!val) cps->reuse = FALSE;
17294 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17295 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17296 if (gameMode == TwoMachinesPlay) {
17297 DisplayTwoMachinesTitle();
17303 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17304 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17305 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17306 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17307 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17308 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17309 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17310 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17311 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17312 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17313 if (IntFeature(&p, "done", &val, cps)) {
17314 FeatureDone(cps, val);
17317 /* Added by Tord: */
17318 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17319 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17320 /* End of additions by Tord */
17322 /* [HGM] added features: */
17323 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17324 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17325 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17326 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17327 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17328 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17329 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17330 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17331 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17332 FREE(cps->option[cps->nrOptions].name);
17333 cps->option[cps->nrOptions].name = q; q = NULL;
17334 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17335 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17336 SendToProgram(buf, cps);
17339 if(cps->nrOptions >= MAX_OPTIONS) {
17341 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17342 DisplayError(buf, 0);
17346 /* End of additions by HGM */
17348 /* unknown feature: complain and skip */
17350 while (*q && *q != '=') q++;
17351 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17352 SendToProgram(buf, cps);
17358 while (*p && *p != '\"') p++;
17359 if (*p == '\"') p++;
17361 while (*p && *p != ' ') p++;
17369 PeriodicUpdatesEvent (int newState)
17371 if (newState == appData.periodicUpdates)
17374 appData.periodicUpdates=newState;
17376 /* Display type changes, so update it now */
17377 // DisplayAnalysis();
17379 /* Get the ball rolling again... */
17381 AnalysisPeriodicEvent(1);
17382 StartAnalysisClock();
17387 PonderNextMoveEvent (int newState)
17389 if (newState == appData.ponderNextMove) return;
17390 if (gameMode == EditPosition) EditPositionDone(TRUE);
17392 SendToProgram("hard\n", &first);
17393 if (gameMode == TwoMachinesPlay) {
17394 SendToProgram("hard\n", &second);
17397 SendToProgram("easy\n", &first);
17398 thinkOutput[0] = NULLCHAR;
17399 if (gameMode == TwoMachinesPlay) {
17400 SendToProgram("easy\n", &second);
17403 appData.ponderNextMove = newState;
17407 NewSettingEvent (int option, int *feature, char *command, int value)
17411 if (gameMode == EditPosition) EditPositionDone(TRUE);
17412 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17413 if(feature == NULL || *feature) SendToProgram(buf, &first);
17414 if (gameMode == TwoMachinesPlay) {
17415 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17420 ShowThinkingEvent ()
17421 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17423 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17424 int newState = appData.showThinking
17425 // [HGM] thinking: other features now need thinking output as well
17426 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17428 if (oldState == newState) return;
17429 oldState = newState;
17430 if (gameMode == EditPosition) EditPositionDone(TRUE);
17432 SendToProgram("post\n", &first);
17433 if (gameMode == TwoMachinesPlay) {
17434 SendToProgram("post\n", &second);
17437 SendToProgram("nopost\n", &first);
17438 thinkOutput[0] = NULLCHAR;
17439 if (gameMode == TwoMachinesPlay) {
17440 SendToProgram("nopost\n", &second);
17443 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17447 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17449 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17450 if (pr == NoProc) return;
17451 AskQuestion(title, question, replyPrefix, pr);
17455 TypeInEvent (char firstChar)
17457 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17458 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17459 gameMode == AnalyzeMode || gameMode == EditGame ||
17460 gameMode == EditPosition || gameMode == IcsExamining ||
17461 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17462 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17463 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17464 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
17465 gameMode == Training) PopUpMoveDialog(firstChar);
17469 TypeInDoneEvent (char *move)
17472 int n, fromX, fromY, toX, toY;
17474 ChessMove moveType;
17477 if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17478 EditPositionPasteFEN(move);
17481 // [HGM] movenum: allow move number to be typed in any mode
17482 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17486 // undocumented kludge: allow command-line option to be typed in!
17487 // (potentially fatal, and does not implement the effect of the option.)
17488 // should only be used for options that are values on which future decisions will be made,
17489 // and definitely not on options that would be used during initialization.
17490 if(strstr(move, "!!! -") == move) {
17491 ParseArgsFromString(move+4);
17495 if (gameMode != EditGame && currentMove != forwardMostMove &&
17496 gameMode != Training) {
17497 DisplayMoveError(_("Displayed move is not current"));
17499 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17500 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17501 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17502 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17503 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17504 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17506 DisplayMoveError(_("Could not parse move"));
17512 DisplayMove (int moveNumber)
17514 char message[MSG_SIZ];
17516 char cpThinkOutput[MSG_SIZ];
17518 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17520 if (moveNumber == forwardMostMove - 1 ||
17521 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17523 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17525 if (strchr(cpThinkOutput, '\n')) {
17526 *strchr(cpThinkOutput, '\n') = NULLCHAR;
17529 *cpThinkOutput = NULLCHAR;
17532 /* [AS] Hide thinking from human user */
17533 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17534 *cpThinkOutput = NULLCHAR;
17535 if( thinkOutput[0] != NULLCHAR ) {
17538 for( i=0; i<=hiddenThinkOutputState; i++ ) {
17539 cpThinkOutput[i] = '.';
17541 cpThinkOutput[i] = NULLCHAR;
17542 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17546 if (moveNumber == forwardMostMove - 1 &&
17547 gameInfo.resultDetails != NULL) {
17548 if (gameInfo.resultDetails[0] == NULLCHAR) {
17549 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17551 snprintf(res, MSG_SIZ, " {%s} %s",
17552 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17558 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17559 DisplayMessage(res, cpThinkOutput);
17561 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17562 WhiteOnMove(moveNumber) ? " " : ".. ",
17563 parseList[moveNumber], res);
17564 DisplayMessage(message, cpThinkOutput);
17569 DisplayComment (int moveNumber, char *text)
17571 char title[MSG_SIZ];
17573 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17574 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17576 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17577 WhiteOnMove(moveNumber) ? " " : ".. ",
17578 parseList[moveNumber]);
17580 if (text != NULL && (appData.autoDisplayComment || commentUp))
17581 CommentPopUp(title, text);
17584 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17585 * might be busy thinking or pondering. It can be omitted if your
17586 * gnuchess is configured to stop thinking immediately on any user
17587 * input. However, that gnuchess feature depends on the FIONREAD
17588 * ioctl, which does not work properly on some flavors of Unix.
17591 Attention (ChessProgramState *cps)
17594 if (!cps->useSigint) return;
17595 if (appData.noChessProgram || (cps->pr == NoProc)) return;
17596 switch (gameMode) {
17597 case MachinePlaysWhite:
17598 case MachinePlaysBlack:
17599 case TwoMachinesPlay:
17600 case IcsPlayingWhite:
17601 case IcsPlayingBlack:
17604 /* Skip if we know it isn't thinking */
17605 if (!cps->maybeThinking) return;
17606 if (appData.debugMode)
17607 fprintf(debugFP, "Interrupting %s\n", cps->which);
17608 InterruptChildProcess(cps->pr);
17609 cps->maybeThinking = FALSE;
17614 #endif /*ATTENTION*/
17620 if (whiteTimeRemaining <= 0) {
17623 if (appData.icsActive) {
17624 if (appData.autoCallFlag &&
17625 gameMode == IcsPlayingBlack && !blackFlag) {
17626 SendToICS(ics_prefix);
17627 SendToICS("flag\n");
17631 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17633 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17634 if (appData.autoCallFlag) {
17635 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17642 if (blackTimeRemaining <= 0) {
17645 if (appData.icsActive) {
17646 if (appData.autoCallFlag &&
17647 gameMode == IcsPlayingWhite && !whiteFlag) {
17648 SendToICS(ics_prefix);
17649 SendToICS("flag\n");
17653 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17655 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17656 if (appData.autoCallFlag) {
17657 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17668 CheckTimeControl ()
17670 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17671 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17674 * add time to clocks when time control is achieved ([HGM] now also used for increment)
17676 if ( !WhiteOnMove(forwardMostMove) ) {
17677 /* White made time control */
17678 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17679 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17680 /* [HGM] time odds: correct new time quota for time odds! */
17681 / WhitePlayer()->timeOdds;
17682 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17684 lastBlack -= blackTimeRemaining;
17685 /* Black made time control */
17686 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17687 / WhitePlayer()->other->timeOdds;
17688 lastWhite = whiteTimeRemaining;
17693 DisplayBothClocks ()
17695 int wom = gameMode == EditPosition ?
17696 !blackPlaysFirst : WhiteOnMove(currentMove);
17697 DisplayWhiteClock(whiteTimeRemaining, wom);
17698 DisplayBlackClock(blackTimeRemaining, !wom);
17702 /* Timekeeping seems to be a portability nightmare. I think everyone
17703 has ftime(), but I'm really not sure, so I'm including some ifdefs
17704 to use other calls if you don't. Clocks will be less accurate if
17705 you have neither ftime nor gettimeofday.
17708 /* VS 2008 requires the #include outside of the function */
17709 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17710 #include <sys/timeb.h>
17713 /* Get the current time as a TimeMark */
17715 GetTimeMark (TimeMark *tm)
17717 #if HAVE_GETTIMEOFDAY
17719 struct timeval timeVal;
17720 struct timezone timeZone;
17722 gettimeofday(&timeVal, &timeZone);
17723 tm->sec = (long) timeVal.tv_sec;
17724 tm->ms = (int) (timeVal.tv_usec / 1000L);
17726 #else /*!HAVE_GETTIMEOFDAY*/
17729 // include <sys/timeb.h> / moved to just above start of function
17730 struct timeb timeB;
17733 tm->sec = (long) timeB.time;
17734 tm->ms = (int) timeB.millitm;
17736 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17737 tm->sec = (long) time(NULL);
17743 /* Return the difference in milliseconds between two
17744 time marks. We assume the difference will fit in a long!
17747 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17749 return 1000L*(tm2->sec - tm1->sec) +
17750 (long) (tm2->ms - tm1->ms);
17755 * Code to manage the game clocks.
17757 * In tournament play, black starts the clock and then white makes a move.
17758 * We give the human user a slight advantage if he is playing white---the
17759 * clocks don't run until he makes his first move, so it takes zero time.
17760 * Also, we don't account for network lag, so we could get out of sync
17761 * with GNU Chess's clock -- but then, referees are always right.
17764 static TimeMark tickStartTM;
17765 static long intendedTickLength;
17768 NextTickLength (long timeRemaining)
17770 long nominalTickLength, nextTickLength;
17772 if (timeRemaining > 0L && timeRemaining <= 10000L)
17773 nominalTickLength = 100L;
17775 nominalTickLength = 1000L;
17776 nextTickLength = timeRemaining % nominalTickLength;
17777 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17779 return nextTickLength;
17782 /* Adjust clock one minute up or down */
17784 AdjustClock (Boolean which, int dir)
17786 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17787 if(which) blackTimeRemaining += 60000*dir;
17788 else whiteTimeRemaining += 60000*dir;
17789 DisplayBothClocks();
17790 adjustedClock = TRUE;
17793 /* Stop clocks and reset to a fresh time control */
17797 (void) StopClockTimer();
17798 if (appData.icsActive) {
17799 whiteTimeRemaining = blackTimeRemaining = 0;
17800 } else if (searchTime) {
17801 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17802 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17803 } else { /* [HGM] correct new time quote for time odds */
17804 whiteTC = blackTC = fullTimeControlString;
17805 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17806 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17808 if (whiteFlag || blackFlag) {
17810 whiteFlag = blackFlag = FALSE;
17812 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17813 DisplayBothClocks();
17814 adjustedClock = FALSE;
17817 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17819 static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness...
17821 /* Decrement running clock by amount of time that has passed */
17826 long lastTickLength, fudge;
17829 if (!appData.clockMode) return;
17830 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17834 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17836 /* Fudge if we woke up a little too soon */
17837 fudge = intendedTickLength - lastTickLength;
17838 if (fudge < 0 || fudge > FUDGE) fudge = 0;
17840 if (WhiteOnMove(forwardMostMove)) {
17841 if(whiteNPS >= 0) lastTickLength = 0;
17842 tRemaining = whiteTimeRemaining -= lastTickLength;
17843 if( tRemaining < 0 && !appData.icsActive) {
17844 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17845 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17846 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17847 lastWhite= tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17850 if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining;
17851 DisplayWhiteClock(whiteTimeRemaining - fudge,
17852 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17855 if(blackNPS >= 0) lastTickLength = 0;
17856 tRemaining = blackTimeRemaining -= lastTickLength;
17857 if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17858 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17860 blackStartMove = forwardMostMove;
17861 lastBlack = tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17864 if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining;
17865 DisplayBlackClock(blackTimeRemaining - fudge,
17866 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17869 if (CheckFlags()) return;
17871 if(twoBoards) { // count down secondary board's clocks as well
17872 activePartnerTime -= lastTickLength;
17874 if(activePartner == 'W')
17875 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17877 DisplayBlackClock(activePartnerTime, TRUE);
17882 intendedTickLength = NextTickLength( tRemaining - fudge) + fudge;
17883 StartClockTimer(intendedTickLength);
17885 /* if the time remaining has fallen below the alarm threshold, sound the
17886 * alarm. if the alarm has sounded and (due to a takeback or time control
17887 * with increment) the time remaining has increased to a level above the
17888 * threshold, reset the alarm so it can sound again.
17891 if (appData.icsActive && appData.icsAlarm) {
17893 /* make sure we are dealing with the user's clock */
17894 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17895 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17898 if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) {
17899 alarmSounded = FALSE;
17900 } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) {
17902 alarmSounded = TRUE;
17908 /* A player has just moved, so stop the previously running
17909 clock and (if in clock mode) start the other one.
17910 We redisplay both clocks in case we're in ICS mode, because
17911 ICS gives us an update to both clocks after every move.
17912 Note that this routine is called *after* forwardMostMove
17913 is updated, so the last fractional tick must be subtracted
17914 from the color that is *not* on move now.
17917 SwitchClocks (int newMoveNr)
17919 long lastTickLength;
17921 int flagged = FALSE;
17925 if (StopClockTimer() && appData.clockMode) {
17926 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17927 if (!WhiteOnMove(forwardMostMove)) {
17928 if(blackNPS >= 0) lastTickLength = 0;
17929 blackTimeRemaining -= lastTickLength;
17930 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17931 // if(pvInfoList[forwardMostMove].time == -1)
17932 pvInfoList[forwardMostMove].time = // use GUI time
17933 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17935 if(whiteNPS >= 0) lastTickLength = 0;
17936 whiteTimeRemaining -= lastTickLength;
17937 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17938 // if(pvInfoList[forwardMostMove].time == -1)
17939 pvInfoList[forwardMostMove].time =
17940 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17942 flagged = CheckFlags();
17944 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17945 CheckTimeControl();
17947 if (flagged || !appData.clockMode) return;
17949 switch (gameMode) {
17950 case MachinePlaysBlack:
17951 case MachinePlaysWhite:
17952 case BeginningOfGame:
17953 if (pausing) return;
17957 case PlayFromGameFile:
17965 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17966 if(WhiteOnMove(forwardMostMove))
17967 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17968 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17972 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17973 whiteTimeRemaining : blackTimeRemaining);
17974 StartClockTimer(intendedTickLength);
17978 /* Stop both clocks */
17982 long lastTickLength;
17985 if (!StopClockTimer()) return;
17986 if (!appData.clockMode) return;
17990 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17991 if (WhiteOnMove(forwardMostMove)) {
17992 if(whiteNPS >= 0) lastTickLength = 0;
17993 whiteTimeRemaining -= lastTickLength;
17994 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17996 if(blackNPS >= 0) lastTickLength = 0;
17997 blackTimeRemaining -= lastTickLength;
17998 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
18003 /* Start clock of player on move. Time may have been reset, so
18004 if clock is already running, stop and restart it. */
18008 (void) StopClockTimer(); /* in case it was running already */
18009 DisplayBothClocks();
18010 if (CheckFlags()) return;
18012 if (!appData.clockMode) return;
18013 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
18015 GetTimeMark(&tickStartTM);
18016 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18017 whiteTimeRemaining : blackTimeRemaining);
18019 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
18020 whiteNPS = blackNPS = -1;
18021 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
18022 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
18023 whiteNPS = first.nps;
18024 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18025 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18026 blackNPS = first.nps;
18027 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18028 whiteNPS = second.nps;
18029 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18030 blackNPS = second.nps;
18031 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18033 StartClockTimer(intendedTickLength);
18037 TimeString (long ms)
18039 long second, minute, hour, day;
18041 static char buf[40], moveTime[8];
18043 if (ms > 0 && ms <= 9900) {
18044 /* convert milliseconds to tenths, rounding up */
18045 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18047 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18051 /* convert milliseconds to seconds, rounding up */
18052 /* use floating point to avoid strangeness of integer division
18053 with negative dividends on many machines */
18054 second = (long) floor(((double) (ms + 999L)) / 1000.0);
18061 day = second / (60 * 60 * 24);
18062 second = second % (60 * 60 * 24);
18063 hour = second / (60 * 60);
18064 second = second % (60 * 60);
18065 minute = second / 60;
18066 second = second % 60;
18068 if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time
18069 else *moveTime = NULLCHAR;
18072 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ",
18073 sign, day, hour, minute, second, moveTime);
18075 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime);
18077 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime);
18084 * This is necessary because some C libraries aren't ANSI C compliant yet.
18087 StrStr (char *string, char *match)
18091 length = strlen(match);
18093 for (i = strlen(string) - length; i >= 0; i--, string++)
18094 if (!strncmp(match, string, length))
18101 StrCaseStr (char *string, char *match)
18105 length = strlen(match);
18107 for (i = strlen(string) - length; i >= 0; i--, string++) {
18108 for (j = 0; j < length; j++) {
18109 if (ToLower(match[j]) != ToLower(string[j]))
18112 if (j == length) return string;
18120 StrCaseCmp (char *s1, char *s2)
18125 c1 = ToLower(*s1++);
18126 c2 = ToLower(*s2++);
18127 if (c1 > c2) return 1;
18128 if (c1 < c2) return -1;
18129 if (c1 == NULLCHAR) return 0;
18137 return isupper(c) ? tolower(c) : c;
18144 return islower(c) ? toupper(c) : c;
18146 #endif /* !_amigados */
18153 if ((ret = (char *) malloc(strlen(s) + 1)))
18155 safeStrCpy(ret, s, strlen(s)+1);
18161 StrSavePtr (char *s, char **savePtr)
18166 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18167 safeStrCpy(*savePtr, s, strlen(s)+1);
18179 clock = time((time_t *)NULL);
18180 tm = localtime(&clock);
18181 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18182 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18183 return StrSave(buf);
18188 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18190 int i, j, fromX, fromY, toX, toY;
18191 int whiteToPlay, haveRights = nrCastlingRights;
18197 whiteToPlay = (gameMode == EditPosition) ?
18198 !blackPlaysFirst : (move % 2 == 0);
18201 /* Piece placement data */
18202 for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18203 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18205 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18206 if (boards[move][i][j] == EmptySquare) {
18208 } else { ChessSquare piece = boards[move][i][j];
18209 if (emptycount > 0) {
18210 if(emptycount<10) /* [HGM] can be >= 10 */
18211 *p++ = '0' + emptycount;
18212 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18215 if(PieceToChar(piece) == '+') {
18216 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18218 piece = (ChessSquare)(CHUDEMOTED(piece));
18220 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18221 if(*p = PieceSuffix(piece)) p++;
18223 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18224 p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18229 if (emptycount > 0) {
18230 if(emptycount<10) /* [HGM] can be >= 10 */
18231 *p++ = '0' + emptycount;
18232 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18239 /* [HGM] print Crazyhouse or Shogi holdings */
18240 if( gameInfo.holdingsWidth ) {
18241 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18243 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18244 piece = boards[move][i][BOARD_WIDTH-1];
18245 if( piece != EmptySquare )
18246 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18247 *p++ = PieceToChar(piece);
18249 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18250 piece = boards[move][BOARD_HEIGHT-i-1][0];
18251 if( piece != EmptySquare )
18252 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18253 *p++ = PieceToChar(piece);
18256 if( q == p ) *p++ = '-';
18262 *p++ = whiteToPlay ? 'w' : 'b';
18265 if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18266 haveRights = 0; q = p;
18267 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18268 piece = boards[move][0][i];
18269 if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18270 if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18273 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18274 piece = boards[move][BOARD_HEIGHT-1][i];
18275 if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18276 if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18279 if(p == q) *p++ = '-';
18283 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18286 if(q != overrideCastling+1) p[-1] = ' '; else --p;
18289 int handW=0, handB=0;
18290 if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18291 for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18292 for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18295 if(appData.fischerCastling) {
18296 if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18297 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18298 if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18300 /* [HGM] write directly from rights */
18301 if(boards[move][CASTLING][2] != NoRights &&
18302 boards[move][CASTLING][0] != NoRights )
18303 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18304 if(boards[move][CASTLING][2] != NoRights &&
18305 boards[move][CASTLING][1] != NoRights )
18306 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18309 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18310 if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18312 if(boards[move][CASTLING][5] != NoRights &&
18313 boards[move][CASTLING][3] != NoRights )
18314 *p++ = boards[move][CASTLING][3] + AAA;
18315 if(boards[move][CASTLING][5] != NoRights &&
18316 boards[move][CASTLING][4] != NoRights )
18317 *p++ = boards[move][CASTLING][4] + AAA;
18321 /* [HGM] write true castling rights */
18322 if( nrCastlingRights == 6 ) {
18324 if(boards[move][CASTLING][0] != NoRights &&
18325 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
18326 q = (boards[move][CASTLING][1] != NoRights &&
18327 boards[move][CASTLING][2] != NoRights );
18328 if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18329 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18330 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18331 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18335 if(boards[move][CASTLING][3] != NoRights &&
18336 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
18337 q = (boards[move][CASTLING][4] != NoRights &&
18338 boards[move][CASTLING][5] != NoRights );
18340 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18341 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18342 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18347 if (q == p) *p++ = '-'; /* No castling rights */
18351 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
18352 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18353 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18354 /* En passant target square */
18355 if (move > backwardMostMove) {
18356 fromX = moveList[move - 1][0] - AAA;
18357 fromY = moveList[move - 1][1] - ONE;
18358 toX = moveList[move - 1][2] - AAA;
18359 toY = moveList[move - 1][3] - ONE;
18360 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18361 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18362 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18364 /* 2-square pawn move just happened */
18366 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18370 } else if(move == backwardMostMove) {
18371 // [HGM] perhaps we should always do it like this, and forget the above?
18372 if((signed char)boards[move][EP_STATUS] >= 0) {
18373 *p++ = boards[move][EP_STATUS] + AAA;
18374 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18386 { int i = 0, j=move;
18388 /* [HGM] find reversible plies */
18389 if (appData.debugMode) { int k;
18390 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18391 for(k=backwardMostMove; k<=forwardMostMove; k++)
18392 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18396 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18397 if( j == backwardMostMove ) i += initialRulePlies;
18398 sprintf(p, "%d ", i);
18399 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18401 /* Fullmove number */
18402 sprintf(p, "%d", (move / 2) + 1);
18403 } else *--p = NULLCHAR;
18405 return StrSave(buf);
18409 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18411 int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18413 int emptycount, virgin[BOARD_FILES];
18414 ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18418 for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) board[BOARD_HEIGHT-i][j] = DarkSquare;
18420 /* Piece placement data */
18421 for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18424 if (*p == '/' || *p == ' ' || *p == '[' ) {
18426 emptycount = gameInfo.boardWidth - j;
18427 while (emptycount--)
18428 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18429 if (*p == '/') p++;
18430 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18431 for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18432 for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18434 appData.NrRanks = gameInfo.boardHeight - i; i=0;
18437 #if(BOARD_FILES >= 10)*0
18438 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18439 p++; emptycount=10;
18440 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18441 while (emptycount--)
18442 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18444 } else if (*p == '*') {
18445 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18446 } else if (isdigit(*p)) {
18447 emptycount = *p++ - '0';
18448 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18449 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18450 while (emptycount--)
18451 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18452 } else if (*p == '<') {
18453 if(i == BOARD_HEIGHT-1) shuffle = 1;
18454 else if (i != 0 || !shuffle) return FALSE;
18456 } else if (shuffle && *p == '>') {
18457 p++; // for now ignore closing shuffle range, and assume rank-end
18458 } else if (*p == '?') {
18459 if (j >= gameInfo.boardWidth) return FALSE;
18460 if (i != 0 && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18461 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18462 } else if (*p == '+' || isalpha(*p)) {
18463 char *q, *s = SUFFIXES;
18464 if (j >= gameInfo.boardWidth) return FALSE;
18467 if(q = strchr(s, p[1])) p++;
18468 piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18469 if(piece == EmptySquare) return FALSE; /* unknown piece */
18470 piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18471 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18474 if(q = strchr(s, *p)) p++;
18475 piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18478 if(piece==EmptySquare) return FALSE; /* unknown piece */
18479 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18480 piece = (ChessSquare) (PROMOTED(piece));
18481 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18484 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18485 if(piece == king) wKingRank = i;
18486 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18492 while (*p == '/' || *p == ' ') p++;
18494 if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18496 /* [HGM] by default clear Crazyhouse holdings, if present */
18497 if(gameInfo.holdingsWidth) {
18498 for(i=0; i<BOARD_HEIGHT; i++) {
18499 board[i][0] = EmptySquare; /* black holdings */
18500 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18501 board[i][1] = (ChessSquare) 0; /* black counts */
18502 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18506 /* [HGM] look for Crazyhouse holdings here */
18507 while(*p==' ') p++;
18508 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18509 int swap=0, wcnt=0, bcnt=0;
18511 if(*p == '<') swap++, p++;
18512 if(*p == '-' ) p++; /* empty holdings */ else {
18513 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18514 /* if we would allow FEN reading to set board size, we would */
18515 /* have to add holdings and shift the board read so far here */
18516 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18518 if((int) piece >= (int) BlackPawn ) {
18519 i = (int)piece - (int)BlackPawn;
18520 i = PieceToNumber((ChessSquare)i);
18521 if( i >= gameInfo.holdingsSize ) return FALSE;
18522 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18523 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
18526 i = (int)piece - (int)WhitePawn;
18527 i = PieceToNumber((ChessSquare)i);
18528 if( i >= gameInfo.holdingsSize ) return FALSE;
18529 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
18530 board[i][BOARD_WIDTH-2]++; /* black holdings */
18534 if(subst) { // substitute back-rank question marks by holdings pieces
18535 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18536 int k, m, n = bcnt + 1;
18537 if(board[0][j] == ClearBoard) {
18538 if(!wcnt) return FALSE;
18540 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18541 board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18542 if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18546 if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18547 if(!bcnt) return FALSE;
18548 if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18549 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18550 board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18551 if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18562 if(subst) return FALSE; // substitution requested, but no holdings
18564 while(*p == ' ') p++;
18568 if(appData.colorNickNames) {
18569 if( c == appData.colorNickNames[0] ) c = 'w'; else
18570 if( c == appData.colorNickNames[1] ) c = 'b';
18574 *blackPlaysFirst = FALSE;
18577 *blackPlaysFirst = TRUE;
18583 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18584 /* return the extra info in global variiables */
18586 while(*p==' ') p++;
18588 if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18589 if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18590 if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18593 /* set defaults in case FEN is incomplete */
18594 board[EP_STATUS] = EP_UNKNOWN;
18595 board[TOUCHED_W] = board[TOUCHED_B] = 0;
18596 for(i=0; i<nrCastlingRights; i++ ) {
18597 board[CASTLING][i] =
18598 appData.fischerCastling ? NoRights : initialRights[i];
18599 } /* assume possible unless obviously impossible */
18600 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18601 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18602 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18603 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18604 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18605 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18606 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18607 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18610 if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18613 while(isalpha(*p)) {
18614 if(isupper(*p)) w |= 1 << (*p++ - 'A');
18615 if(islower(*p)) b |= 1 << (*p++ - 'a');
18619 board[TOUCHED_W] = ~w;
18620 board[TOUCHED_B] = ~b;
18621 while(*p == ' ') p++;
18625 if(nrCastlingRights) {
18627 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18628 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18629 /* castling indicator present, so default becomes no castlings */
18630 for(i=0; i<nrCastlingRights; i++ ) {
18631 board[CASTLING][i] = NoRights;
18634 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18635 (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18636 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18637 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
18638 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18640 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18641 if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18642 if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18644 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18645 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18646 if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18647 && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18648 if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18649 && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18652 for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18653 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18654 board[CASTLING][2] = whiteKingFile;
18655 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18656 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18657 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18660 for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18661 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18662 board[CASTLING][2] = whiteKingFile;
18663 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18664 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18665 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18668 for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18669 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18670 board[CASTLING][5] = blackKingFile;
18671 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18672 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18673 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18676 for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18677 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18678 board[CASTLING][5] = blackKingFile;
18679 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18680 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18681 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18684 default: /* FRC castlings */
18685 if(c >= 'a') { /* black rights */
18686 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18687 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18688 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18689 if(i == BOARD_RGHT) break;
18690 board[CASTLING][5] = i;
18692 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
18693 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
18695 board[CASTLING][3] = c;
18697 board[CASTLING][4] = c;
18698 } else { /* white rights */
18699 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18700 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18701 if(board[0][i] == WhiteKing) break;
18702 if(i == BOARD_RGHT) break;
18703 board[CASTLING][2] = i;
18704 c -= AAA - 'a' + 'A';
18705 if(board[0][c] >= WhiteKing) break;
18707 board[CASTLING][0] = c;
18709 board[CASTLING][1] = c;
18713 for(i=0; i<nrCastlingRights; i++)
18714 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18715 if(gameInfo.variant == VariantSChess)
18716 for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18717 if(fischer && shuffle) appData.fischerCastling = TRUE;
18718 if (appData.debugMode) {
18719 fprintf(debugFP, "FEN castling rights:");
18720 for(i=0; i<nrCastlingRights; i++)
18721 fprintf(debugFP, " %d", board[CASTLING][i]);
18722 fprintf(debugFP, "\n");
18725 while(*p==' ') p++;
18728 if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18730 /* read e.p. field in games that know e.p. capture */
18731 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
18732 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18733 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18735 p++; board[EP_STATUS] = EP_NONE;
18737 char c = *p++ - AAA;
18739 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18740 if(*p >= '0' && *p <='9') p++;
18741 board[EP_STATUS] = c;
18746 if(sscanf(p, "%d", &i) == 1) {
18747 FENrulePlies = i; /* 50-move ply counter */
18748 /* (The move number is still ignored) */
18755 EditPositionPasteFEN (char *fen)
18758 Board initial_position;
18760 if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18761 DisplayError(_("Bad FEN position in clipboard"), 0);
18764 int savedBlackPlaysFirst = blackPlaysFirst;
18765 EditPositionEvent();
18766 blackPlaysFirst = savedBlackPlaysFirst;
18767 CopyBoard(boards[0], initial_position);
18768 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18769 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18770 DisplayBothClocks();
18771 DrawPosition(FALSE, boards[currentMove]);
18776 static char cseq[12] = "\\ ";
18779 set_cont_sequence (char *new_seq)
18784 // handle bad attempts to set the sequence
18786 return 0; // acceptable error - no debug
18788 len = strlen(new_seq);
18789 ret = (len > 0) && (len < sizeof(cseq));
18791 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18792 else if (appData.debugMode)
18793 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18798 reformat a source message so words don't cross the width boundary. internal
18799 newlines are not removed. returns the wrapped size (no null character unless
18800 included in source message). If dest is NULL, only calculate the size required
18801 for the dest buffer. lp argument indicats line position upon entry, and it's
18802 passed back upon exit.
18805 wrap (char *dest, char *src, int count, int width, int *lp)
18807 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18809 cseq_len = strlen(cseq);
18810 old_line = line = *lp;
18811 ansi = len = clen = 0;
18813 for (i=0; i < count; i++)
18815 if (src[i] == '\033')
18818 // if we hit the width, back up
18819 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18821 // store i & len in case the word is too long
18822 old_i = i, old_len = len;
18824 // find the end of the last word
18825 while (i && src[i] != ' ' && src[i] != '\n')
18831 // word too long? restore i & len before splitting it
18832 if ((old_i-i+clen) >= width)
18839 if (i && src[i-1] == ' ')
18842 if (src[i] != ' ' && src[i] != '\n')
18849 // now append the newline and continuation sequence
18854 strncpy(dest+len, cseq, cseq_len);
18862 dest[len] = src[i];
18866 if (src[i] == '\n')
18871 if (dest && appData.debugMode)
18873 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18874 count, width, line, len, *lp);
18875 show_bytes(debugFP, src, count);
18876 fprintf(debugFP, "\ndest: ");
18877 show_bytes(debugFP, dest, len);
18878 fprintf(debugFP, "\n");
18880 *lp = dest ? line : old_line;
18885 // [HGM] vari: routines for shelving variations
18886 Boolean modeRestore = FALSE;
18889 PushInner (int firstMove, int lastMove)
18891 int i, j, nrMoves = lastMove - firstMove;
18893 // push current tail of game on stack
18894 savedResult[storedGames] = gameInfo.result;
18895 savedDetails[storedGames] = gameInfo.resultDetails;
18896 gameInfo.resultDetails = NULL;
18897 savedFirst[storedGames] = firstMove;
18898 savedLast [storedGames] = lastMove;
18899 savedFramePtr[storedGames] = framePtr;
18900 framePtr -= nrMoves; // reserve space for the boards
18901 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18902 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18903 for(j=0; j<MOVE_LEN; j++)
18904 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18905 for(j=0; j<2*MOVE_LEN; j++)
18906 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18907 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18908 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18909 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18910 pvInfoList[firstMove+i-1].depth = 0;
18911 commentList[framePtr+i] = commentList[firstMove+i];
18912 commentList[firstMove+i] = NULL;
18916 forwardMostMove = firstMove; // truncate game so we can start variation
18920 PushTail (int firstMove, int lastMove)
18922 if(appData.icsActive) { // only in local mode
18923 forwardMostMove = currentMove; // mimic old ICS behavior
18926 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18928 PushInner(firstMove, lastMove);
18929 if(storedGames == 1) GreyRevert(FALSE);
18930 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18934 PopInner (Boolean annotate)
18937 char buf[8000], moveBuf[20];
18939 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18940 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18941 nrMoves = savedLast[storedGames] - currentMove;
18944 if(!WhiteOnMove(currentMove))
18945 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18946 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18947 for(i=currentMove; i<forwardMostMove; i++) {
18949 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18950 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18951 strcat(buf, moveBuf);
18952 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18953 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18957 for(i=1; i<=nrMoves; i++) { // copy last variation back
18958 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18959 for(j=0; j<MOVE_LEN; j++)
18960 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18961 for(j=0; j<2*MOVE_LEN; j++)
18962 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18963 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18964 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18965 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18966 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18967 commentList[currentMove+i] = commentList[framePtr+i];
18968 commentList[framePtr+i] = NULL;
18970 if(annotate) AppendComment(currentMove+1, buf, FALSE);
18971 framePtr = savedFramePtr[storedGames];
18972 gameInfo.result = savedResult[storedGames];
18973 if(gameInfo.resultDetails != NULL) {
18974 free(gameInfo.resultDetails);
18976 gameInfo.resultDetails = savedDetails[storedGames];
18977 forwardMostMove = currentMove + nrMoves;
18981 PopTail (Boolean annotate)
18983 if(appData.icsActive) return FALSE; // only in local mode
18984 if(!storedGames) return FALSE; // sanity
18985 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18987 PopInner(annotate);
18988 if(currentMove < forwardMostMove) ForwardEvent(); else
18989 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18991 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18997 { // remove all shelved variations
18999 for(i=0; i<storedGames; i++) {
19000 if(savedDetails[i])
19001 free(savedDetails[i]);
19002 savedDetails[i] = NULL;
19004 for(i=framePtr; i<MAX_MOVES; i++) {
19005 if(commentList[i]) free(commentList[i]);
19006 commentList[i] = NULL;
19008 framePtr = MAX_MOVES-1;
19013 LoadVariation (int index, char *text)
19014 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
19015 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
19016 int level = 0, move;
19018 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
19019 // first find outermost bracketing variation
19020 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
19021 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
19022 if(*p == '{') wait = '}'; else
19023 if(*p == '[') wait = ']'; else
19024 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
19025 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
19027 if(*p == wait) wait = NULLCHAR; // closing ]} found
19030 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19031 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19032 end[1] = NULLCHAR; // clip off comment beyond variation
19033 ToNrEvent(currentMove-1);
19034 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19035 // kludge: use ParsePV() to append variation to game
19036 move = currentMove;
19037 ParsePV(start, TRUE, TRUE);
19038 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19039 ClearPremoveHighlights();
19041 ToNrEvent(currentMove+1);
19047 char *p, *q, buf[MSG_SIZ];
19048 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19049 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
19050 ParseArgsFromString(buf);
19051 ActivateTheme(TRUE); // also redo colors
19055 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19058 q = appData.themeNames;
19059 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
19060 if(appData.useBitmaps) {
19061 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
19062 appData.liteBackTextureFile, appData.darkBackTextureFile,
19063 appData.liteBackTextureMode,
19064 appData.darkBackTextureMode );
19066 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
19067 Col2Text(2), // lightSquareColor
19068 Col2Text(3) ); // darkSquareColor
19070 if(appData.useBorder) {
19071 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
19074 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
19076 if(appData.useFont) {
19077 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19078 appData.renderPiecesWithFont,
19079 appData.fontToPieceTable,
19080 Col2Text(9), // appData.fontBackColorWhite
19081 Col2Text(10) ); // appData.fontForeColorBlack
19083 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
19084 appData.pieceDirectory);
19085 if(!appData.pieceDirectory[0])
19086 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
19087 Col2Text(0), // whitePieceColor
19088 Col2Text(1) ); // blackPieceColor
19090 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19091 Col2Text(4), // highlightSquareColor
19092 Col2Text(5) ); // premoveHighlightColor
19093 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19094 if(insert != q) insert[-1] = NULLCHAR;
19095 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19098 ActivateTheme(FALSE);