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 */
264 int deadRanks, handSize, handOffsets;
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, *currentEngine[2]; // 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 ASSIGN(currentEngine[i], engineLine);
992 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
993 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
994 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
995 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
996 appData.firstProtocolVersion = PROTOVER;
997 ParseArgsFromString(buf);
999 ReplaceEngine(cps, i);
1000 FloatToFront(&appData.recentEngineList, engineLine);
1001 if(gameMode == BeginningOfGame) Reset(TRUE, TRUE);
1005 while(q = strchr(p, SLASH)) p = q+1;
1006 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1007 if(engineDir[0] != NULLCHAR) {
1008 ASSIGN(appData.directory[i], engineDir); p = engineName;
1009 } else if(p != engineName) { // derive directory from engine path, when not given
1011 ASSIGN(appData.directory[i], engineName);
1013 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1014 } else { ASSIGN(appData.directory[i], "."); }
1015 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1017 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1018 snprintf(command, MSG_SIZ, "%s %s", p, params);
1021 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1022 ASSIGN(appData.chessProgram[i], p);
1023 appData.isUCI[i] = isUCI;
1024 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1025 appData.hasOwnBookUCI[i] = hasBook;
1026 if(!nickName[0]) useNick = FALSE;
1027 if(useNick) ASSIGN(appData.pgnName[i], nickName);
1031 q = firstChessProgramNames;
1032 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1033 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1034 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s",
1035 quote, p, quote, appData.directory[i],
1036 useNick ? " -fn \"" : "",
1037 useNick ? nickName : "",
1038 useNick ? "\"" : "",
1039 v1 ? " -firstProtocolVersion 1" : "",
1040 hasBook ? "" : " -fNoOwnBookUCI",
1041 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1042 storeVariant ? " -variant " : "",
1043 storeVariant ? VariantName(gameInfo.variant) : "");
1044 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " %s", wbOptions);
1045 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 2);
1046 if(insert != q) insert[-1] = NULLCHAR;
1047 snprintf(firstChessProgramNames, len, "%s\n%s\n%s", q, buf, insert);
1049 FloatToFront(&appData.recentEngineList, buf);
1050 ASSIGN(currentEngine[i], buf);
1052 ReplaceEngine(cps, i);
1058 int matched, min, sec;
1060 * Parse timeControl resource
1062 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1063 appData.movesPerSession)) {
1065 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1066 DisplayFatalError(buf, 0, 2);
1070 * Parse searchTime resource
1072 if (*appData.searchTime != NULLCHAR) {
1073 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1075 searchTime = min * 60;
1076 } else if (matched == 2) {
1077 searchTime = min * 60 + sec;
1080 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1081 DisplayFatalError(buf, 0, 2);
1090 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1091 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1093 GetTimeMark(&programStartTime);
1094 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1095 appData.seedBase = random() + (random()<<15);
1096 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1098 ClearProgramStats();
1099 programStats.ok_to_send = 1;
1100 programStats.seen_stat = 0;
1103 * Initialize game list
1109 * Internet chess server status
1111 if (appData.icsActive) {
1112 appData.matchMode = FALSE;
1113 appData.matchGames = 0;
1115 appData.noChessProgram = !appData.zippyPlay;
1117 appData.zippyPlay = FALSE;
1118 appData.zippyTalk = FALSE;
1119 appData.noChessProgram = TRUE;
1121 if (*appData.icsHelper != NULLCHAR) {
1122 appData.useTelnet = TRUE;
1123 appData.telnetProgram = appData.icsHelper;
1126 appData.zippyTalk = appData.zippyPlay = FALSE;
1129 /* [AS] Initialize pv info list [HGM] and game state */
1133 for( i=0; i<=framePtr; i++ ) {
1134 pvInfoList[i].depth = -1;
1135 boards[i][EP_STATUS] = EP_NONE;
1136 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1142 /* [AS] Adjudication threshold */
1143 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1145 InitEngine(&first, 0);
1146 InitEngine(&second, 1);
1149 pairing.which = "pairing"; // pairing engine
1150 pairing.pr = NoProc;
1152 pairing.program = appData.pairingEngine;
1153 pairing.host = "localhost";
1156 if (appData.icsActive) {
1157 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1158 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1159 appData.clockMode = FALSE;
1160 first.sendTime = second.sendTime = 0;
1164 /* Override some settings from environment variables, for backward
1165 compatibility. Unfortunately it's not feasible to have the env
1166 vars just set defaults, at least in xboard. Ugh.
1168 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1173 if (!appData.icsActive) {
1177 /* Check for variants that are supported only in ICS mode,
1178 or not at all. Some that are accepted here nevertheless
1179 have bugs; see comments below.
1181 VariantClass variant = StringToVariant(appData.variant);
1183 case VariantBughouse: /* need four players and two boards */
1184 case VariantKriegspiel: /* need to hide pieces and move details */
1185 /* case VariantFischeRandom: (Fabien: moved below) */
1186 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1187 if( (len >= MSG_SIZ) && appData.debugMode )
1188 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1190 DisplayFatalError(buf, 0, 2);
1193 case VariantUnknown:
1194 case VariantLoadable:
1204 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1205 if( (len >= MSG_SIZ) && appData.debugMode )
1206 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1208 DisplayFatalError(buf, 0, 2);
1211 case VariantNormal: /* definitely works! */
1212 if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1213 safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1216 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1217 case VariantFairy: /* [HGM] TestLegality definitely off! */
1218 case VariantGothic: /* [HGM] should work */
1219 case VariantCapablanca: /* [HGM] should work */
1220 case VariantCourier: /* [HGM] initial forced moves not implemented */
1221 case VariantShogi: /* [HGM] could still mate with pawn drop */
1222 case VariantChu: /* [HGM] experimental */
1223 case VariantKnightmate: /* [HGM] should work */
1224 case VariantCylinder: /* [HGM] untested */
1225 case VariantFalcon: /* [HGM] untested */
1226 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1227 offboard interposition not understood */
1228 case VariantWildCastle: /* pieces not automatically shuffled */
1229 case VariantNoCastle: /* pieces not automatically shuffled */
1230 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1231 case VariantLosers: /* should work except for win condition,
1232 and doesn't know captures are mandatory */
1233 case VariantSuicide: /* should work except for win condition,
1234 and doesn't know captures are mandatory */
1235 case VariantGiveaway: /* should work except for win condition,
1236 and doesn't know captures are mandatory */
1237 case VariantTwoKings: /* should work */
1238 case VariantAtomic: /* should work except for win condition */
1239 case Variant3Check: /* should work except for win condition */
1240 case VariantShatranj: /* should work except for all win conditions */
1241 case VariantMakruk: /* should work except for draw countdown */
1242 case VariantASEAN : /* should work except for draw countdown */
1243 case VariantBerolina: /* might work if TestLegality is off */
1244 case VariantCapaRandom: /* should work */
1245 case VariantJanus: /* should work */
1246 case VariantSuper: /* experimental */
1247 case VariantGreat: /* experimental, requires legality testing to be off */
1248 case VariantSChess: /* S-Chess, should work */
1249 case VariantGrand: /* should work */
1250 case VariantSpartan: /* should work */
1251 case VariantLion: /* should work */
1252 case VariantChuChess: /* should work */
1260 NextIntegerFromString (char ** str, long * value)
1265 while( *s == ' ' || *s == '\t' ) {
1271 if( *s >= '0' && *s <= '9' ) {
1272 while( *s >= '0' && *s <= '9' ) {
1273 *value = *value * 10 + (*s - '0');
1286 NextTimeControlFromString (char ** str, long * value)
1289 int result = NextIntegerFromString( str, &temp );
1292 *value = temp * 60; /* Minutes */
1293 if( **str == ':' ) {
1295 result = NextIntegerFromString( str, &temp );
1296 *value += temp; /* Seconds */
1304 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1305 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1306 int result = -1, type = 0; long temp, temp2;
1308 if(**str != ':') return -1; // old params remain in force!
1310 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1311 if( NextIntegerFromString( str, &temp ) ) return -1;
1312 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1315 /* time only: incremental or sudden-death time control */
1316 if(**str == '+') { /* increment follows; read it */
1318 if(**str == '!') type = *(*str)++; // Bronstein TC
1319 if(result = NextIntegerFromString( str, &temp2)) return -1;
1320 *inc = temp2 * 1000;
1321 if(**str == '.') { // read fraction of increment
1322 char *start = ++(*str);
1323 if(result = NextIntegerFromString( str, &temp2)) return -1;
1325 while(start++ < *str) temp2 /= 10;
1329 *moves = 0; *tc = temp * 1000; *incType = type;
1333 (*str)++; /* classical time control */
1334 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1346 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1347 { /* [HGM] get time to add from the multi-session time-control string */
1348 int incType, moves=1; /* kludge to force reading of first session */
1349 long time, increment;
1352 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1354 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1355 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1356 if(movenr == -1) return time; /* last move before new session */
1357 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1358 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1359 if(!moves) return increment; /* current session is incremental */
1360 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1361 } while(movenr >= -1); /* try again for next session */
1363 return 0; // no new time quota on this move
1367 ParseTimeControl (char *tc, float ti, int mps)
1371 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1374 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1375 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1376 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1380 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1382 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1385 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1387 snprintf(buf, MSG_SIZ, ":%s", mytc);
1389 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1391 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1396 /* Parse second time control */
1399 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1407 timeControl_2 = tc2 * 1000;
1417 timeControl = tc1 * 1000;
1420 timeIncrement = ti * 1000; /* convert to ms */
1421 movesPerSession = 0;
1424 movesPerSession = mps;
1432 if (appData.debugMode) {
1433 # ifdef __GIT_VERSION
1434 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1436 fprintf(debugFP, "Version: %s\n", programVersion);
1439 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1441 set_cont_sequence(appData.wrapContSeq);
1442 if (appData.matchGames > 0) {
1443 appData.matchMode = TRUE;
1444 } else if (appData.matchMode) {
1445 appData.matchGames = 1;
1447 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1448 appData.matchGames = appData.sameColorGames;
1449 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1450 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1451 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1454 if (appData.noChessProgram || first.protocolVersion == 1) {
1457 /* kludge: allow timeout for initial "feature" commands */
1459 DisplayMessage("", _("Starting chess program"));
1460 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1465 CalculateIndex (int index, int gameNr)
1466 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1468 if(index > 0) return index; // fixed nmber
1469 if(index == 0) return 1;
1470 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1471 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1476 LoadGameOrPosition (int gameNr)
1477 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1478 if (*appData.loadGameFile != NULLCHAR) {
1479 if (!LoadGameFromFile(appData.loadGameFile,
1480 CalculateIndex(appData.loadGameIndex, gameNr),
1481 appData.loadGameFile, FALSE)) {
1482 DisplayFatalError(_("Bad game file"), 0, 1);
1485 } else if (*appData.loadPositionFile != NULLCHAR) {
1486 if (!LoadPositionFromFile(appData.loadPositionFile,
1487 CalculateIndex(appData.loadPositionIndex, gameNr),
1488 appData.loadPositionFile)) {
1489 DisplayFatalError(_("Bad position file"), 0, 1);
1497 ReserveGame (int gameNr, char resChar)
1499 FILE *tf = fopen(appData.tourneyFile, "r+");
1500 char *p, *q, c, buf[MSG_SIZ];
1501 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1502 safeStrCpy(buf, lastMsg, MSG_SIZ);
1503 DisplayMessage(_("Pick new game"), "");
1504 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1505 ParseArgsFromFile(tf);
1506 p = q = appData.results;
1507 if(appData.debugMode) {
1508 char *r = appData.participants;
1509 fprintf(debugFP, "results = '%s'\n", p);
1510 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1511 fprintf(debugFP, "\n");
1513 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1515 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1516 safeStrCpy(q, p, strlen(p) + 2);
1517 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1518 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1519 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1520 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1523 fseek(tf, -(strlen(p)+4), SEEK_END);
1525 if(c != '"') // depending on DOS or Unix line endings we can be one off
1526 fseek(tf, -(strlen(p)+2), SEEK_END);
1527 else fseek(tf, -(strlen(p)+3), SEEK_END);
1528 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1529 DisplayMessage(buf, "");
1530 free(p); appData.results = q;
1531 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1532 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1533 int round = appData.defaultMatchGames * appData.tourneyType;
1534 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1535 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1536 UnloadEngine(&first); // next game belongs to other pairing;
1537 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1539 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1543 MatchEvent (int mode)
1544 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1546 if(matchMode) { // already in match mode: switch it off
1548 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1551 // if(gameMode != BeginningOfGame) {
1552 // DisplayError(_("You can only start a match from the initial position."), 0);
1556 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1557 /* Set up machine vs. machine match */
1559 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1560 if(appData.tourneyFile[0]) {
1562 if(nextGame > appData.matchGames) {
1564 if(strchr(appData.results, '*') == NULL) {
1566 appData.tourneyCycles++;
1567 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1569 NextTourneyGame(-1, &dummy);
1571 if(nextGame <= appData.matchGames) {
1572 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1574 ScheduleDelayedEvent(NextMatchGame, 10000);
1579 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1580 DisplayError(buf, 0);
1581 appData.tourneyFile[0] = 0;
1585 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1586 DisplayFatalError(_("Can't have a match with no chess programs"),
1591 matchGame = roundNr = 1;
1592 first.matchWins = second.matchWins = totalTime = 0; // [HGM] match: needed in later matches
1597 InitBackEnd3 P((void))
1599 GameMode initialMode;
1603 ParseFeatures(appData.features[0], &first);
1604 if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode && // mode involves only first engine
1605 !strcmp(appData.variant, "normal") && // no explicit variant request
1606 appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 && // no size overrides requested
1607 !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") && // but 'normal' won't work with engine
1608 !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1609 char c, *q = first.variants, *p = strchr(q, ',');
1610 if(p) *p = NULLCHAR;
1611 if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1613 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1614 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1615 ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1616 Reset(TRUE, FALSE); // and re-initialize
1621 InitChessProgram(&first, startedFromSetupPosition);
1623 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1624 free(programVersion);
1625 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1626 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1627 FloatToFront(&appData.recentEngineList, currentEngine[0] ? currentEngine[0] : appData.firstChessProgram);
1630 if (appData.icsActive) {
1632 /* [DM] Make a console window if needed [HGM] merged ifs */
1638 if (*appData.icsCommPort != NULLCHAR)
1639 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1640 appData.icsCommPort);
1642 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1643 appData.icsHost, appData.icsPort);
1645 if( (len >= MSG_SIZ) && appData.debugMode )
1646 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1648 DisplayFatalError(buf, err, 1);
1653 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1655 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1656 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1657 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1658 } else if (appData.noChessProgram) {
1664 if (*appData.cmailGameName != NULLCHAR) {
1666 OpenLoopback(&cmailPR);
1668 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1672 DisplayMessage("", "");
1673 if (StrCaseCmp(appData.initialMode, "") == 0) {
1674 initialMode = BeginningOfGame;
1675 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1676 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1677 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1678 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1681 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1682 initialMode = TwoMachinesPlay;
1683 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1684 initialMode = AnalyzeFile;
1685 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1686 initialMode = AnalyzeMode;
1687 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1688 initialMode = MachinePlaysWhite;
1689 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1690 initialMode = MachinePlaysBlack;
1691 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1692 initialMode = EditGame;
1693 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1694 initialMode = EditPosition;
1695 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1696 initialMode = Training;
1698 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1699 if( (len >= MSG_SIZ) && appData.debugMode )
1700 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1702 DisplayFatalError(buf, 0, 2);
1706 if (appData.matchMode) {
1707 if(appData.tourneyFile[0]) { // start tourney from command line
1709 if(f = fopen(appData.tourneyFile, "r")) {
1710 ParseArgsFromFile(f); // make sure tourney parmeters re known
1712 appData.clockMode = TRUE;
1714 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1717 } else if (*appData.cmailGameName != NULLCHAR) {
1718 /* Set up cmail mode */
1719 ReloadCmailMsgEvent(TRUE);
1721 /* Set up other modes */
1722 if (initialMode == AnalyzeFile) {
1723 if (*appData.loadGameFile == NULLCHAR) {
1724 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1728 if (*appData.loadGameFile != NULLCHAR) {
1729 (void) LoadGameFromFile(appData.loadGameFile,
1730 appData.loadGameIndex,
1731 appData.loadGameFile, TRUE);
1732 } else if (*appData.loadPositionFile != NULLCHAR) {
1733 (void) LoadPositionFromFile(appData.loadPositionFile,
1734 appData.loadPositionIndex,
1735 appData.loadPositionFile);
1736 /* [HGM] try to make self-starting even after FEN load */
1737 /* to allow automatic setup of fairy variants with wtm */
1738 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1739 gameMode = BeginningOfGame;
1740 setboardSpoiledMachineBlack = 1;
1742 /* [HGM] loadPos: make that every new game uses the setup */
1743 /* from file as long as we do not switch variant */
1744 if(!blackPlaysFirst) {
1745 startedFromPositionFile = TRUE;
1746 CopyBoard(filePosition, boards[0]);
1747 CopyBoard(initialPosition, boards[0]);
1749 } else if(*appData.fen != NULLCHAR) {
1750 if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
1751 startedFromPositionFile = TRUE;
1755 if (initialMode == AnalyzeMode) {
1756 if (appData.noChessProgram) {
1757 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1760 if (appData.icsActive) {
1761 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1765 } else if (initialMode == AnalyzeFile) {
1766 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1767 ShowThinkingEvent();
1769 AnalysisPeriodicEvent(1);
1770 } else if (initialMode == MachinePlaysWhite) {
1771 if (appData.noChessProgram) {
1772 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1776 if (appData.icsActive) {
1777 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1781 MachineWhiteEvent();
1782 } else if (initialMode == MachinePlaysBlack) {
1783 if (appData.noChessProgram) {
1784 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1788 if (appData.icsActive) {
1789 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1793 MachineBlackEvent();
1794 } else if (initialMode == TwoMachinesPlay) {
1795 if (appData.noChessProgram) {
1796 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1800 if (appData.icsActive) {
1801 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1806 } else if (initialMode == EditGame) {
1808 } else if (initialMode == EditPosition) {
1809 EditPositionEvent();
1810 } else if (initialMode == Training) {
1811 if (*appData.loadGameFile == NULLCHAR) {
1812 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1821 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1823 DisplayBook(current+1);
1825 MoveHistorySet( movelist, first, last, current, pvInfoList );
1827 EvalGraphSet( first, last, current, pvInfoList );
1829 MakeEngineOutputTitle();
1833 * Establish will establish a contact to a remote host.port.
1834 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1835 * used to talk to the host.
1836 * Returns 0 if okay, error code if not.
1843 if (*appData.icsCommPort != NULLCHAR) {
1844 /* Talk to the host through a serial comm port */
1845 return OpenCommPort(appData.icsCommPort, &icsPR);
1847 } else if (*appData.gateway != NULLCHAR) {
1848 if (*appData.remoteShell == NULLCHAR) {
1849 /* Use the rcmd protocol to run telnet program on a gateway host */
1850 snprintf(buf, sizeof(buf), "%s %s %s",
1851 appData.telnetProgram, appData.icsHost, appData.icsPort);
1852 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1855 /* Use the rsh program to run telnet program on a gateway host */
1856 if (*appData.remoteUser == NULLCHAR) {
1857 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1858 appData.gateway, appData.telnetProgram,
1859 appData.icsHost, appData.icsPort);
1861 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1862 appData.remoteShell, appData.gateway,
1863 appData.remoteUser, appData.telnetProgram,
1864 appData.icsHost, appData.icsPort);
1866 return StartChildProcess(buf, "", &icsPR);
1869 } else if (appData.useTelnet) {
1870 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1873 /* TCP socket interface differs somewhat between
1874 Unix and NT; handle details in the front end.
1876 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1881 EscapeExpand (char *p, char *q)
1882 { // [HGM] initstring: routine to shape up string arguments
1883 while(*p++ = *q++) if(p[-1] == '\\')
1885 case 'n': p[-1] = '\n'; break;
1886 case 'r': p[-1] = '\r'; break;
1887 case 't': p[-1] = '\t'; break;
1888 case '\\': p[-1] = '\\'; break;
1889 case 0: *p = 0; return;
1890 default: p[-1] = q[-1]; break;
1895 show_bytes (FILE *fp, char *buf, int count)
1898 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1899 fprintf(fp, "\\%03o", *buf & 0xff);
1908 /* Returns an errno value */
1910 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1912 char buf[8192], *p, *q, *buflim;
1913 int left, newcount, outcount;
1915 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1916 *appData.gateway != NULLCHAR) {
1917 if (appData.debugMode) {
1918 fprintf(debugFP, ">ICS: ");
1919 show_bytes(debugFP, message, count);
1920 fprintf(debugFP, "\n");
1922 return OutputToProcess(pr, message, count, outError);
1925 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1932 if (appData.debugMode) {
1933 fprintf(debugFP, ">ICS: ");
1934 show_bytes(debugFP, buf, newcount);
1935 fprintf(debugFP, "\n");
1937 outcount = OutputToProcess(pr, buf, newcount, outError);
1938 if (outcount < newcount) return -1; /* to be sure */
1945 } else if (((unsigned char) *p) == TN_IAC) {
1946 *q++ = (char) TN_IAC;
1953 if (appData.debugMode) {
1954 fprintf(debugFP, ">ICS: ");
1955 show_bytes(debugFP, buf, newcount);
1956 fprintf(debugFP, "\n");
1958 outcount = OutputToProcess(pr, buf, newcount, outError);
1959 if (outcount < newcount) return -1; /* to be sure */
1964 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1966 int outError, outCount;
1967 static int gotEof = 0;
1970 /* Pass data read from player on to ICS */
1973 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1974 if (outCount < count) {
1975 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1977 if(have_sent_ICS_logon == 2) {
1978 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1979 fprintf(ini, "%s", message);
1980 have_sent_ICS_logon = 3;
1982 have_sent_ICS_logon = 1;
1983 } else if(have_sent_ICS_logon == 3) {
1984 fprintf(ini, "%s", message);
1986 have_sent_ICS_logon = 1;
1988 } else if (count < 0) {
1989 RemoveInputSource(isr);
1990 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1991 } else if (gotEof++ > 0) {
1992 RemoveInputSource(isr);
1993 DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
1999 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
2000 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
2001 connectionAlive = FALSE; // only sticks if no response to 'date' command.
2002 SendToICS("date\n");
2003 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
2006 /* added routine for printf style output to ics */
2008 ics_printf (char *format, ...)
2010 char buffer[MSG_SIZ];
2013 va_start(args, format);
2014 vsnprintf(buffer, sizeof(buffer), format, args);
2015 buffer[sizeof(buffer)-1] = '\0';
2023 int count, outCount, outError;
2025 if (icsPR == NoProc) return;
2028 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2029 if (outCount < count) {
2030 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2034 /* This is used for sending logon scripts to the ICS. Sending
2035 without a delay causes problems when using timestamp on ICC
2036 (at least on my machine). */
2038 SendToICSDelayed (char *s, long msdelay)
2040 int count, outCount, outError;
2042 if (icsPR == NoProc) return;
2045 if (appData.debugMode) {
2046 fprintf(debugFP, ">ICS: ");
2047 show_bytes(debugFP, s, count);
2048 fprintf(debugFP, "\n");
2050 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2052 if (outCount < count) {
2053 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2058 /* Remove all highlighting escape sequences in s
2059 Also deletes any suffix starting with '('
2062 StripHighlightAndTitle (char *s)
2064 static char retbuf[MSG_SIZ];
2067 while (*s != NULLCHAR) {
2068 while (*s == '\033') {
2069 while (*s != NULLCHAR && !isalpha(*s)) s++;
2070 if (*s != NULLCHAR) s++;
2072 while (*s != NULLCHAR && *s != '\033') {
2073 if (*s == '(' || *s == '[') {
2084 /* Remove all highlighting escape sequences in s */
2086 StripHighlight (char *s)
2088 static char retbuf[MSG_SIZ];
2091 while (*s != NULLCHAR) {
2092 while (*s == '\033') {
2093 while (*s != NULLCHAR && !isalpha(*s)) s++;
2094 if (*s != NULLCHAR) s++;
2096 while (*s != NULLCHAR && *s != '\033') {
2104 char engineVariant[MSG_SIZ];
2105 char *variantNames[] = VARIANT_NAMES;
2107 VariantName (VariantClass v)
2109 if(v == VariantUnknown || *engineVariant) return engineVariant;
2110 return variantNames[v];
2114 /* Identify a variant from the strings the chess servers use or the
2115 PGN Variant tag names we use. */
2117 StringToVariant (char *e)
2121 VariantClass v = VariantNormal;
2122 int i, found = FALSE;
2123 char buf[MSG_SIZ], c;
2128 /* [HGM] skip over optional board-size prefixes */
2129 if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2130 sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2131 while( *e++ != '_');
2134 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2138 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2139 if (p = StrCaseStr(e, variantNames[i])) {
2140 if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2141 v = (VariantClass) i;
2148 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2149 || StrCaseStr(e, "wild/fr")
2150 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2151 v = VariantFischeRandom;
2152 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2153 (i = 1, p = StrCaseStr(e, "w"))) {
2155 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2162 case 0: /* FICS only, actually */
2164 /* Castling legal even if K starts on d-file */
2165 v = VariantWildCastle;
2170 /* Castling illegal even if K & R happen to start in
2171 normal positions. */
2172 v = VariantNoCastle;
2185 /* Castling legal iff K & R start in normal positions */
2191 /* Special wilds for position setup; unclear what to do here */
2192 v = VariantLoadable;
2195 /* Bizarre ICC game */
2196 v = VariantTwoKings;
2199 v = VariantKriegspiel;
2205 v = VariantFischeRandom;
2208 v = VariantCrazyhouse;
2211 v = VariantBughouse;
2217 /* Not quite the same as FICS suicide! */
2218 v = VariantGiveaway;
2224 v = VariantShatranj;
2227 /* Temporary names for future ICC types. The name *will* change in
2228 the next xboard/WinBoard release after ICC defines it. */
2266 v = VariantCapablanca;
2269 v = VariantKnightmate;
2275 v = VariantCylinder;
2281 v = VariantCapaRandom;
2284 v = VariantBerolina;
2296 /* Found "wild" or "w" in the string but no number;
2297 must assume it's normal chess. */
2301 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2302 if( (len >= MSG_SIZ) && appData.debugMode )
2303 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2305 DisplayError(buf, 0);
2311 if (appData.debugMode) {
2312 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2313 e, wnum, VariantName(v));
2318 static int leftover_start = 0, leftover_len = 0;
2319 char star_match[STAR_MATCH_N][MSG_SIZ];
2321 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2322 advance *index beyond it, and set leftover_start to the new value of
2323 *index; else return FALSE. If pattern contains the character '*', it
2324 matches any sequence of characters not containing '\r', '\n', or the
2325 character following the '*' (if any), and the matched sequence(s) are
2326 copied into star_match.
2329 looking_at ( char *buf, int *index, char *pattern)
2331 char *bufp = &buf[*index], *patternp = pattern;
2333 char *matchp = star_match[0];
2336 if (*patternp == NULLCHAR) {
2337 *index = leftover_start = bufp - buf;
2341 if (*bufp == NULLCHAR) return FALSE;
2342 if (*patternp == '*') {
2343 if (*bufp == *(patternp + 1)) {
2345 matchp = star_match[++star_count];
2349 } else if (*bufp == '\n' || *bufp == '\r') {
2351 if (*patternp == NULLCHAR)
2356 *matchp++ = *bufp++;
2360 if (*patternp != *bufp) return FALSE;
2367 SendToPlayer (char *data, int length)
2369 int error, outCount;
2370 outCount = OutputToProcess(NoProc, data, length, &error);
2371 if (outCount < length) {
2372 DisplayFatalError(_("Error writing to display"), error, 1);
2377 PackHolding (char packed[], char *holding)
2387 switch (runlength) {
2398 sprintf(q, "%d", runlength);
2410 /* Telnet protocol requests from the front end */
2412 TelnetRequest (unsigned char ddww, unsigned char option)
2414 unsigned char msg[3];
2415 int outCount, outError;
2417 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2419 if (appData.debugMode) {
2420 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2436 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2445 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2448 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2453 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2455 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2462 if (!appData.icsActive) return;
2463 TelnetRequest(TN_DO, TN_ECHO);
2469 if (!appData.icsActive) return;
2470 TelnetRequest(TN_DONT, TN_ECHO);
2474 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2476 /* put the holdings sent to us by the server on the board holdings area */
2477 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2481 if(gameInfo.holdingsWidth < 2) return;
2482 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2483 return; // prevent overwriting by pre-board holdings
2485 if( (int)lowestPiece >= BlackPawn ) {
2488 holdingsStartRow = handSize-1;
2491 holdingsColumn = BOARD_WIDTH-1;
2492 countsColumn = BOARD_WIDTH-2;
2493 holdingsStartRow = 0;
2497 for(i=0; i<BOARD_RANKS-1; i++) { /* clear holdings */
2498 board[i][holdingsColumn] = EmptySquare;
2499 board[i][countsColumn] = (ChessSquare) 0;
2501 while( (p=*holdings++) != NULLCHAR ) {
2502 piece = CharToPiece( ToUpper(p) );
2503 if(piece == EmptySquare) continue;
2504 /*j = (int) piece - (int) WhitePawn;*/
2505 j = PieceToNumber(piece);
2506 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2507 if(j < 0) continue; /* should not happen */
2508 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2509 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2510 board[holdingsStartRow+j*direction][countsColumn]++;
2516 VariantSwitch (Board board, VariantClass newVariant)
2518 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2519 static Board oldBoard;
2521 startedFromPositionFile = FALSE;
2522 if(gameInfo.variant == newVariant) return;
2524 /* [HGM] This routine is called each time an assignment is made to
2525 * gameInfo.variant during a game, to make sure the board sizes
2526 * are set to match the new variant. If that means adding or deleting
2527 * holdings, we shift the playing board accordingly
2528 * This kludge is needed because in ICS observe mode, we get boards
2529 * of an ongoing game without knowing the variant, and learn about the
2530 * latter only later. This can be because of the move list we requested,
2531 * in which case the game history is refilled from the beginning anyway,
2532 * but also when receiving holdings of a crazyhouse game. In the latter
2533 * case we want to add those holdings to the already received position.
2537 if (appData.debugMode) {
2538 fprintf(debugFP, "Switch board from %s to %s\n",
2539 VariantName(gameInfo.variant), VariantName(newVariant));
2540 setbuf(debugFP, NULL);
2542 shuffleOpenings = 0; /* [HGM] shuffle */
2543 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2547 newWidth = 9; newHeight = 9;
2548 gameInfo.holdingsSize = 7;
2549 case VariantBughouse:
2550 case VariantCrazyhouse:
2551 newHoldingsWidth = 2; break;
2555 newHoldingsWidth = 2;
2556 gameInfo.holdingsSize = 8;
2559 case VariantCapablanca:
2560 case VariantCapaRandom:
2563 newHoldingsWidth = gameInfo.holdingsSize = 0;
2566 if(newWidth != gameInfo.boardWidth ||
2567 newHeight != gameInfo.boardHeight ||
2568 newHoldingsWidth != gameInfo.holdingsWidth ) {
2570 /* shift position to new playing area, if needed */
2571 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2572 for(i=0; i<BOARD_HEIGHT; i++)
2573 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2574 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2576 for(i=0; i<newHeight; i++) {
2577 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2578 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2580 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2581 for(i=0; i<BOARD_HEIGHT; i++)
2582 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2583 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2586 board[HOLDINGS_SET] = 0;
2587 gameInfo.boardWidth = newWidth;
2588 gameInfo.boardHeight = newHeight;
2589 gameInfo.holdingsWidth = newHoldingsWidth;
2590 gameInfo.variant = newVariant;
2591 InitDrawingSizes(-2, 0);
2592 } else gameInfo.variant = newVariant;
2593 CopyBoard(oldBoard, board); // remember correctly formatted board
2594 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2595 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2598 static int loggedOn = FALSE;
2600 /*-- Game start info cache: --*/
2602 char gs_kind[MSG_SIZ];
2603 static char player1Name[128] = "";
2604 static char player2Name[128] = "";
2605 static char cont_seq[] = "\n\\ ";
2606 static int player1Rating = -1;
2607 static int player2Rating = -1;
2608 /*----------------------------*/
2610 ColorClass curColor = ColorNormal;
2611 int suppressKibitz = 0;
2614 Boolean soughtPending = FALSE;
2615 Boolean seekGraphUp;
2616 #define MAX_SEEK_ADS 200
2618 char *seekAdList[MAX_SEEK_ADS];
2619 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2620 float tcList[MAX_SEEK_ADS];
2621 char colorList[MAX_SEEK_ADS];
2622 int nrOfSeekAds = 0;
2623 int minRating = 1010, maxRating = 2800;
2624 int hMargin = 10, vMargin = 20, h, w;
2625 extern int squareSize, lineGap;
2630 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2631 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2632 if(r < minRating+100 && r >=0 ) r = minRating+100;
2633 if(r > maxRating) r = maxRating;
2634 if(tc < 1.f) tc = 1.f;
2635 if(tc > 95.f) tc = 95.f;
2636 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2637 y = ((double)r - minRating)/(maxRating - minRating)
2638 * (h-vMargin-squareSize/8-1) + vMargin;
2639 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2640 if(strstr(seekAdList[i], " u ")) color = 1;
2641 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2642 !strstr(seekAdList[i], "bullet") &&
2643 !strstr(seekAdList[i], "blitz") &&
2644 !strstr(seekAdList[i], "standard") ) color = 2;
2645 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2646 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2650 PlotSingleSeekAd (int i)
2656 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2658 char buf[MSG_SIZ], *ext = "";
2659 VariantClass v = StringToVariant(type);
2660 if(strstr(type, "wild")) {
2661 ext = type + 4; // append wild number
2662 if(v == VariantFischeRandom) type = "chess960"; else
2663 if(v == VariantLoadable) type = "setup"; else
2664 type = VariantName(v);
2666 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2667 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2668 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2669 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2670 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2671 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2672 seekNrList[nrOfSeekAds] = nr;
2673 zList[nrOfSeekAds] = 0;
2674 seekAdList[nrOfSeekAds++] = StrSave(buf);
2675 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2680 EraseSeekDot (int i)
2682 int x = xList[i], y = yList[i], d=squareSize/4, k;
2683 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2684 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2685 // now replot every dot that overlapped
2686 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2687 int xx = xList[k], yy = yList[k];
2688 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2689 DrawSeekDot(xx, yy, colorList[k]);
2694 RemoveSeekAd (int nr)
2697 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2699 if(seekAdList[i]) free(seekAdList[i]);
2700 seekAdList[i] = seekAdList[--nrOfSeekAds];
2701 seekNrList[i] = seekNrList[nrOfSeekAds];
2702 ratingList[i] = ratingList[nrOfSeekAds];
2703 colorList[i] = colorList[nrOfSeekAds];
2704 tcList[i] = tcList[nrOfSeekAds];
2705 xList[i] = xList[nrOfSeekAds];
2706 yList[i] = yList[nrOfSeekAds];
2707 zList[i] = zList[nrOfSeekAds];
2708 seekAdList[nrOfSeekAds] = NULL;
2714 MatchSoughtLine (char *line)
2716 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2717 int nr, base, inc, u=0; char dummy;
2719 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2720 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2722 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2723 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2724 // match: compact and save the line
2725 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2735 if(!seekGraphUp) return FALSE;
2736 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2737 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap + 2*border;
2739 DrawSeekBackground(0, 0, w, h);
2740 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2741 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2742 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2743 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2745 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2748 snprintf(buf, MSG_SIZ, "%d", i);
2749 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2752 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2753 for(i=1; i<100; i+=(i<10?1:5)) {
2754 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2755 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2756 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2758 snprintf(buf, MSG_SIZ, "%d", i);
2759 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2762 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2767 SeekGraphClick (ClickType click, int x, int y, int moving)
2769 static int lastDown = 0, displayed = 0, lastSecond;
2770 if(y < 0) return FALSE;
2771 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2772 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2773 if(!seekGraphUp) return FALSE;
2774 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2775 DrawPosition(TRUE, NULL);
2778 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2779 if(click == Release || moving) return FALSE;
2781 soughtPending = TRUE;
2782 SendToICS(ics_prefix);
2783 SendToICS("sought\n"); // should this be "sought all"?
2784 } else { // issue challenge based on clicked ad
2785 int dist = 10000; int i, closest = 0, second = 0;
2786 for(i=0; i<nrOfSeekAds; i++) {
2787 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2788 if(d < dist) { dist = d; closest = i; }
2789 second += (d - zList[i] < 120); // count in-range ads
2790 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2794 second = (second > 1);
2795 if(displayed != closest || second != lastSecond) {
2796 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2797 lastSecond = second; displayed = closest;
2799 if(click == Press) {
2800 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2803 } // on press 'hit', only show info
2804 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2805 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2806 SendToICS(ics_prefix);
2808 return TRUE; // let incoming board of started game pop down the graph
2809 } else if(click == Release) { // release 'miss' is ignored
2810 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2811 if(moving == 2) { // right up-click
2812 nrOfSeekAds = 0; // refresh graph
2813 soughtPending = TRUE;
2814 SendToICS(ics_prefix);
2815 SendToICS("sought\n"); // should this be "sought all"?
2818 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2819 // press miss or release hit 'pop down' seek graph
2820 seekGraphUp = FALSE;
2821 DrawPosition(TRUE, NULL);
2827 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2829 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2830 #define STARTED_NONE 0
2831 #define STARTED_MOVES 1
2832 #define STARTED_BOARD 2
2833 #define STARTED_OBSERVE 3
2834 #define STARTED_HOLDINGS 4
2835 #define STARTED_CHATTER 5
2836 #define STARTED_COMMENT 6
2837 #define STARTED_MOVES_NOHIDE 7
2839 static int started = STARTED_NONE;
2840 static char parse[20000];
2841 static int parse_pos = 0;
2842 static char buf[BUF_SIZE + 1];
2843 static int firstTime = TRUE, intfSet = FALSE;
2844 static ColorClass prevColor = ColorNormal;
2845 static int savingComment = FALSE;
2846 static int cmatch = 0; // continuation sequence match
2853 int backup; /* [DM] For zippy color lines */
2855 char talker[MSG_SIZ]; // [HGM] chat
2856 int channel, collective=0;
2858 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2860 if (appData.debugMode) {
2862 fprintf(debugFP, "<ICS: ");
2863 show_bytes(debugFP, data, count);
2864 fprintf(debugFP, "\n");
2868 if (appData.debugMode) { int f = forwardMostMove;
2869 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2870 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2871 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2874 /* If last read ended with a partial line that we couldn't parse,
2875 prepend it to the new read and try again. */
2876 if (leftover_len > 0) {
2877 for (i=0; i<leftover_len; i++)
2878 buf[i] = buf[leftover_start + i];
2881 /* copy new characters into the buffer */
2882 bp = buf + leftover_len;
2883 buf_len=leftover_len;
2884 for (i=0; i<count; i++)
2887 if (data[i] == '\r')
2890 // join lines split by ICS?
2891 if (!appData.noJoin)
2894 Joining just consists of finding matches against the
2895 continuation sequence, and discarding that sequence
2896 if found instead of copying it. So, until a match
2897 fails, there's nothing to do since it might be the
2898 complete sequence, and thus, something we don't want
2901 if (data[i] == cont_seq[cmatch])
2904 if (cmatch == strlen(cont_seq))
2906 cmatch = 0; // complete match. just reset the counter
2909 it's possible for the ICS to not include the space
2910 at the end of the last word, making our [correct]
2911 join operation fuse two separate words. the server
2912 does this when the space occurs at the width setting.
2914 if (!buf_len || buf[buf_len-1] != ' ')
2925 match failed, so we have to copy what matched before
2926 falling through and copying this character. In reality,
2927 this will only ever be just the newline character, but
2928 it doesn't hurt to be precise.
2930 strncpy(bp, cont_seq, cmatch);
2942 buf[buf_len] = NULLCHAR;
2943 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2948 while (i < buf_len) {
2949 /* Deal with part of the TELNET option negotiation
2950 protocol. We refuse to do anything beyond the
2951 defaults, except that we allow the WILL ECHO option,
2952 which ICS uses to turn off password echoing when we are
2953 directly connected to it. We reject this option
2954 if localLineEditing mode is on (always on in xboard)
2955 and we are talking to port 23, which might be a real
2956 telnet server that will try to keep WILL ECHO on permanently.
2958 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2959 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2960 unsigned char option;
2962 switch ((unsigned char) buf[++i]) {
2964 if (appData.debugMode)
2965 fprintf(debugFP, "\n<WILL ");
2966 switch (option = (unsigned char) buf[++i]) {
2968 if (appData.debugMode)
2969 fprintf(debugFP, "ECHO ");
2970 /* Reply only if this is a change, according
2971 to the protocol rules. */
2972 if (remoteEchoOption) break;
2973 if (appData.localLineEditing &&
2974 atoi(appData.icsPort) == TN_PORT) {
2975 TelnetRequest(TN_DONT, TN_ECHO);
2978 TelnetRequest(TN_DO, TN_ECHO);
2979 remoteEchoOption = TRUE;
2983 if (appData.debugMode)
2984 fprintf(debugFP, "%d ", option);
2985 /* Whatever this is, we don't want it. */
2986 TelnetRequest(TN_DONT, option);
2991 if (appData.debugMode)
2992 fprintf(debugFP, "\n<WONT ");
2993 switch (option = (unsigned char) buf[++i]) {
2995 if (appData.debugMode)
2996 fprintf(debugFP, "ECHO ");
2997 /* Reply only if this is a change, according
2998 to the protocol rules. */
2999 if (!remoteEchoOption) break;
3001 TelnetRequest(TN_DONT, TN_ECHO);
3002 remoteEchoOption = FALSE;
3005 if (appData.debugMode)
3006 fprintf(debugFP, "%d ", (unsigned char) option);
3007 /* Whatever this is, it must already be turned
3008 off, because we never agree to turn on
3009 anything non-default, so according to the
3010 protocol rules, we don't reply. */
3015 if (appData.debugMode)
3016 fprintf(debugFP, "\n<DO ");
3017 switch (option = (unsigned char) buf[++i]) {
3019 /* Whatever this is, we refuse to do it. */
3020 if (appData.debugMode)
3021 fprintf(debugFP, "%d ", option);
3022 TelnetRequest(TN_WONT, option);
3027 if (appData.debugMode)
3028 fprintf(debugFP, "\n<DONT ");
3029 switch (option = (unsigned char) buf[++i]) {
3031 if (appData.debugMode)
3032 fprintf(debugFP, "%d ", option);
3033 /* Whatever this is, we are already not doing
3034 it, because we never agree to do anything
3035 non-default, so according to the protocol
3036 rules, we don't reply. */
3041 if (appData.debugMode)
3042 fprintf(debugFP, "\n<IAC ");
3043 /* Doubled IAC; pass it through */
3047 if (appData.debugMode)
3048 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3049 /* Drop all other telnet commands on the floor */
3052 if (oldi > next_out)
3053 SendToPlayer(&buf[next_out], oldi - next_out);
3059 /* OK, this at least will *usually* work */
3060 if (!loggedOn && looking_at(buf, &i, "ics%")) {
3064 if (loggedOn && !intfSet) {
3065 if (ics_type == ICS_ICC) {
3066 snprintf(str, MSG_SIZ,
3067 "/set-quietly interface %s\n/set-quietly style 12\n",
3069 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3070 strcat(str, "/set-2 51 1\n/set seek 1\n");
3071 } else if (ics_type == ICS_CHESSNET) {
3072 snprintf(str, MSG_SIZ, "/style 12\n");
3074 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3075 strcat(str, programVersion);
3076 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3077 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3078 strcat(str, "$iset seekremove 1\n$set seek 1\n");
3080 strcat(str, "$iset nohighlight 1\n");
3082 strcat(str, "$iset lock 1\n$style 12\n");
3085 NotifyFrontendLogin();
3089 if (started == STARTED_COMMENT) {
3090 /* Accumulate characters in comment */
3091 parse[parse_pos++] = buf[i];
3092 if (buf[i] == '\n') {
3093 parse[parse_pos] = NULLCHAR;
3094 if(chattingPartner>=0) {
3096 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3097 OutputChatMessage(chattingPartner, mess);
3098 if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3100 talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3101 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3102 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3103 OutputChatMessage(p, mess);
3107 chattingPartner = -1;
3108 if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3111 if(!suppressKibitz) // [HGM] kibitz
3112 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3113 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3114 int nrDigit = 0, nrAlph = 0, j;
3115 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3116 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3117 parse[parse_pos] = NULLCHAR;
3118 // try to be smart: if it does not look like search info, it should go to
3119 // ICS interaction window after all, not to engine-output window.
3120 for(j=0; j<parse_pos; j++) { // count letters and digits
3121 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3122 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3123 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3125 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3126 int depth=0; float score;
3127 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3128 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3129 pvInfoList[forwardMostMove-1].depth = depth;
3130 pvInfoList[forwardMostMove-1].score = 100*score;
3132 OutputKibitz(suppressKibitz, parse);
3135 if(gameMode == IcsObserving) // restore original ICS messages
3136 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3137 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3139 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3140 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3141 SendToPlayer(tmp, strlen(tmp));
3143 next_out = i+1; // [HGM] suppress printing in ICS window
3145 started = STARTED_NONE;
3147 /* Don't match patterns against characters in comment */
3152 if (started == STARTED_CHATTER) {
3153 if (buf[i] != '\n') {
3154 /* Don't match patterns against characters in chatter */
3158 started = STARTED_NONE;
3159 if(suppressKibitz) next_out = i+1;
3162 /* Kludge to deal with rcmd protocol */
3163 if (firstTime && looking_at(buf, &i, "\001*")) {
3164 DisplayFatalError(&buf[1], 0, 1);
3170 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3173 if (appData.debugMode)
3174 fprintf(debugFP, "ics_type %d\n", ics_type);
3177 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3178 ics_type = ICS_FICS;
3180 if (appData.debugMode)
3181 fprintf(debugFP, "ics_type %d\n", ics_type);
3184 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3185 ics_type = ICS_CHESSNET;
3187 if (appData.debugMode)
3188 fprintf(debugFP, "ics_type %d\n", ics_type);
3193 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3194 looking_at(buf, &i, "Logging you in as \"*\"") ||
3195 looking_at(buf, &i, "will be \"*\""))) {
3196 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3200 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3202 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3203 DisplayIcsInteractionTitle(buf);
3204 have_set_title = TRUE;
3207 /* skip finger notes */
3208 if (started == STARTED_NONE &&
3209 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3210 (buf[i] == '1' && buf[i+1] == '0')) &&
3211 buf[i+2] == ':' && buf[i+3] == ' ') {
3212 started = STARTED_CHATTER;
3218 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3219 if(appData.seekGraph) {
3220 if(soughtPending && MatchSoughtLine(buf+i)) {
3221 i = strstr(buf+i, "rated") - buf;
3222 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3223 next_out = leftover_start = i;
3224 started = STARTED_CHATTER;
3225 suppressKibitz = TRUE;
3228 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3229 && looking_at(buf, &i, "* ads displayed")) {
3230 soughtPending = FALSE;
3235 if(appData.autoRefresh) {
3236 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3237 int s = (ics_type == ICS_ICC); // ICC format differs
3239 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3240 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3241 looking_at(buf, &i, "*% "); // eat prompt
3242 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3243 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3244 next_out = i; // suppress
3247 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3248 char *p = star_match[0];
3250 if(seekGraphUp) RemoveSeekAd(atoi(p));
3251 while(*p && *p++ != ' '); // next
3253 looking_at(buf, &i, "*% "); // eat prompt
3254 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3261 /* skip formula vars */
3262 if (started == STARTED_NONE &&
3263 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3264 started = STARTED_CHATTER;
3269 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3270 if (appData.autoKibitz && started == STARTED_NONE &&
3271 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3272 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3273 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3274 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3275 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3276 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3277 suppressKibitz = TRUE;
3278 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3280 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3281 && (gameMode == IcsPlayingWhite)) ||
3282 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3283 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3284 started = STARTED_CHATTER; // own kibitz we simply discard
3286 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3287 parse_pos = 0; parse[0] = NULLCHAR;
3288 savingComment = TRUE;
3289 suppressKibitz = gameMode != IcsObserving ? 2 :
3290 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3294 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3295 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3296 && atoi(star_match[0])) {
3297 // suppress the acknowledgements of our own autoKibitz
3299 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3300 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3301 SendToPlayer(star_match[0], strlen(star_match[0]));
3302 if(looking_at(buf, &i, "*% ")) // eat prompt
3303 suppressKibitz = FALSE;
3307 } // [HGM] kibitz: end of patch
3309 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3311 // [HGM] chat: intercept tells by users for which we have an open chat window
3313 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3314 looking_at(buf, &i, "* whispers:") ||
3315 looking_at(buf, &i, "* kibitzes:") ||
3316 looking_at(buf, &i, "* shouts:") ||
3317 looking_at(buf, &i, "* c-shouts:") ||
3318 looking_at(buf, &i, "--> * ") ||
3319 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3320 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3321 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3322 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3324 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3325 chattingPartner = -1; collective = 0;
3327 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3328 for(p=0; p<MAX_CHAT; p++) {
3330 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3331 talker[0] = '['; strcat(talker, "] ");
3332 Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3333 chattingPartner = p; break;
3336 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3337 for(p=0; p<MAX_CHAT; p++) {
3339 if(!strcmp("kibitzes", chatPartner[p])) {
3340 talker[0] = '['; strcat(talker, "] ");
3341 chattingPartner = p; break;
3344 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3345 for(p=0; p<MAX_CHAT; p++) {
3347 if(!strcmp("whispers", chatPartner[p])) {
3348 talker[0] = '['; strcat(talker, "] ");
3349 chattingPartner = p; break;
3352 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3353 if(buf[i-8] == '-' && buf[i-3] == 't')
3354 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3356 if(!strcmp("c-shouts", chatPartner[p])) {
3357 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3358 chattingPartner = p; break;
3361 if(chattingPartner < 0)
3362 for(p=0; p<MAX_CHAT; p++) {
3364 if(!strcmp("shouts", chatPartner[p])) {
3365 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3366 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3367 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3368 chattingPartner = p; break;
3372 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3373 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3375 Colorize(ColorTell, FALSE);
3376 if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3378 chattingPartner = p; break;
3380 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3381 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3382 started = STARTED_COMMENT;
3383 parse_pos = 0; parse[0] = NULLCHAR;
3384 savingComment = 3 + chattingPartner; // counts as TRUE
3385 if(collective == 3) i = oldi; else {
3386 suppressKibitz = TRUE;
3387 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3388 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3392 } // [HGM] chat: end of patch
3395 if (appData.zippyTalk || appData.zippyPlay) {
3396 /* [DM] Backup address for color zippy lines */
3398 if (loggedOn == TRUE)
3399 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3400 (appData.zippyPlay && ZippyMatch(buf, &backup)))
3403 } // [DM] 'else { ' deleted
3405 /* Regular tells and says */
3406 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3407 looking_at(buf, &i, "* (your partner) tells you: ") ||
3408 looking_at(buf, &i, "* says: ") ||
3409 /* Don't color "message" or "messages" output */
3410 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3411 looking_at(buf, &i, "*. * at *:*: ") ||
3412 looking_at(buf, &i, "--* (*:*): ") ||
3413 /* Message notifications (same color as tells) */
3414 looking_at(buf, &i, "* has left a message ") ||
3415 looking_at(buf, &i, "* just sent you a message:\n") ||
3416 /* Whispers and kibitzes */
3417 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3418 looking_at(buf, &i, "* kibitzes: ") ||
3420 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3422 if (tkind == 1 && strchr(star_match[0], ':')) {
3423 /* Avoid "tells you:" spoofs in channels */
3426 if (star_match[0][0] == NULLCHAR ||
3427 strchr(star_match[0], ' ') ||
3428 (tkind == 3 && strchr(star_match[1], ' '))) {
3429 /* Reject bogus matches */
3432 if (appData.colorize) {
3433 if (oldi > next_out) {
3434 SendToPlayer(&buf[next_out], oldi - next_out);
3439 Colorize(ColorTell, FALSE);
3440 curColor = ColorTell;
3443 Colorize(ColorKibitz, FALSE);
3444 curColor = ColorKibitz;
3447 p = strrchr(star_match[1], '(');
3454 Colorize(ColorChannel1, FALSE);
3455 curColor = ColorChannel1;
3457 Colorize(ColorChannel, FALSE);
3458 curColor = ColorChannel;
3462 curColor = ColorNormal;
3466 if (started == STARTED_NONE && appData.autoComment &&
3467 (gameMode == IcsObserving ||
3468 gameMode == IcsPlayingWhite ||
3469 gameMode == IcsPlayingBlack)) {
3470 parse_pos = i - oldi;
3471 memcpy(parse, &buf[oldi], parse_pos);
3472 parse[parse_pos] = NULLCHAR;
3473 started = STARTED_COMMENT;
3474 savingComment = TRUE;
3475 } else if(collective != 3) {
3476 started = STARTED_CHATTER;
3477 savingComment = FALSE;
3484 if (looking_at(buf, &i, "* s-shouts: ") ||
3485 looking_at(buf, &i, "* c-shouts: ")) {
3486 if (appData.colorize) {
3487 if (oldi > next_out) {
3488 SendToPlayer(&buf[next_out], oldi - next_out);
3491 Colorize(ColorSShout, FALSE);
3492 curColor = ColorSShout;
3495 started = STARTED_CHATTER;
3499 if (looking_at(buf, &i, "--->")) {
3504 if (looking_at(buf, &i, "* shouts: ") ||
3505 looking_at(buf, &i, "--> ")) {
3506 if (appData.colorize) {
3507 if (oldi > next_out) {
3508 SendToPlayer(&buf[next_out], oldi - next_out);
3511 Colorize(ColorShout, FALSE);
3512 curColor = ColorShout;
3515 started = STARTED_CHATTER;
3519 if (looking_at( buf, &i, "Challenge:")) {
3520 if (appData.colorize) {
3521 if (oldi > next_out) {
3522 SendToPlayer(&buf[next_out], oldi - next_out);
3525 Colorize(ColorChallenge, FALSE);
3526 curColor = ColorChallenge;
3532 if (looking_at(buf, &i, "* offers you") ||
3533 looking_at(buf, &i, "* offers to be") ||
3534 looking_at(buf, &i, "* would like to") ||
3535 looking_at(buf, &i, "* requests to") ||
3536 looking_at(buf, &i, "Your opponent offers") ||
3537 looking_at(buf, &i, "Your opponent requests")) {
3539 if (appData.colorize) {
3540 if (oldi > next_out) {
3541 SendToPlayer(&buf[next_out], oldi - next_out);
3544 Colorize(ColorRequest, FALSE);
3545 curColor = ColorRequest;
3550 if (looking_at(buf, &i, "* (*) seeking")) {
3551 if (appData.colorize) {
3552 if (oldi > next_out) {
3553 SendToPlayer(&buf[next_out], oldi - next_out);
3556 Colorize(ColorSeek, FALSE);
3557 curColor = ColorSeek;
3562 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3564 if (looking_at(buf, &i, "\\ ")) {
3565 if (prevColor != ColorNormal) {
3566 if (oldi > next_out) {
3567 SendToPlayer(&buf[next_out], oldi - next_out);
3570 Colorize(prevColor, TRUE);
3571 curColor = prevColor;
3573 if (savingComment) {
3574 parse_pos = i - oldi;
3575 memcpy(parse, &buf[oldi], parse_pos);
3576 parse[parse_pos] = NULLCHAR;
3577 started = STARTED_COMMENT;
3578 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3579 chattingPartner = savingComment - 3; // kludge to remember the box
3581 started = STARTED_CHATTER;
3586 if (looking_at(buf, &i, "Black Strength :") ||
3587 looking_at(buf, &i, "<<< style 10 board >>>") ||
3588 looking_at(buf, &i, "<10>") ||
3589 looking_at(buf, &i, "#@#")) {
3590 /* Wrong board style */
3592 SendToICS(ics_prefix);
3593 SendToICS("set style 12\n");
3594 SendToICS(ics_prefix);
3595 SendToICS("refresh\n");
3599 if (looking_at(buf, &i, "login:")) {
3600 if (!have_sent_ICS_logon) {
3602 have_sent_ICS_logon = 1;
3603 else // no init script was found
3604 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3605 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3606 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3611 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3612 (looking_at(buf, &i, "\n<12> ") ||
3613 looking_at(buf, &i, "<12> "))) {
3615 if (oldi > next_out) {
3616 SendToPlayer(&buf[next_out], oldi - next_out);
3619 started = STARTED_BOARD;
3624 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3625 looking_at(buf, &i, "<b1> ")) {
3626 if (oldi > next_out) {
3627 SendToPlayer(&buf[next_out], oldi - next_out);
3630 started = STARTED_HOLDINGS;
3635 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3637 /* Header for a move list -- first line */
3639 switch (ics_getting_history) {
3643 case BeginningOfGame:
3644 /* User typed "moves" or "oldmoves" while we
3645 were idle. Pretend we asked for these
3646 moves and soak them up so user can step
3647 through them and/or save them.
3650 gameMode = IcsObserving;
3653 ics_getting_history = H_GOT_UNREQ_HEADER;
3655 case EditGame: /*?*/
3656 case EditPosition: /*?*/
3657 /* Should above feature work in these modes too? */
3658 /* For now it doesn't */
3659 ics_getting_history = H_GOT_UNWANTED_HEADER;
3662 ics_getting_history = H_GOT_UNWANTED_HEADER;
3667 /* Is this the right one? */
3668 if (gameInfo.white && gameInfo.black &&
3669 strcmp(gameInfo.white, star_match[0]) == 0 &&
3670 strcmp(gameInfo.black, star_match[2]) == 0) {
3672 ics_getting_history = H_GOT_REQ_HEADER;
3675 case H_GOT_REQ_HEADER:
3676 case H_GOT_UNREQ_HEADER:
3677 case H_GOT_UNWANTED_HEADER:
3678 case H_GETTING_MOVES:
3679 /* Should not happen */
3680 DisplayError(_("Error gathering move list: two headers"), 0);
3681 ics_getting_history = H_FALSE;
3685 /* Save player ratings into gameInfo if needed */
3686 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3687 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3688 (gameInfo.whiteRating == -1 ||
3689 gameInfo.blackRating == -1)) {
3691 gameInfo.whiteRating = string_to_rating(star_match[1]);
3692 gameInfo.blackRating = string_to_rating(star_match[3]);
3693 if (appData.debugMode)
3694 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3695 gameInfo.whiteRating, gameInfo.blackRating);
3700 if (looking_at(buf, &i,
3701 "* * match, initial time: * minute*, increment: * second")) {
3702 /* Header for a move list -- second line */
3703 /* Initial board will follow if this is a wild game */
3704 if (gameInfo.event != NULL) free(gameInfo.event);
3705 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3706 gameInfo.event = StrSave(str);
3707 /* [HGM] we switched variant. Translate boards if needed. */
3708 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3712 if (looking_at(buf, &i, "Move ")) {
3713 /* Beginning of a move list */
3714 switch (ics_getting_history) {
3716 /* Normally should not happen */
3717 /* Maybe user hit reset while we were parsing */
3720 /* Happens if we are ignoring a move list that is not
3721 * the one we just requested. Common if the user
3722 * tries to observe two games without turning off
3725 case H_GETTING_MOVES:
3726 /* Should not happen */
3727 DisplayError(_("Error gathering move list: nested"), 0);
3728 ics_getting_history = H_FALSE;
3730 case H_GOT_REQ_HEADER:
3731 ics_getting_history = H_GETTING_MOVES;
3732 started = STARTED_MOVES;
3734 if (oldi > next_out) {
3735 SendToPlayer(&buf[next_out], oldi - next_out);
3738 case H_GOT_UNREQ_HEADER:
3739 ics_getting_history = H_GETTING_MOVES;
3740 started = STARTED_MOVES_NOHIDE;
3743 case H_GOT_UNWANTED_HEADER:
3744 ics_getting_history = H_FALSE;
3750 if (looking_at(buf, &i, "% ") ||
3751 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3752 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3753 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3754 soughtPending = FALSE;
3758 if(suppressKibitz) next_out = i;
3759 savingComment = FALSE;
3763 case STARTED_MOVES_NOHIDE:
3764 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3765 parse[parse_pos + i - oldi] = NULLCHAR;
3766 ParseGameHistory(parse);
3768 if (appData.zippyPlay && first.initDone) {
3769 FeedMovesToProgram(&first, forwardMostMove);
3770 if (gameMode == IcsPlayingWhite) {
3771 if (WhiteOnMove(forwardMostMove)) {
3772 if (first.sendTime) {
3773 if (first.useColors) {
3774 SendToProgram("black\n", &first);
3776 SendTimeRemaining(&first, TRUE);
3778 if (first.useColors) {
3779 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3781 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3782 first.maybeThinking = TRUE;
3784 if (first.usePlayother) {
3785 if (first.sendTime) {
3786 SendTimeRemaining(&first, TRUE);
3788 SendToProgram("playother\n", &first);
3794 } else if (gameMode == IcsPlayingBlack) {
3795 if (!WhiteOnMove(forwardMostMove)) {
3796 if (first.sendTime) {
3797 if (first.useColors) {
3798 SendToProgram("white\n", &first);
3800 SendTimeRemaining(&first, FALSE);
3802 if (first.useColors) {
3803 SendToProgram("black\n", &first);
3805 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3806 first.maybeThinking = TRUE;
3808 if (first.usePlayother) {
3809 if (first.sendTime) {
3810 SendTimeRemaining(&first, FALSE);
3812 SendToProgram("playother\n", &first);
3821 if (gameMode == IcsObserving && ics_gamenum == -1) {
3822 /* Moves came from oldmoves or moves command
3823 while we weren't doing anything else.
3825 currentMove = forwardMostMove;
3826 ClearHighlights();/*!!could figure this out*/
3827 flipView = appData.flipView;
3828 DrawPosition(TRUE, boards[currentMove]);
3829 DisplayBothClocks();
3830 snprintf(str, MSG_SIZ, "%s %s %s",
3831 gameInfo.white, _("vs."), gameInfo.black);
3835 /* Moves were history of an active game */
3836 if (gameInfo.resultDetails != NULL) {
3837 free(gameInfo.resultDetails);
3838 gameInfo.resultDetails = NULL;
3841 HistorySet(parseList, backwardMostMove,
3842 forwardMostMove, currentMove-1);
3843 DisplayMove(currentMove - 1);
3844 if (started == STARTED_MOVES) next_out = i;
3845 started = STARTED_NONE;
3846 ics_getting_history = H_FALSE;
3849 case STARTED_OBSERVE:
3850 started = STARTED_NONE;
3851 SendToICS(ics_prefix);
3852 SendToICS("refresh\n");
3858 if(bookHit) { // [HGM] book: simulate book reply
3859 static char bookMove[MSG_SIZ]; // a bit generous?
3861 programStats.nodes = programStats.depth = programStats.time =
3862 programStats.score = programStats.got_only_move = 0;
3863 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3865 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3866 strcat(bookMove, bookHit);
3867 HandleMachineMove(bookMove, &first);
3872 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3873 started == STARTED_HOLDINGS ||
3874 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3875 /* Accumulate characters in move list or board */
3876 parse[parse_pos++] = buf[i];
3879 /* Start of game messages. Mostly we detect start of game
3880 when the first board image arrives. On some versions
3881 of the ICS, though, we need to do a "refresh" after starting
3882 to observe in order to get the current board right away. */
3883 if (looking_at(buf, &i, "Adding game * to observation list")) {
3884 started = STARTED_OBSERVE;
3888 /* Handle auto-observe */
3889 if (appData.autoObserve &&
3890 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3891 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3893 /* Choose the player that was highlighted, if any. */
3894 if (star_match[0][0] == '\033' ||
3895 star_match[1][0] != '\033') {
3896 player = star_match[0];
3898 player = star_match[2];
3900 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3901 ics_prefix, StripHighlightAndTitle(player));
3904 /* Save ratings from notify string */
3905 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3906 player1Rating = string_to_rating(star_match[1]);
3907 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3908 player2Rating = string_to_rating(star_match[3]);
3910 if (appData.debugMode)
3912 "Ratings from 'Game notification:' %s %d, %s %d\n",
3913 player1Name, player1Rating,
3914 player2Name, player2Rating);
3919 /* Deal with automatic examine mode after a game,
3920 and with IcsObserving -> IcsExamining transition */
3921 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3922 looking_at(buf, &i, "has made you an examiner of game *")) {
3924 int gamenum = atoi(star_match[0]);
3925 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3926 gamenum == ics_gamenum) {
3927 /* We were already playing or observing this game;
3928 no need to refetch history */
3929 gameMode = IcsExamining;
3931 pauseExamForwardMostMove = forwardMostMove;
3932 } else if (currentMove < forwardMostMove) {
3933 ForwardInner(forwardMostMove);
3936 /* I don't think this case really can happen */
3937 SendToICS(ics_prefix);
3938 SendToICS("refresh\n");
3943 /* Error messages */
3944 // if (ics_user_moved) {
3945 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3946 if (looking_at(buf, &i, "Illegal move") ||
3947 looking_at(buf, &i, "Not a legal move") ||
3948 looking_at(buf, &i, "Your king is in check") ||
3949 looking_at(buf, &i, "It isn't your turn") ||
3950 looking_at(buf, &i, "It is not your move")) {
3952 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3953 currentMove = forwardMostMove-1;
3954 DisplayMove(currentMove - 1); /* before DMError */
3955 DrawPosition(FALSE, boards[currentMove]);
3956 SwitchClocks(forwardMostMove-1); // [HGM] race
3957 DisplayBothClocks();
3959 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3965 if (looking_at(buf, &i, "still have time") ||
3966 looking_at(buf, &i, "not out of time") ||
3967 looking_at(buf, &i, "either player is out of time") ||
3968 looking_at(buf, &i, "has timeseal; checking")) {
3969 /* We must have called his flag a little too soon */
3970 whiteFlag = blackFlag = FALSE;
3974 if (looking_at(buf, &i, "added * seconds to") ||
3975 looking_at(buf, &i, "seconds were added to")) {
3976 /* Update the clocks */
3977 SendToICS(ics_prefix);
3978 SendToICS("refresh\n");
3982 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3983 ics_clock_paused = TRUE;
3988 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3989 ics_clock_paused = FALSE;
3994 /* Grab player ratings from the Creating: message.
3995 Note we have to check for the special case when
3996 the ICS inserts things like [white] or [black]. */
3997 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3998 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
4000 0 player 1 name (not necessarily white)
4002 2 empty, white, or black (IGNORED)
4003 3 player 2 name (not necessarily black)
4006 The names/ratings are sorted out when the game
4007 actually starts (below).
4009 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4010 player1Rating = string_to_rating(star_match[1]);
4011 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4012 player2Rating = string_to_rating(star_match[4]);
4014 if (appData.debugMode)
4016 "Ratings from 'Creating:' %s %d, %s %d\n",
4017 player1Name, player1Rating,
4018 player2Name, player2Rating);
4023 /* Improved generic start/end-of-game messages */
4024 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4025 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4026 /* If tkind == 0: */
4027 /* star_match[0] is the game number */
4028 /* [1] is the white player's name */
4029 /* [2] is the black player's name */
4030 /* For end-of-game: */
4031 /* [3] is the reason for the game end */
4032 /* [4] is a PGN end game-token, preceded by " " */
4033 /* For start-of-game: */
4034 /* [3] begins with "Creating" or "Continuing" */
4035 /* [4] is " *" or empty (don't care). */
4036 int gamenum = atoi(star_match[0]);
4037 char *whitename, *blackname, *why, *endtoken;
4038 ChessMove endtype = EndOfFile;
4041 whitename = star_match[1];
4042 blackname = star_match[2];
4043 why = star_match[3];
4044 endtoken = star_match[4];
4046 whitename = star_match[1];
4047 blackname = star_match[3];
4048 why = star_match[5];
4049 endtoken = star_match[6];
4052 /* Game start messages */
4053 if (strncmp(why, "Creating ", 9) == 0 ||
4054 strncmp(why, "Continuing ", 11) == 0) {
4055 gs_gamenum = gamenum;
4056 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4057 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4058 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4060 if (appData.zippyPlay) {
4061 ZippyGameStart(whitename, blackname);
4064 partnerBoardValid = FALSE; // [HGM] bughouse
4068 /* Game end messages */
4069 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4070 ics_gamenum != gamenum) {
4073 while (endtoken[0] == ' ') endtoken++;
4074 switch (endtoken[0]) {
4077 endtype = GameUnfinished;
4080 endtype = BlackWins;
4083 if (endtoken[1] == '/')
4084 endtype = GameIsDrawn;
4086 endtype = WhiteWins;
4089 GameEnds(endtype, why, GE_ICS);
4091 if (appData.zippyPlay && first.initDone) {
4092 ZippyGameEnd(endtype, why);
4093 if (first.pr == NoProc) {
4094 /* Start the next process early so that we'll
4095 be ready for the next challenge */
4096 StartChessProgram(&first);
4098 /* Send "new" early, in case this command takes
4099 a long time to finish, so that we'll be ready
4100 for the next challenge. */
4101 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4105 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4109 if (looking_at(buf, &i, "Removing game * from observation") ||
4110 looking_at(buf, &i, "no longer observing game *") ||
4111 looking_at(buf, &i, "Game * (*) has no examiners")) {
4112 if (gameMode == IcsObserving &&
4113 atoi(star_match[0]) == ics_gamenum)
4115 /* icsEngineAnalyze */
4116 if (appData.icsEngineAnalyze) {
4123 ics_user_moved = FALSE;
4128 if (looking_at(buf, &i, "no longer examining game *")) {
4129 if (gameMode == IcsExamining &&
4130 atoi(star_match[0]) == ics_gamenum)
4134 ics_user_moved = FALSE;
4139 /* Advance leftover_start past any newlines we find,
4140 so only partial lines can get reparsed */
4141 if (looking_at(buf, &i, "\n")) {
4142 prevColor = curColor;
4143 if (curColor != ColorNormal) {
4144 if (oldi > next_out) {
4145 SendToPlayer(&buf[next_out], oldi - next_out);
4148 Colorize(ColorNormal, FALSE);
4149 curColor = ColorNormal;
4151 if (started == STARTED_BOARD) {
4152 started = STARTED_NONE;
4153 parse[parse_pos] = NULLCHAR;
4154 ParseBoard12(parse);
4157 /* Send premove here */
4158 if (appData.premove) {
4160 if (currentMove == 0 &&
4161 gameMode == IcsPlayingWhite &&
4162 appData.premoveWhite) {
4163 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4164 if (appData.debugMode)
4165 fprintf(debugFP, "Sending premove:\n");
4167 } else if (currentMove == 1 &&
4168 gameMode == IcsPlayingBlack &&
4169 appData.premoveBlack) {
4170 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4171 if (appData.debugMode)
4172 fprintf(debugFP, "Sending premove:\n");
4174 } else if (gotPremove) {
4175 int oldFMM = forwardMostMove;
4177 ClearPremoveHighlights();
4178 if (appData.debugMode)
4179 fprintf(debugFP, "Sending premove:\n");
4180 UserMoveEvent(premoveFromX, premoveFromY,
4181 premoveToX, premoveToY,
4183 if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4184 if(moveList[oldFMM-1][1] != '@')
4185 SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4186 moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4188 SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4193 /* Usually suppress following prompt */
4194 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4195 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4196 if (looking_at(buf, &i, "*% ")) {
4197 savingComment = FALSE;
4202 } else if (started == STARTED_HOLDINGS) {
4204 char new_piece[MSG_SIZ];
4205 started = STARTED_NONE;
4206 parse[parse_pos] = NULLCHAR;
4207 if (appData.debugMode)
4208 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4209 parse, currentMove);
4210 if (sscanf(parse, " game %d", &gamenum) == 1) {
4211 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4212 new_piece[0] = NULLCHAR;
4213 sscanf(parse, "game %d white [%s black [%s <- %s",
4214 &gamenum, white_holding, black_holding,
4216 white_holding[strlen(white_holding)-1] = NULLCHAR;
4217 black_holding[strlen(black_holding)-1] = NULLCHAR;
4218 if (gameInfo.variant == VariantNormal) {
4219 /* [HGM] We seem to switch variant during a game!
4220 * Presumably no holdings were displayed, so we have
4221 * to move the position two files to the right to
4222 * create room for them!
4224 VariantClass newVariant;
4225 switch(gameInfo.boardWidth) { // base guess on board width
4226 case 9: newVariant = VariantShogi; break;
4227 case 10: newVariant = VariantGreat; break;
4228 default: newVariant = VariantCrazyhouse;
4229 if(strchr(white_holding, 'E') || strchr(black_holding, 'E') ||
4230 strchr(white_holding, 'H') || strchr(black_holding, 'H') )
4231 newVariant = VariantSChess;
4233 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4234 /* Get a move list just to see the header, which
4235 will tell us whether this is really bug or zh */
4236 if (ics_getting_history == H_FALSE) {
4237 ics_getting_history = H_REQUESTED;
4238 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4242 /* [HGM] copy holdings to board holdings area */
4243 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4244 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4245 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4247 if (appData.zippyPlay && first.initDone) {
4248 ZippyHoldings(white_holding, black_holding,
4252 if (tinyLayout || smallLayout) {
4253 char wh[16], bh[16];
4254 PackHolding(wh, white_holding);
4255 PackHolding(bh, black_holding);
4256 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4257 gameInfo.white, gameInfo.black);
4259 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4260 gameInfo.white, white_holding, _("vs."),
4261 gameInfo.black, black_holding);
4263 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4264 DrawPosition(FALSE, boards[currentMove]);
4266 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4267 sscanf(parse, "game %d white [%s black [%s <- %s",
4268 &gamenum, white_holding, black_holding,
4270 white_holding[strlen(white_holding)-1] = NULLCHAR;
4271 black_holding[strlen(black_holding)-1] = NULLCHAR;
4272 /* [HGM] copy holdings to partner-board holdings area */
4273 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4274 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4275 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4276 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4277 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4280 /* Suppress following prompt */
4281 if (looking_at(buf, &i, "*% ")) {
4282 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4283 savingComment = FALSE;
4291 i++; /* skip unparsed character and loop back */
4294 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4295 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4296 // SendToPlayer(&buf[next_out], i - next_out);
4297 started != STARTED_HOLDINGS && leftover_start > next_out) {
4298 SendToPlayer(&buf[next_out], leftover_start - next_out);
4302 leftover_len = buf_len - leftover_start;
4303 /* if buffer ends with something we couldn't parse,
4304 reparse it after appending the next read */
4306 } else if (count == 0) {
4307 RemoveInputSource(isr);
4308 DisplayFatalError(_("Connection closed by ICS"), 0, 6666);
4310 DisplayFatalError(_("Error reading from ICS"), error, 1);
4315 /* Board style 12 looks like this:
4317 <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
4319 * The "<12> " is stripped before it gets to this routine. The two
4320 * trailing 0's (flip state and clock ticking) are later addition, and
4321 * some chess servers may not have them, or may have only the first.
4322 * Additional trailing fields may be added in the future.
4325 #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"
4327 #define RELATION_OBSERVING_PLAYED 0
4328 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4329 #define RELATION_PLAYING_MYMOVE 1
4330 #define RELATION_PLAYING_NOTMYMOVE -1
4331 #define RELATION_EXAMINING 2
4332 #define RELATION_ISOLATED_BOARD -3
4333 #define RELATION_STARTING_POSITION -4 /* FICS only */
4336 ParseBoard12 (char *string)
4340 char *bookHit = NULL; // [HGM] book
4342 GameMode newGameMode;
4343 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4344 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4345 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4346 char to_play, board_chars[200];
4347 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4348 char black[32], white[32];
4350 int prevMove = currentMove;
4353 int fromX, fromY, toX, toY;
4355 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4356 Boolean weird = FALSE, reqFlag = FALSE;
4358 fromX = fromY = toX = toY = -1;
4362 if (appData.debugMode)
4363 fprintf(debugFP, "Parsing board: %s\n", string);
4365 move_str[0] = NULLCHAR;
4366 elapsed_time[0] = NULLCHAR;
4367 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4369 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4370 if(string[i] == ' ') { ranks++; files = 0; }
4372 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4375 for(j = 0; j <i; j++) board_chars[j] = string[j];
4376 board_chars[i] = '\0';
4379 n = sscanf(string, PATTERN, &to_play, &double_push,
4380 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4381 &gamenum, white, black, &relation, &basetime, &increment,
4382 &white_stren, &black_stren, &white_time, &black_time,
4383 &moveNum, str, elapsed_time, move_str, &ics_flip,
4387 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4388 DisplayError(str, 0);
4392 /* Convert the move number to internal form */
4393 moveNum = (moveNum - 1) * 2;
4394 if (to_play == 'B') moveNum++;
4395 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4396 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4402 case RELATION_OBSERVING_PLAYED:
4403 case RELATION_OBSERVING_STATIC:
4404 if (gamenum == -1) {
4405 /* Old ICC buglet */
4406 relation = RELATION_OBSERVING_STATIC;
4408 newGameMode = IcsObserving;
4410 case RELATION_PLAYING_MYMOVE:
4411 case RELATION_PLAYING_NOTMYMOVE:
4413 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4414 IcsPlayingWhite : IcsPlayingBlack;
4415 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4417 case RELATION_EXAMINING:
4418 newGameMode = IcsExamining;
4420 case RELATION_ISOLATED_BOARD:
4422 /* Just display this board. If user was doing something else,
4423 we will forget about it until the next board comes. */
4424 newGameMode = IcsIdle;
4426 case RELATION_STARTING_POSITION:
4427 newGameMode = gameMode;
4431 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4432 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4433 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4434 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4435 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4436 static int lastBgGame = -1;
4438 for (k = 0; k < ranks; k++) {
4439 for (j = 0; j < files; j++)
4440 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4441 if(gameInfo.holdingsWidth > 1) {
4442 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4443 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4446 CopyBoard(partnerBoard, board);
4447 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4448 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4449 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4450 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4451 if(toSqr = strchr(str, '-')) {
4452 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4453 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4454 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4455 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4456 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4457 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4459 DisplayWhiteClock(white_time*fac, to_play == 'W');
4460 DisplayBlackClock(black_time*fac, to_play != 'W');
4461 activePartner = to_play;
4462 if(gamenum != lastBgGame) {
4464 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4467 lastBgGame = gamenum;
4468 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4469 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4470 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4471 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4472 if(!twoBoards) DisplayMessage(partnerStatus, "");
4473 partnerBoardValid = TRUE;
4477 if(appData.dualBoard && appData.bgObserve) {
4478 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4479 SendToICS(ics_prefix), SendToICS("pobserve\n");
4480 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4482 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4487 /* Modify behavior for initial board display on move listing
4490 switch (ics_getting_history) {
4494 case H_GOT_REQ_HEADER:
4495 case H_GOT_UNREQ_HEADER:
4496 /* This is the initial position of the current game */
4497 gamenum = ics_gamenum;
4498 moveNum = 0; /* old ICS bug workaround */
4499 if (to_play == 'B') {
4500 startedFromSetupPosition = TRUE;
4501 blackPlaysFirst = TRUE;
4503 if (forwardMostMove == 0) forwardMostMove = 1;
4504 if (backwardMostMove == 0) backwardMostMove = 1;
4505 if (currentMove == 0) currentMove = 1;
4507 newGameMode = gameMode;
4508 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4510 case H_GOT_UNWANTED_HEADER:
4511 /* This is an initial board that we don't want */
4513 case H_GETTING_MOVES:
4514 /* Should not happen */
4515 DisplayError(_("Error gathering move list: extra board"), 0);
4516 ics_getting_history = H_FALSE;
4520 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4521 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4522 weird && (int)gameInfo.variant < (int)VariantShogi) {
4523 /* [HGM] We seem to have switched variant unexpectedly
4524 * Try to guess new variant from board size
4526 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4527 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4528 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4529 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4530 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4531 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4532 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4533 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4534 /* Get a move list just to see the header, which
4535 will tell us whether this is really bug or zh */
4536 if (ics_getting_history == H_FALSE) {
4537 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4538 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4543 /* Take action if this is the first board of a new game, or of a
4544 different game than is currently being displayed. */
4545 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4546 relation == RELATION_ISOLATED_BOARD) {
4548 /* Forget the old game and get the history (if any) of the new one */
4549 if (gameMode != BeginningOfGame) {
4553 if (appData.autoRaiseBoard) BoardToTop();
4555 if (gamenum == -1) {
4556 newGameMode = IcsIdle;
4557 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4558 appData.getMoveList && !reqFlag) {
4559 /* Need to get game history */
4560 ics_getting_history = H_REQUESTED;
4561 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4565 /* Initially flip the board to have black on the bottom if playing
4566 black or if the ICS flip flag is set, but let the user change
4567 it with the Flip View button. */
4568 flipView = appData.autoFlipView ?
4569 (newGameMode == IcsPlayingBlack) || ics_flip :
4572 /* Done with values from previous mode; copy in new ones */
4573 gameMode = newGameMode;
4575 ics_gamenum = gamenum;
4576 if (gamenum == gs_gamenum) {
4577 int klen = strlen(gs_kind);
4578 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4579 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4580 gameInfo.event = StrSave(str);
4582 gameInfo.event = StrSave("ICS game");
4584 gameInfo.site = StrSave(appData.icsHost);
4585 gameInfo.date = PGNDate();
4586 gameInfo.round = StrSave("-");
4587 gameInfo.white = StrSave(white);
4588 gameInfo.black = StrSave(black);
4589 timeControl = basetime * 60 * 1000;
4591 timeIncrement = increment * 1000;
4592 movesPerSession = 0;
4593 gameInfo.timeControl = TimeControlTagValue();
4594 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4595 if (appData.debugMode) {
4596 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4597 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4598 setbuf(debugFP, NULL);
4601 gameInfo.outOfBook = NULL;
4603 /* Do we have the ratings? */
4604 if (strcmp(player1Name, white) == 0 &&
4605 strcmp(player2Name, black) == 0) {
4606 if (appData.debugMode)
4607 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4608 player1Rating, player2Rating);
4609 gameInfo.whiteRating = player1Rating;
4610 gameInfo.blackRating = player2Rating;
4611 } else if (strcmp(player2Name, white) == 0 &&
4612 strcmp(player1Name, black) == 0) {
4613 if (appData.debugMode)
4614 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4615 player2Rating, player1Rating);
4616 gameInfo.whiteRating = player2Rating;
4617 gameInfo.blackRating = player1Rating;
4619 player1Name[0] = player2Name[0] = NULLCHAR;
4621 /* Silence shouts if requested */
4622 if (appData.quietPlay &&
4623 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4624 SendToICS(ics_prefix);
4625 SendToICS("set shout 0\n");
4629 /* Deal with midgame name changes */
4631 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4632 if (gameInfo.white) free(gameInfo.white);
4633 gameInfo.white = StrSave(white);
4635 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4636 if (gameInfo.black) free(gameInfo.black);
4637 gameInfo.black = StrSave(black);
4641 /* Throw away game result if anything actually changes in examine mode */
4642 if (gameMode == IcsExamining && !newGame) {
4643 gameInfo.result = GameUnfinished;
4644 if (gameInfo.resultDetails != NULL) {
4645 free(gameInfo.resultDetails);
4646 gameInfo.resultDetails = NULL;
4650 /* In pausing && IcsExamining mode, we ignore boards coming
4651 in if they are in a different variation than we are. */
4652 if (pauseExamInvalid) return;
4653 if (pausing && gameMode == IcsExamining) {
4654 if (moveNum <= pauseExamForwardMostMove) {
4655 pauseExamInvalid = TRUE;
4656 forwardMostMove = pauseExamForwardMostMove;
4661 if (appData.debugMode) {
4662 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4664 /* Parse the board */
4665 for (k = 0; k < ranks; k++) {
4666 for (j = 0; j < files; j++)
4667 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4668 if(gameInfo.holdingsWidth > 1) {
4669 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4670 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4673 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4674 board[5][BOARD_RGHT+1] = WhiteAngel;
4675 board[6][BOARD_RGHT+1] = WhiteMarshall;
4676 board[1][0] = BlackMarshall;
4677 board[2][0] = BlackAngel;
4678 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4680 CopyBoard(boards[moveNum], board);
4681 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4683 startedFromSetupPosition =
4684 !CompareBoards(board, initialPosition);
4685 if(startedFromSetupPosition)
4686 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4689 /* [HGM] Set castling rights. Take the outermost Rooks,
4690 to make it also work for FRC opening positions. Note that board12
4691 is really defective for later FRC positions, as it has no way to
4692 indicate which Rook can castle if they are on the same side of King.
4693 For the initial position we grant rights to the outermost Rooks,
4694 and remember thos rights, and we then copy them on positions
4695 later in an FRC game. This means WB might not recognize castlings with
4696 Rooks that have moved back to their original position as illegal,
4697 but in ICS mode that is not its job anyway.
4699 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4700 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4702 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4703 if(board[0][i] == WhiteRook) j = i;
4704 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4705 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4706 if(board[0][i] == WhiteRook) j = i;
4707 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4708 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4709 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4710 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4711 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4712 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4713 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4715 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4716 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4717 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4718 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4719 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4720 if(board[BOARD_HEIGHT-1][k] == bKing)
4721 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4722 if(gameInfo.variant == VariantTwoKings) {
4723 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4724 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4725 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4728 r = boards[moveNum][CASTLING][0] = initialRights[0];
4729 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4730 r = boards[moveNum][CASTLING][1] = initialRights[1];
4731 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4732 r = boards[moveNum][CASTLING][3] = initialRights[3];
4733 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4734 r = boards[moveNum][CASTLING][4] = initialRights[4];
4735 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4736 /* wildcastle kludge: always assume King has rights */
4737 r = boards[moveNum][CASTLING][2] = initialRights[2];
4738 r = boards[moveNum][CASTLING][5] = initialRights[5];
4740 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4741 boards[moveNum][EP_STATUS] = EP_NONE;
4742 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4743 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4744 if(double_push != -1) {
4745 int dir = WhiteOnMove(moveNum) ? 1 : -1, last = BOARD_HEIGHT-1;
4746 boards[moveNum][EP_FILE] = // also set new e.p. variables
4747 boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4748 boards[moveNum][EP_RANK] = (last + 3*dir)/2;
4749 boards[moveNum][LAST_TO] = 128*(last + dir) + boards[moveNum][EP_FILE];
4750 } else boards[moveNum][EP_FILE] = boards[moveNum][EP_RANK] = 100;
4753 if (ics_getting_history == H_GOT_REQ_HEADER ||
4754 ics_getting_history == H_GOT_UNREQ_HEADER) {
4755 /* This was an initial position from a move list, not
4756 the current position */
4760 /* Update currentMove and known move number limits */
4761 newMove = newGame || moveNum > forwardMostMove;
4764 forwardMostMove = backwardMostMove = currentMove = moveNum;
4765 if (gameMode == IcsExamining && moveNum == 0) {
4766 /* Workaround for ICS limitation: we are not told the wild
4767 type when starting to examine a game. But if we ask for
4768 the move list, the move list header will tell us */
4769 ics_getting_history = H_REQUESTED;
4770 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4773 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4774 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4776 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4777 /* [HGM] applied this also to an engine that is silently watching */
4778 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4779 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4780 gameInfo.variant == currentlyInitializedVariant) {
4781 takeback = forwardMostMove - moveNum;
4782 for (i = 0; i < takeback; i++) {
4783 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4784 SendToProgram("undo\n", &first);
4789 forwardMostMove = moveNum;
4790 if (!pausing || currentMove > forwardMostMove)
4791 currentMove = forwardMostMove;
4793 /* New part of history that is not contiguous with old part */
4794 if (pausing && gameMode == IcsExamining) {
4795 pauseExamInvalid = TRUE;
4796 forwardMostMove = pauseExamForwardMostMove;
4799 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4801 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4802 // [HGM] when we will receive the move list we now request, it will be
4803 // fed to the engine from the first move on. So if the engine is not
4804 // in the initial position now, bring it there.
4805 InitChessProgram(&first, 0);
4808 ics_getting_history = H_REQUESTED;
4809 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4812 forwardMostMove = backwardMostMove = currentMove = moveNum;
4815 /* Update the clocks */
4816 if (strchr(elapsed_time, '.')) {
4818 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4819 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4821 /* Time is in seconds */
4822 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4823 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4828 if (appData.zippyPlay && newGame &&
4829 gameMode != IcsObserving && gameMode != IcsIdle &&
4830 gameMode != IcsExamining)
4831 ZippyFirstBoard(moveNum, basetime, increment);
4834 /* Put the move on the move list, first converting
4835 to canonical algebraic form. */
4837 if (appData.debugMode) {
4838 int f = forwardMostMove;
4839 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4840 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4841 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4842 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4843 fprintf(debugFP, "moveNum = %d\n", moveNum);
4844 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4845 setbuf(debugFP, NULL);
4847 if (moveNum <= backwardMostMove) {
4848 /* We don't know what the board looked like before
4850 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4851 strcat(parseList[moveNum - 1], " ");
4852 strcat(parseList[moveNum - 1], elapsed_time);
4853 moveList[moveNum - 1][0] = NULLCHAR;
4854 } else if (strcmp(move_str, "none") == 0) {
4855 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4856 /* Again, we don't know what the board looked like;
4857 this is really the start of the game. */
4858 parseList[moveNum - 1][0] = NULLCHAR;
4859 moveList[moveNum - 1][0] = NULLCHAR;
4860 backwardMostMove = moveNum;
4861 startedFromSetupPosition = TRUE;
4862 fromX = fromY = toX = toY = -1;
4864 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4865 // So we parse the long-algebraic move string in stead of the SAN move
4866 int valid; char buf[MSG_SIZ], *prom;
4868 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4869 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4870 // str looks something like "Q/a1-a2"; kill the slash
4872 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4873 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4874 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4875 strcat(buf, prom); // long move lacks promo specification!
4876 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4877 if(appData.debugMode)
4878 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4879 safeStrCpy(move_str, buf, MSG_SIZ);
4881 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4882 &fromX, &fromY, &toX, &toY, &promoChar)
4883 || ParseOneMove(buf, moveNum - 1, &moveType,
4884 &fromX, &fromY, &toX, &toY, &promoChar);
4885 // end of long SAN patch
4887 (void) CoordsToAlgebraic(boards[moveNum - 1],
4888 PosFlags(moveNum - 1),
4889 fromY, fromX, toY, toX, promoChar,
4890 parseList[moveNum-1]);
4891 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4897 if(!IS_SHOGI(gameInfo.variant))
4898 strcat(parseList[moveNum - 1], "+");
4901 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4902 strcat(parseList[moveNum - 1], "#");
4905 strcat(parseList[moveNum - 1], " ");
4906 strcat(parseList[moveNum - 1], elapsed_time);
4907 /* currentMoveString is set as a side-effect of ParseOneMove */
4908 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4909 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4910 strcat(moveList[moveNum - 1], "\n");
4912 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4913 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4914 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4915 ChessSquare old, new = boards[moveNum][k][j];
4916 if(new == EmptySquare || fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4917 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4918 if(old == new) continue;
4919 if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4920 else if(new == WhiteWazir || new == BlackWazir) {
4921 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4922 boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4923 else boards[moveNum][k][j] = old; // preserve type of Gold
4924 } else if(old == WhitePawn || old == BlackPawn) // Pawn promotions (but not e.p.capture!)
4925 boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4928 /* Move from ICS was illegal!? Punt. */
4929 if (appData.debugMode) {
4930 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4931 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4933 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4934 strcat(parseList[moveNum - 1], " ");
4935 strcat(parseList[moveNum - 1], elapsed_time);
4936 moveList[moveNum - 1][0] = NULLCHAR;
4937 fromX = fromY = toX = toY = -1;
4940 if (appData.debugMode) {
4941 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4942 setbuf(debugFP, NULL);
4946 /* Send move to chess program (BEFORE animating it). */
4947 if (appData.zippyPlay && !newGame && newMove &&
4948 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4950 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4951 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4952 if (moveList[moveNum - 1][0] == NULLCHAR) {
4953 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4955 DisplayError(str, 0);
4957 if (first.sendTime) {
4958 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4960 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4961 if (firstMove && !bookHit) {
4963 if (first.useColors) {
4964 SendToProgram(gameMode == IcsPlayingWhite ?
4966 "black\ngo\n", &first);
4968 SendToProgram("go\n", &first);
4970 first.maybeThinking = TRUE;
4973 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4974 if (moveList[moveNum - 1][0] == NULLCHAR) {
4975 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4976 DisplayError(str, 0);
4978 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4979 SendMoveToProgram(moveNum - 1, &first);
4986 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4987 /* If move comes from a remote source, animate it. If it
4988 isn't remote, it will have already been animated. */
4989 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4990 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4992 if (!pausing && appData.highlightLastMove) {
4993 SetHighlights(fromX, fromY, toX, toY);
4997 /* Start the clocks */
4998 whiteFlag = blackFlag = FALSE;
4999 appData.clockMode = !(basetime == 0 && increment == 0);
5001 ics_clock_paused = TRUE;
5003 } else if (ticking == 1) {
5004 ics_clock_paused = FALSE;
5006 if (gameMode == IcsIdle ||
5007 relation == RELATION_OBSERVING_STATIC ||
5008 relation == RELATION_EXAMINING ||
5010 DisplayBothClocks();
5014 /* Display opponents and material strengths */
5015 if (gameInfo.variant != VariantBughouse &&
5016 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
5017 if (tinyLayout || smallLayout) {
5018 if(gameInfo.variant == VariantNormal)
5019 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5020 gameInfo.white, white_stren, gameInfo.black, black_stren,
5021 basetime, increment);
5023 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5024 gameInfo.white, white_stren, gameInfo.black, black_stren,
5025 basetime, increment, (int) gameInfo.variant);
5027 if(gameInfo.variant == VariantNormal)
5028 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5029 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5030 basetime, increment);
5032 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5033 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5034 basetime, increment, VariantName(gameInfo.variant));
5037 if (appData.debugMode) {
5038 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5043 /* Display the board */
5044 if (!pausing && !appData.noGUI) {
5046 if (appData.premove)
5048 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5049 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5050 ClearPremoveHighlights();
5052 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5053 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5054 DrawPosition(j, boards[currentMove]);
5056 DisplayMove(moveNum - 1);
5057 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5058 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5059 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
5060 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5064 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5066 if(bookHit) { // [HGM] book: simulate book reply
5067 static char bookMove[MSG_SIZ]; // a bit generous?
5069 programStats.nodes = programStats.depth = programStats.time =
5070 programStats.score = programStats.got_only_move = 0;
5071 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5073 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5074 strcat(bookMove, bookHit);
5075 HandleMachineMove(bookMove, &first);
5084 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5085 ics_getting_history = H_REQUESTED;
5086 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5092 SendToBoth (char *msg)
5093 { // to make it easy to keep two engines in step in dual analysis
5094 SendToProgram(msg, &first);
5095 if(second.analyzing) SendToProgram(msg, &second);
5099 AnalysisPeriodicEvent (int force)
5101 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5102 && !force) || !appData.periodicUpdates)
5105 /* Send . command to Crafty to collect stats */
5108 /* Don't send another until we get a response (this makes
5109 us stop sending to old Crafty's which don't understand
5110 the "." command (sending illegal cmds resets node count & time,
5111 which looks bad)) */
5112 programStats.ok_to_send = 0;
5116 ics_update_width (int new_width)
5118 ics_printf("set width %d\n", new_width);
5122 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5126 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5127 if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5128 sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5129 SendToProgram(buf, cps);
5132 // null move in variant where engine does not understand it (for analysis purposes)
5133 SendBoard(cps, moveNum + 1); // send position after move in stead.
5136 if (cps->useUsermove) {
5137 SendToProgram("usermove ", cps);
5141 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5142 int len = space - parseList[moveNum];
5143 memcpy(buf, parseList[moveNum], len);
5145 buf[len] = NULLCHAR;
5147 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5149 SendToProgram(buf, cps);
5151 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5152 AlphaRank(moveList[moveNum], 4);
5153 SendToProgram(moveList[moveNum], cps);
5154 AlphaRank(moveList[moveNum], 4); // and back
5156 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5157 * the engine. It would be nice to have a better way to identify castle
5159 if(appData.fischerCastling && cps->useOOCastle) {
5160 int fromX = moveList[moveNum][0] - AAA;
5161 int fromY = moveList[moveNum][1] - ONE;
5162 int toX = moveList[moveNum][2] - AAA;
5163 int toY = moveList[moveNum][3] - ONE;
5164 if((boards[moveNum][fromY][fromX] == WhiteKing
5165 && boards[moveNum][toY][toX] == WhiteRook)
5166 || (boards[moveNum][fromY][fromX] == BlackKing
5167 && boards[moveNum][toY][toX] == BlackRook)) {
5168 if(toX > fromX) SendToProgram("O-O\n", cps);
5169 else SendToProgram("O-O-O\n", cps);
5171 else SendToProgram(moveList[moveNum], cps);
5173 if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5174 char *m = moveList[moveNum];
5176 *c = m[7]; if(*c == '\n') *c = NULLCHAR; // promoChar
5177 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
5178 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5181 m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5182 else if(*c && m[8] != '\n') { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5183 *c = m[9]; if(*c == '\n') *c = NULLCHAR;
5184 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
5189 m[2], m[3] - '0', c);
5191 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5194 m[2], m[3] - '0', c);
5195 SendToProgram(buf, cps);
5197 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5198 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5199 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5200 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5201 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5203 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5204 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5205 SendToProgram(buf, cps);
5207 else SendToProgram(moveList[moveNum], cps);
5208 /* End of additions by Tord */
5211 /* [HGM] setting up the opening has brought engine in force mode! */
5212 /* Send 'go' if we are in a mode where machine should play. */
5213 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5214 (gameMode == TwoMachinesPlay ||
5216 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5218 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5219 SendToProgram("go\n", cps);
5220 if (appData.debugMode) {
5221 fprintf(debugFP, "(extra)\n");
5224 setboardSpoiledMachineBlack = 0;
5228 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5230 char user_move[MSG_SIZ];
5233 if(gameInfo.variant == VariantSChess && promoChar) {
5234 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5235 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5236 } else suffix[0] = NULLCHAR;
5240 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5241 (int)moveType, fromX, fromY, toX, toY);
5242 DisplayError(user_move + strlen("say "), 0);
5244 case WhiteKingSideCastle:
5245 case BlackKingSideCastle:
5246 case WhiteQueenSideCastleWild:
5247 case BlackQueenSideCastleWild:
5249 case WhiteHSideCastleFR:
5250 case BlackHSideCastleFR:
5252 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5254 case WhiteQueenSideCastle:
5255 case BlackQueenSideCastle:
5256 case WhiteKingSideCastleWild:
5257 case BlackKingSideCastleWild:
5259 case WhiteASideCastleFR:
5260 case BlackASideCastleFR:
5262 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5264 case WhiteNonPromotion:
5265 case BlackNonPromotion:
5266 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5268 case WhitePromotion:
5269 case BlackPromotion:
5270 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5271 gameInfo.variant == VariantMakruk)
5272 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5273 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5274 PieceToChar(WhiteFerz));
5275 else if(gameInfo.variant == VariantGreat)
5276 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5277 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5278 PieceToChar(WhiteMan));
5280 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5281 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5287 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5288 ToUpper(PieceToChar((ChessSquare) fromX)),
5289 AAA + toX, ONE + toY);
5291 case IllegalMove: /* could be a variant we don't quite understand */
5292 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5294 case WhiteCapturesEnPassant:
5295 case BlackCapturesEnPassant:
5296 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5297 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5300 SendToICS(user_move);
5301 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5302 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5307 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5308 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5309 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5310 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5311 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5314 if(gameMode != IcsExamining) { // is this ever not the case?
5315 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5317 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5318 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5319 } else { // on FICS we must first go to general examine mode
5320 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5322 if(gameInfo.variant != VariantNormal) {
5323 // try figure out wild number, as xboard names are not always valid on ICS
5324 for(i=1; i<=36; i++) {
5325 snprintf(buf, MSG_SIZ, "wild/%d", i);
5326 if(StringToVariant(buf) == gameInfo.variant) break;
5328 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5329 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5330 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5331 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5332 SendToICS(ics_prefix);
5334 if(startedFromSetupPosition || backwardMostMove != 0) {
5335 fen = PositionToFEN(backwardMostMove, NULL, 1);
5336 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5337 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5339 } else { // FICS: everything has to set by separate bsetup commands
5340 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5341 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5343 if(!WhiteOnMove(backwardMostMove)) {
5344 SendToICS("bsetup tomove black\n");
5346 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5347 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5349 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5350 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5352 i = boards[backwardMostMove][EP_STATUS];
5353 if(i >= 0) { // set e.p.
5354 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5360 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5361 SendToICS("bsetup done\n"); // switch to normal examining.
5363 for(i = backwardMostMove; i<last; i++) {
5365 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5366 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5367 int len = strlen(moveList[i]);
5368 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5369 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5373 SendToICS(ics_prefix);
5374 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5377 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5381 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5383 if (rf == DROP_RANK) {
5384 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5385 sprintf(move, "%c@%c%c\n",
5386 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5388 if (promoChar == 'x' || promoChar == NULLCHAR) {
5389 sprintf(move, "%c%c%c%c\n",
5390 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5391 if(killX >= 0 && killY >= 0) {
5392 sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5393 if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5396 sprintf(move, "%c%c%c%c%c\n",
5397 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5398 if(killX >= 0 && killY >= 0) {
5399 sprintf(move+4, ";%c%c%c\n", AAA + killX, ONE + killY, promoChar);
5400 if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5407 ProcessICSInitScript (FILE *f)
5411 while (fgets(buf, MSG_SIZ, f)) {
5412 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5419 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5421 static ClickType lastClickType;
5424 PieceInString (char *s, ChessSquare piece)
5426 char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5427 while((p = strchr(s, ID))) {
5428 if(!suffix || p[1] == suffix) return TRUE;
5435 Partner (ChessSquare *p)
5436 { // change piece into promotion partner if one shogi-promotes to the other
5437 ChessSquare partner = promoPartner[*p];
5438 if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5439 if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5447 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5448 static int toggleFlag;
5449 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5450 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5451 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5452 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5453 if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5454 if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5456 if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5457 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5458 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5459 else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5460 else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5461 if(!step) step = -1;
5462 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5463 !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5464 promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it
5465 promoSweep == pawn ||
5466 appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5467 (promoSweep == WhiteLion || promoSweep == BlackLion)));
5469 int victim = boards[currentMove][toY][toX];
5470 boards[currentMove][toY][toX] = promoSweep;
5471 DrawPosition(FALSE, boards[currentMove]);
5472 boards[currentMove][toY][toX] = victim;
5474 ChangeDragPiece(promoSweep);
5478 PromoScroll (int x, int y)
5482 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5483 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5484 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5485 if(!step) return FALSE;
5486 lastX = x; lastY = y;
5487 if((promoSweep < BlackPawn) == flipView) step = -step;
5488 if(step > 0) selectFlag = 1;
5489 if(!selectFlag) Sweep(step);
5494 NextPiece (int step)
5496 ChessSquare piece = boards[currentMove][toY][toX];
5499 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5500 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5501 if(!step) step = -1;
5502 } while(PieceToChar(pieceSweep) == '.');
5503 boards[currentMove][toY][toX] = pieceSweep;
5504 DrawPosition(FALSE, boards[currentMove]);
5505 boards[currentMove][toY][toX] = piece;
5507 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5509 AlphaRank (char *move, int n)
5511 // char *p = move, c; int x, y;
5513 if (appData.debugMode) {
5514 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5518 move[2]>='0' && move[2]<='9' &&
5519 move[3]>='a' && move[3]<='x' ) {
5521 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5522 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5524 if(move[0]>='0' && move[0]<='9' &&
5525 move[1]>='a' && move[1]<='x' &&
5526 move[2]>='0' && move[2]<='9' &&
5527 move[3]>='a' && move[3]<='x' ) {
5528 /* input move, Shogi -> normal */
5529 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5530 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5531 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5532 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5535 move[3]>='0' && move[3]<='9' &&
5536 move[2]>='a' && move[2]<='x' ) {
5538 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5539 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5542 move[0]>='a' && move[0]<='x' &&
5543 move[3]>='0' && move[3]<='9' &&
5544 move[2]>='a' && move[2]<='x' ) {
5545 /* output move, normal -> Shogi */
5546 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5547 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5548 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5549 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5550 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5552 if (appData.debugMode) {
5553 fprintf(debugFP, " out = '%s'\n", move);
5557 char yy_textstr[8000];
5559 /* Parser for moves from gnuchess, ICS, or user typein box */
5561 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5563 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5565 switch (*moveType) {
5566 case WhitePromotion:
5567 case BlackPromotion:
5568 case WhiteNonPromotion:
5569 case BlackNonPromotion:
5572 case WhiteCapturesEnPassant:
5573 case BlackCapturesEnPassant:
5574 case WhiteKingSideCastle:
5575 case WhiteQueenSideCastle:
5576 case BlackKingSideCastle:
5577 case BlackQueenSideCastle:
5578 case WhiteKingSideCastleWild:
5579 case WhiteQueenSideCastleWild:
5580 case BlackKingSideCastleWild:
5581 case BlackQueenSideCastleWild:
5582 /* Code added by Tord: */
5583 case WhiteHSideCastleFR:
5584 case WhiteASideCastleFR:
5585 case BlackHSideCastleFR:
5586 case BlackASideCastleFR:
5587 /* End of code added by Tord */
5588 case IllegalMove: /* bug or odd chess variant */
5589 if(currentMoveString[1] == '@') { // illegal drop
5590 *fromX = WhiteOnMove(moveNum) ?
5591 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5592 (int) CharToPiece(ToLower(currentMoveString[0]));
5595 *fromX = currentMoveString[0] - AAA;
5596 *fromY = currentMoveString[1] - ONE;
5597 *toX = currentMoveString[2] - AAA;
5598 *toY = currentMoveString[3] - ONE;
5599 *promoChar = currentMoveString[4];
5600 if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5601 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5602 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5603 if (appData.debugMode) {
5604 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5606 *fromX = *fromY = *toX = *toY = 0;
5609 if (appData.testLegality) {
5610 return (*moveType != IllegalMove);
5612 return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5613 // [HGM] lion: if this is a double move we are less critical
5614 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5619 *fromX = *moveType == WhiteDrop ?
5620 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5621 (int) CharToPiece(ToLower(currentMoveString[0]));
5624 *toX = currentMoveString[2] - AAA;
5625 *toY = currentMoveString[3] - ONE;
5626 *promoChar = NULLCHAR;
5630 case ImpossibleMove:
5640 if (appData.debugMode) {
5641 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5644 *fromX = *fromY = *toX = *toY = 0;
5645 *promoChar = NULLCHAR;
5650 Boolean pushed = FALSE;
5651 char *lastParseAttempt;
5654 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5655 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5656 int fromX, fromY, toX, toY; char promoChar;
5661 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5662 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5663 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5666 endPV = forwardMostMove;
5668 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5669 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5670 lastParseAttempt = pv;
5671 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5672 if(!valid && nr == 0 &&
5673 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5674 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5675 // Hande case where played move is different from leading PV move
5676 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5677 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5678 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5679 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5680 endPV += 2; // if position different, keep this
5681 moveList[endPV-1][0] = fromX + AAA;
5682 moveList[endPV-1][1] = fromY + ONE;
5683 moveList[endPV-1][2] = toX + AAA;
5684 moveList[endPV-1][3] = toY + ONE;
5685 parseList[endPV-1][0] = NULLCHAR;
5686 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5689 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5690 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5691 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5692 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5693 valid++; // allow comments in PV
5697 if(endPV+1 > framePtr) break; // no space, truncate
5700 CopyBoard(boards[endPV], boards[endPV-1]);
5701 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5702 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5703 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5704 CoordsToAlgebraic(boards[endPV - 1],
5705 PosFlags(endPV - 1),
5706 fromY, fromX, toY, toX, promoChar,
5707 parseList[endPV - 1]);
5709 if(atEnd == 2) return; // used hidden, for PV conversion
5710 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5711 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5712 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5713 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5714 DrawPosition(TRUE, boards[currentMove]);
5718 MultiPV (ChessProgramState *cps, int kind)
5719 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5721 for(i=0; i<cps->nrOptions; i++) {
5722 char *s = cps->option[i].name;
5723 if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5724 if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5725 && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5730 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5731 static int multi, pv_margin;
5732 static ChessProgramState *activeCps;
5735 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5737 int startPV, lineStart, origIndex = index;
5738 char *p, buf2[MSG_SIZ];
5739 ChessProgramState *cps = (pane ? &second : &first);
5741 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5742 lastX = x; lastY = y;
5743 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5744 lineStart = startPV = index;
5745 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5746 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5748 do{ while(buf[index] && buf[index] != '\n') index++;
5749 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5751 if(lineStart == 0 && gameMode == AnalyzeMode) {
5753 if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5754 if(n == 0) { // click not on "fewer" or "more"
5755 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5756 pv_margin = cps->option[multi].value;
5757 activeCps = cps; // non-null signals margin adjustment
5759 } else if((multi = MultiPV(cps, 1)) >= 0) {
5760 n += cps->option[multi].value; if(n < 1) n = 1;
5761 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5762 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5763 cps->option[multi].value = n;
5767 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5768 ExcludeClick(origIndex - lineStart);
5770 } else if(!strncmp(buf+lineStart, "dep\t", 4)) { // column headers clicked
5771 Collapse(origIndex - lineStart);
5774 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5775 *start = startPV; *end = index-1;
5776 extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5783 static char buf[10*MSG_SIZ];
5784 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5786 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5787 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5788 for(i = forwardMostMove; i<endPV; i++){
5789 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5790 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5793 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5794 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5795 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5801 LoadPV (int x, int y)
5802 { // called on right mouse click to load PV
5803 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5804 lastX = x; lastY = y;
5805 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5813 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5815 if(pv_margin != activeCps->option[multi].value) {
5817 snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5818 SendToProgram(buf, activeCps);
5819 activeCps->option[multi].value = pv_margin;
5824 if(endPV < 0) return;
5825 if(appData.autoCopyPV) CopyFENToClipboard();
5827 if(extendGame && currentMove > forwardMostMove) {
5828 Boolean saveAnimate = appData.animate;
5830 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5831 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5832 } else storedGames--; // abandon shelved tail of original game
5835 forwardMostMove = currentMove;
5836 currentMove = oldFMM;
5837 appData.animate = FALSE;
5838 ToNrEvent(forwardMostMove);
5839 appData.animate = saveAnimate;
5841 currentMove = forwardMostMove;
5842 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5843 ClearPremoveHighlights();
5844 DrawPosition(TRUE, boards[currentMove]);
5848 MovePV (int x, int y, int h)
5849 { // step through PV based on mouse coordinates (called on mouse move)
5850 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5852 if(activeCps) { // adjusting engine's multi-pv margin
5853 if(x > lastX) pv_margin++; else
5854 if(x < lastX) pv_margin -= (pv_margin > 0);
5857 snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5858 DisplayMessage(buf, "");
5863 // we must somehow check if right button is still down (might be released off board!)
5864 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5865 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5866 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5868 lastX = x; lastY = y;
5870 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5871 if(endPV < 0) return;
5872 if(y < margin) step = 1; else
5873 if(y > h - margin) step = -1;
5874 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5875 currentMove += step;
5876 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5877 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5878 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5879 DrawPosition(FALSE, boards[currentMove]);
5883 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5884 // All positions will have equal probability, but the current method will not provide a unique
5885 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5891 int piecesLeft[(int)BlackPawn];
5892 int seed, nrOfShuffles;
5895 GetPositionNumber ()
5896 { // sets global variable seed
5899 seed = appData.defaultFrcPosition;
5900 if(seed < 0) { // randomize based on time for negative FRC position numbers
5901 for(i=0; i<50; i++) seed += random();
5902 seed = random() ^ random() >> 8 ^ random() << 8;
5903 if(seed<0) seed = -seed;
5908 put (Board board, int pieceType, int rank, int n, int shade)
5909 // put the piece on the (n-1)-th empty squares of the given shade
5913 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5914 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5915 board[rank][i] = (ChessSquare) pieceType;
5916 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5918 piecesLeft[pieceType]--;
5927 AddOnePiece (Board board, int pieceType, int rank, int shade)
5928 // calculate where the next piece goes, (any empty square), and put it there
5932 i = seed % squaresLeft[shade];
5933 nrOfShuffles *= squaresLeft[shade];
5934 seed /= squaresLeft[shade];
5935 put(board, pieceType, rank, i, shade);
5939 AddTwoPieces (Board board, int pieceType, int rank)
5940 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5942 int i, n=squaresLeft[ANY], j=n-1, k;
5944 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5945 i = seed % k; // pick one
5948 while(i >= j) i -= j--;
5949 j = n - 1 - j; i += j;
5950 put(board, pieceType, rank, j, ANY);
5951 put(board, pieceType, rank, i, ANY);
5955 SetUpShuffle (Board board, int number)
5959 GetPositionNumber(); nrOfShuffles = 1;
5961 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5962 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5963 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5965 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5967 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5968 p = (int) board[0][i];
5969 if(p < (int) BlackPawn) piecesLeft[p] ++;
5970 board[0][i] = EmptySquare;
5973 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5974 // shuffles restricted to allow normal castling put KRR first
5975 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5976 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5977 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5978 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5979 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5980 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5981 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5982 put(board, WhiteRook, 0, 0, ANY);
5983 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5986 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5987 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5988 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5989 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5990 while(piecesLeft[p] >= 2) {
5991 AddOnePiece(board, p, 0, LITE);
5992 AddOnePiece(board, p, 0, DARK);
5994 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5997 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5998 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5999 // but we leave King and Rooks for last, to possibly obey FRC restriction
6000 if(p == (int)WhiteRook) continue;
6001 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
6002 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
6005 // now everything is placed, except perhaps King (Unicorn) and Rooks
6007 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
6008 // Last King gets castling rights
6009 while(piecesLeft[(int)WhiteUnicorn]) {
6010 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6011 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
6014 while(piecesLeft[(int)WhiteKing]) {
6015 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6016 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
6021 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
6022 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6025 // Only Rooks can be left; simply place them all
6026 while(piecesLeft[(int)WhiteRook]) {
6027 i = put(board, WhiteRook, 0, 0, ANY);
6028 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6031 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
6033 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
6036 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6037 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6040 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6044 ptclen (const char *s, char *escapes)
6047 if(!*escapes) return strlen(s);
6048 while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6053 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6054 /* [HGM] moved here from winboard.c because of its general usefulness */
6055 /* Basically a safe strcpy that uses the last character as King */
6057 int result = FALSE; int NrPieces;
6058 unsigned char partner[EmptySquare];
6060 if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6061 && NrPieces >= 12 && !(NrPieces&1)) {
6062 int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6064 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6065 for( i=offs=0; i<NrPieces/2-1; i++ ) {
6067 if(map[j] == '/') offs = WhitePBishop - i, j++;
6068 if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6069 table[i+offs] = map[j++];
6070 if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6071 if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6072 if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6074 table[(int) WhiteKing] = map[j++];
6075 for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6077 if(map[j] == '/') offs = WhitePBishop - ii, j++;
6078 i = WHITE_TO_BLACK ii;
6079 if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6080 table[i+offs] = map[j++];
6081 if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6082 if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6083 if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6085 table[(int) BlackKing] = map[j++];
6088 if(*escapes) { // set up promotion pairing
6089 for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6090 // pieceToChar entirely filled, so we can look up specified partners
6091 for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6093 if(c == '^' || c == '-') { // has specified partner
6095 for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6096 if(c == '^') table[i] = '+';
6097 if(p < EmptySquare) {
6098 if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6099 if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6100 promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6102 } else if(c == '*') {
6103 table[i] = partner[i];
6104 promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6116 SetCharTable (unsigned char *table, const char * map)
6118 return SetCharTableEsc(table, map, "");
6122 Prelude (Board board)
6123 { // [HGM] superchess: random selection of exo-pieces
6124 int i, j, k; ChessSquare p;
6125 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6127 GetPositionNumber(); // use FRC position number
6129 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6130 SetCharTable(pieceToChar, appData.pieceToCharTable);
6131 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6132 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6135 j = seed%4; seed /= 4;
6136 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6137 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6138 board[handSize-1-k][0] = WHITE_TO_BLACK p; board[handSize-1-k][1]++;
6139 j = seed%3 + (seed%3 >= j); seed /= 3;
6140 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6141 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6142 board[handSize-1-k][0] = WHITE_TO_BLACK p; board[handSize-1-k][1]++;
6143 j = seed%3; seed /= 3;
6144 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6145 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6146 board[handSize-1-k][0] = WHITE_TO_BLACK p; board[handSize-1-k][1]++;
6147 j = seed%2 + (seed%2 >= j); seed /= 2;
6148 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6149 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6150 board[handSize-1-k][0] = WHITE_TO_BLACK p; board[handSize-1-k][1]++;
6151 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
6152 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
6153 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6154 put(board, exoPieces[0], 0, 0, ANY);
6155 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6159 InitPosition (int redraw)
6161 ChessSquare (* pieces)[BOARD_FILES];
6162 int i, j, pawnRow=1, pieceRows=1, overrule,
6163 oldx = gameInfo.boardWidth,
6164 oldy = gameInfo.boardHeight,
6165 oldh = gameInfo.holdingsWidth;
6168 if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6170 /* [AS] Initialize pv info list [HGM] and game status */
6172 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6173 pvInfoList[i].depth = 0;
6174 boards[i][EP_STATUS] = EP_NONE;
6175 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6178 initialRulePlies = 0; /* 50-move counter start */
6180 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6181 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6185 /* [HGM] logic here is completely changed. In stead of full positions */
6186 /* the initialized data only consist of the two backranks. The switch */
6187 /* selects which one we will use, which is than copied to the Board */
6188 /* initialPosition, which for the rest is initialized by Pawns and */
6189 /* empty squares. This initial position is then copied to boards[0], */
6190 /* possibly after shuffling, so that it remains available. */
6192 gameInfo.holdingsWidth = 0; /* default board sizes */
6193 gameInfo.boardWidth = 8;
6194 gameInfo.boardHeight = 8;
6195 gameInfo.holdingsSize = 0;
6196 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6197 for(i=0; i<BOARD_FILES-6; i++)
6198 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6199 initialPosition[EP_STATUS] = EP_NONE;
6200 initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6201 SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6202 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6203 SetCharTable(pieceNickName, appData.pieceNickNames);
6204 else SetCharTable(pieceNickName, "............");
6207 switch (gameInfo.variant) {
6208 case VariantFischeRandom:
6209 shuffleOpenings = TRUE;
6210 appData.fischerCastling = TRUE;
6213 case VariantShatranj:
6214 pieces = ShatranjArray;
6215 nrCastlingRights = 0;
6216 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6219 pieces = makrukArray;
6220 nrCastlingRights = 0;
6221 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6224 pieces = aseanArray;
6225 nrCastlingRights = 0;
6226 SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6228 case VariantTwoKings:
6229 pieces = twoKingsArray;
6232 pieces = GrandArray;
6233 nrCastlingRights = 0;
6234 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6235 gameInfo.boardWidth = 10;
6236 gameInfo.boardHeight = 10;
6237 gameInfo.holdingsSize = 7;
6239 case VariantCapaRandom:
6240 shuffleOpenings = TRUE;
6241 appData.fischerCastling = TRUE;
6242 case VariantCapablanca:
6243 pieces = CapablancaArray;
6244 gameInfo.boardWidth = 10;
6245 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6248 pieces = GothicArray;
6249 gameInfo.boardWidth = 10;
6250 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6253 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6254 gameInfo.holdingsSize = 7;
6255 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6258 pieces = JanusArray;
6259 gameInfo.boardWidth = 10;
6260 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6261 nrCastlingRights = 6;
6262 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6263 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6264 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6265 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6266 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6267 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6270 pieces = FalconArray;
6271 gameInfo.boardWidth = 10;
6272 SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6274 case VariantXiangqi:
6275 pieces = XiangqiArray;
6276 gameInfo.boardWidth = 9;
6277 gameInfo.boardHeight = 10;
6278 nrCastlingRights = 0;
6279 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6282 pieces = ShogiArray;
6283 gameInfo.boardWidth = 9;
6284 gameInfo.boardHeight = 9;
6285 gameInfo.holdingsSize = 7;
6286 nrCastlingRights = 0;
6287 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6290 pieces = ChuArray; pieceRows = 3;
6291 gameInfo.boardWidth = 12;
6292 gameInfo.boardHeight = 12;
6293 nrCastlingRights = 0;
6294 // SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6295 // "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6296 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"
6297 "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);
6299 case VariantCourier:
6300 pieces = CourierArray;
6301 gameInfo.boardWidth = 12;
6302 nrCastlingRights = 0;
6303 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6305 case VariantKnightmate:
6306 pieces = KnightmateArray;
6307 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6309 case VariantSpartan:
6310 pieces = SpartanArray;
6311 SetCharTable(pieceToChar, "PNBRQ.....................K......lw......g...h......ck");
6315 SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6317 case VariantChuChess:
6318 pieces = ChuChessArray;
6319 gameInfo.boardWidth = 10;
6320 gameInfo.boardHeight = 10;
6321 SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6324 pieces = fairyArray;
6325 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6328 pieces = GreatArray;
6329 gameInfo.boardWidth = 10;
6330 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6331 gameInfo.holdingsSize = 8;
6335 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6336 gameInfo.holdingsSize = 8;
6337 startedFromSetupPosition = TRUE;
6339 case VariantCrazyhouse:
6340 case VariantBughouse:
6342 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6343 gameInfo.holdingsSize = 5;
6345 case VariantWildCastle:
6347 /* !!?shuffle with kings guaranteed to be on d or e file */
6348 shuffleOpenings = 1;
6350 case VariantNoCastle:
6352 nrCastlingRights = 0;
6353 /* !!?unconstrained back-rank shuffle */
6354 shuffleOpenings = 1;
6359 if(appData.NrFiles >= 0) {
6360 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6361 gameInfo.boardWidth = appData.NrFiles;
6363 if(appData.NrRanks >= 0) {
6364 gameInfo.boardHeight = appData.NrRanks;
6366 if(appData.holdingsSize >= 0) {
6367 i = appData.holdingsSize;
6368 // if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6369 gameInfo.holdingsSize = i;
6371 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6372 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6373 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6375 if(!handSize) handSize = BOARD_HEIGHT;
6376 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6377 if(pawnRow < 1) pawnRow = 1;
6378 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6379 gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6380 if(gameInfo.variant == VariantChu) pawnRow = 3;
6382 /* User pieceToChar list overrules defaults */
6383 if(appData.pieceToCharTable != NULL)
6384 SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6386 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6388 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6389 s = (ChessSquare) 0; /* account holding counts in guard band */
6390 for( i=0; i<BOARD_HEIGHT; i++ )
6391 initialPosition[i][j] = s;
6393 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6394 initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6395 initialPosition[pawnRow][j] = WhitePawn;
6396 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6397 if(gameInfo.variant == VariantXiangqi) {
6399 initialPosition[pawnRow][j] =
6400 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6401 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6402 initialPosition[2][j] = WhiteCannon;
6403 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6407 if(gameInfo.variant == VariantChu) {
6408 if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6409 initialPosition[pawnRow+1][j] = WhiteCobra,
6410 initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6411 for(i=1; i<pieceRows; i++) {
6412 initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6413 initialPosition[BOARD_HEIGHT-1-i][j] = pieces[2*i+1][j-gameInfo.holdingsWidth];
6416 if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6417 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6418 initialPosition[0][j] = WhiteRook;
6419 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6422 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] = pieces[1][j-gameInfo.holdingsWidth];
6424 if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6425 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6428 initialPosition[1][j] = WhiteBishop;
6429 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6431 initialPosition[1][j] = WhiteRook;
6432 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6435 if( nrCastlingRights == -1) {
6436 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6437 /* This sets default castling rights from none to normal corners */
6438 /* Variants with other castling rights must set them themselves above */
6439 nrCastlingRights = 6;
6441 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6442 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6443 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6444 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6445 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6446 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6449 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6450 if(gameInfo.variant == VariantGreat) { // promotion commoners
6451 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6452 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6453 initialPosition[handSize-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6454 initialPosition[handSize-1-PieceToNumber(WhiteMan)][1] = 9;
6456 if( gameInfo.variant == VariantSChess ) {
6457 initialPosition[1][0] = BlackMarshall;
6458 initialPosition[2][0] = BlackAngel;
6459 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6460 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6461 initialPosition[1][1] = initialPosition[2][1] =
6462 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6464 initialPosition[CHECK_COUNT] = (gameInfo.variant == Variant3Check ? 0x303 : 0);
6465 if (appData.debugMode) {
6466 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6468 if(shuffleOpenings) {
6469 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6470 startedFromSetupPosition = TRUE;
6472 if(startedFromPositionFile) {
6473 /* [HGM] loadPos: use PositionFile for every new game */
6474 CopyBoard(initialPosition, filePosition);
6475 for(i=0; i<nrCastlingRights; i++)
6476 initialRights[i] = filePosition[CASTLING][i];
6477 startedFromSetupPosition = TRUE;
6479 if(*appData.men) LoadPieceDesc(appData.men);
6481 CopyBoard(boards[0], initialPosition);
6483 if(oldx != gameInfo.boardWidth ||
6484 oldy != gameInfo.boardHeight ||
6485 oldv != gameInfo.variant ||
6486 oldh != gameInfo.holdingsWidth
6488 InitDrawingSizes(-2 ,0);
6490 oldv = gameInfo.variant;
6492 DrawPosition(TRUE, boards[currentMove]);
6496 SendBoard (ChessProgramState *cps, int moveNum)
6498 char message[MSG_SIZ];
6500 if (cps->useSetboard) {
6501 char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6502 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6503 SendToProgram(message, cps);
6508 int i, j, left=0, right=BOARD_WIDTH;
6509 /* Kludge to set black to move, avoiding the troublesome and now
6510 * deprecated "black" command.
6512 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6513 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn && !pieceDesc[WhitePawn] ? "a2a3\n" : "black\nforce\n", cps);
6515 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6517 SendToProgram("edit\n", cps);
6518 SendToProgram("#\n", cps);
6519 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6520 bp = &boards[moveNum][i][left];
6521 for (j = left; j < right; j++, bp++) {
6522 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6523 if ((int) *bp < (int) BlackPawn) {
6524 if(j == BOARD_RGHT+1)
6525 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6526 else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6527 if(message[0] == '+' || message[0] == '~') {
6528 snprintf(message, MSG_SIZ,"%c%c%d+\n",
6529 PieceToChar((ChessSquare)(DEMOTED(*bp))),
6530 AAA + j, ONE + i - '0');
6532 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6533 message[1] = BOARD_RGHT - 1 - j + '1';
6534 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6536 SendToProgram(message, cps);
6541 SendToProgram("c\n", cps);
6542 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6543 bp = &boards[moveNum][i][left];
6544 for (j = left; j < right; j++, bp++) {
6545 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6546 if (((int) *bp != (int) EmptySquare)
6547 && ((int) *bp >= (int) BlackPawn)) {
6548 if(j == BOARD_LEFT-2)
6549 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6550 else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6551 AAA + j, ONE + i - '0');
6552 if(message[0] == '+' || message[0] == '~') {
6553 snprintf(message, MSG_SIZ,"%c%c%d+\n",
6554 PieceToChar((ChessSquare)(DEMOTED(*bp))),
6555 AAA + j, ONE + i - '0');
6557 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6558 message[1] = BOARD_RGHT - 1 - j + '1';
6559 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6561 SendToProgram(message, cps);
6566 SendToProgram(".\n", cps);
6568 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6571 char exclusionHeader[MSG_SIZ];
6572 int exCnt, excludePtr;
6573 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6574 static Exclusion excluTab[200];
6575 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6581 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6582 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6588 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6589 excludePtr = 24; exCnt = 0;
6594 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6595 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6596 char buf[2*MOVE_LEN], *p;
6597 Exclusion *e = excluTab;
6599 for(i=0; i<exCnt; i++)
6600 if(e[i].ff == fromX && e[i].fr == fromY &&
6601 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6602 if(i == exCnt) { // was not in exclude list; add it
6603 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6604 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6605 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6608 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6609 excludePtr++; e[i].mark = excludePtr++;
6610 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6613 exclusionHeader[e[i].mark] = state;
6617 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6618 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6622 if((signed char)promoChar == -1) { // kludge to indicate best move
6623 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6624 return 1; // if unparsable, abort
6626 // update exclusion map (resolving toggle by consulting existing state)
6627 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6629 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6630 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6631 excludeMap[k] |= 1<<j;
6632 else excludeMap[k] &= ~(1<<j);
6634 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6636 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6637 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6639 return (state == '+');
6643 ExcludeClick (int index)
6646 Exclusion *e = excluTab;
6647 if(index < 25) { // none, best or tail clicked
6648 if(index < 13) { // none: include all
6649 WriteMap(0); // clear map
6650 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6651 SendToBoth("include all\n"); // and inform engine
6652 } else if(index > 18) { // tail
6653 if(exclusionHeader[19] == '-') { // tail was excluded
6654 SendToBoth("include all\n");
6655 WriteMap(0); // clear map completely
6656 // now re-exclude selected moves
6657 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6658 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6659 } else { // tail was included or in mixed state
6660 SendToBoth("exclude all\n");
6661 WriteMap(0xFF); // fill map completely
6662 // now re-include selected moves
6663 j = 0; // count them
6664 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6665 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6666 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6669 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6672 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6673 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6674 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6681 DefaultPromoChoice (int white)
6684 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6685 gameInfo.variant == VariantMakruk)
6686 result = WhiteFerz; // no choice
6687 else if(gameInfo.variant == VariantASEAN)
6688 result = WhiteRook; // no choice
6689 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6690 result= WhiteKing; // in Suicide Q is the last thing we want
6691 else if(gameInfo.variant == VariantSpartan)
6692 result = white ? WhiteQueen : WhiteAngel;
6693 else result = WhiteQueen;
6694 if(!white) result = WHITE_TO_BLACK result;
6698 static int autoQueen; // [HGM] oneclick
6701 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6703 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6704 /* [HGM] add Shogi promotions */
6705 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6706 ChessSquare piece, partner;
6710 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6711 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6713 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6714 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6717 if(legal[toY][toX] == 4) return FALSE;
6719 piece = boards[currentMove][fromY][fromX];
6720 if(gameInfo.variant == VariantChu) {
6721 promotionZoneSize = (BOARD_HEIGHT - deadRanks)/3;
6722 if(legal[toY][toX] == 6) return FALSE; // no promotion if highlights deny it
6723 highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6724 } else if(gameInfo.variant == VariantShogi) {
6725 promotionZoneSize = (BOARD_HEIGHT- deadRanks)/3 +(BOARD_HEIGHT == 8);
6726 highestPromotingPiece = (int)WhiteAlfil;
6727 if(PieceToChar(piece) != '+' || PieceToChar(CHUPROMOTED(piece)) == '+') highestPromotingPiece = piece;
6728 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6729 promotionZoneSize = 3;
6732 // Treat Lance as Pawn when it is not representing Amazon or Lance
6733 if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6734 if(piece == WhiteLance) piece = WhitePawn; else
6735 if(piece == BlackLance) piece = BlackPawn;
6738 // next weed out all moves that do not touch the promotion zone at all
6739 if((int)piece >= BlackPawn) {
6740 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6742 if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6743 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6745 if( toY < BOARD_HEIGHT - deadRanks - promotionZoneSize &&
6746 fromY < BOARD_HEIGHT - deadRanks - promotionZoneSize) return FALSE;
6747 if(fromY >= BOARD_HEIGHT - deadRanks - promotionZoneSize && gameInfo.variant == VariantChuChess)
6751 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6753 // weed out mandatory Shogi promotions
6754 if(gameInfo.variant == VariantShogi) {
6755 if(piece >= BlackPawn) {
6756 if(toY == 0 && piece == BlackPawn ||
6757 toY == 0 && piece == BlackQueen ||
6758 toY <= 1 && piece == BlackKnight) {
6763 if(toY == BOARD_HEIGHT-deadRanks-1 && piece == WhitePawn ||
6764 toY == BOARD_HEIGHT-deadRanks-1 && piece == WhiteQueen ||
6765 toY >= BOARD_HEIGHT-deadRanks-2 && piece == WhiteKnight) {
6772 // weed out obviously illegal Pawn moves
6773 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6774 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6775 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6776 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6777 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6778 // note we are not allowed to test for valid (non-)capture, due to premove
6781 // we either have a choice what to promote to, or (in Shogi) whether to promote
6782 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6783 gameInfo.variant == VariantMakruk) {
6784 ChessSquare p=BlackFerz; // no choice
6785 while(p < EmptySquare) { //but make sure we use piece that exists
6786 *promoChoice = PieceToChar(p++);
6787 if(*promoChoice != '.') break;
6789 if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6791 // no sense asking what we must promote to if it is going to explode...
6792 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6793 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6796 // give caller the default choice even if we will not make it
6797 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6798 partner = piece; // pieces can promote if the pieceToCharTable says so
6799 if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6800 else if(Partner(&partner)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6801 if( sweepSelect && gameInfo.variant != VariantGreat
6802 && gameInfo.variant != VariantGrand
6803 && gameInfo.variant != VariantSuper) return FALSE;
6804 if(autoQueen) return FALSE; // predetermined
6806 // suppress promotion popup on illegal moves that are not premoves
6807 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6808 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6809 if(appData.testLegality && !premove) {
6810 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6811 fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6812 if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6813 if(moveType != WhitePromotion && moveType != BlackPromotion)
6821 InPalace (int row, int column)
6822 { /* [HGM] for Xiangqi */
6823 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6824 column < (BOARD_WIDTH + 4)/2 &&
6825 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6830 PieceForSquare (int x, int y)
6832 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT) return -1;
6833 if(x == BOARD_RGHT+1 && handOffsets & 1) y += handSize - BOARD_HEIGHT;
6834 if(x == BOARD_LEFT-2 && !(handOffsets & 2)) y += handSize - BOARD_HEIGHT;
6835 return boards[currentMove][y][x];
6839 More (Board board, int col, int start, int end)
6842 for(k=start; k<end; k++) if(board[k][col]) return (col == 1 ? WhiteMonarch : BlackMonarch); // arrow image
6847 DrawPosition (int repaint, Board board)
6849 Board compactedBoard;
6850 if(handSize > BOARD_HEIGHT && board) {
6852 CopyBoard(compactedBoard, board);
6853 if(handOffsets & 1) {
6854 for(k=0; k<BOARD_HEIGHT; k++) {
6855 compactedBoard[k][BOARD_WIDTH-1] = board[k+handSize-BOARD_HEIGHT][BOARD_WIDTH-1];
6856 compactedBoard[k][BOARD_WIDTH-2] = board[k+handSize-BOARD_HEIGHT][BOARD_WIDTH-2];
6858 compactedBoard[0][BOARD_WIDTH-1] = More(board, BOARD_WIDTH-2, 0, handSize-BOARD_HEIGHT+1);
6859 } else compactedBoard[BOARD_HEIGHT-1][BOARD_WIDTH-1] = More(board, BOARD_WIDTH-2, BOARD_HEIGHT-1, handSize);
6860 if(!(handOffsets & 2)) {
6861 for(k=0; k<BOARD_HEIGHT; k++) {
6862 compactedBoard[k][0] = board[k+handSize-BOARD_HEIGHT][0];
6863 compactedBoard[k][1] = board[k+handSize-BOARD_HEIGHT][1];
6865 compactedBoard[0][0] = More(board, 1, 0, handSize-BOARD_HEIGHT+1);
6866 } else compactedBoard[BOARD_HEIGHT-1][0] = More(board, 1, BOARD_HEIGHT-1, handSize);
6867 DrawPositionX(TRUE, compactedBoard);
6868 } else DrawPositionX(repaint, board);
6872 OKToStartUserMove (int x, int y)
6874 ChessSquare from_piece;
6877 if (matchMode) return FALSE;
6878 if (gameMode == EditPosition) return TRUE;
6880 if (x >= 0 && y >= 0)
6881 from_piece = boards[currentMove][y][x];
6883 from_piece = EmptySquare;
6885 if (from_piece == EmptySquare) return FALSE;
6887 white_piece = (int)from_piece >= (int)WhitePawn &&
6888 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6892 case TwoMachinesPlay:
6900 case MachinePlaysWhite:
6901 case IcsPlayingBlack:
6902 if (appData.zippyPlay) return FALSE;
6904 DisplayMoveError(_("You are playing Black"));
6909 case MachinePlaysBlack:
6910 case IcsPlayingWhite:
6911 if (appData.zippyPlay) return FALSE;
6913 DisplayMoveError(_("You are playing White"));
6918 case PlayFromGameFile:
6919 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6922 if (!white_piece && WhiteOnMove(currentMove)) {
6923 DisplayMoveError(_("It is White's turn"));
6926 if (white_piece && !WhiteOnMove(currentMove)) {
6927 DisplayMoveError(_("It is Black's turn"));
6930 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6931 /* Editing correspondence game history */
6932 /* Could disallow this or prompt for confirmation */
6937 case BeginningOfGame:
6938 if (appData.icsActive) return FALSE;
6939 if (!appData.noChessProgram) {
6941 DisplayMoveError(_("You are playing White"));
6948 if (!white_piece && WhiteOnMove(currentMove)) {
6949 DisplayMoveError(_("It is White's turn"));
6952 if (white_piece && !WhiteOnMove(currentMove)) {
6953 DisplayMoveError(_("It is Black's turn"));
6962 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6963 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6964 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6965 && gameMode != AnalyzeFile && gameMode != Training) {
6966 DisplayMoveError(_("Displayed position is not current"));
6973 OnlyMove (int *x, int *y, Boolean captures)
6975 DisambiguateClosure cl;
6976 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6978 case MachinePlaysBlack:
6979 case IcsPlayingWhite:
6980 case BeginningOfGame:
6981 if(!WhiteOnMove(currentMove)) return FALSE;
6983 case MachinePlaysWhite:
6984 case IcsPlayingBlack:
6985 if(WhiteOnMove(currentMove)) return FALSE;
6992 cl.pieceIn = EmptySquare;
6997 cl.promoCharIn = NULLCHAR;
6998 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6999 if( cl.kind == NormalMove ||
7000 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
7001 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
7002 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
7009 if(cl.kind != ImpossibleMove) return FALSE;
7010 cl.pieceIn = EmptySquare;
7015 cl.promoCharIn = NULLCHAR;
7016 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
7017 if( cl.kind == NormalMove ||
7018 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
7019 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
7020 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
7025 autoQueen = TRUE; // act as if autoQueen on when we click to-square
7031 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
7032 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
7033 int lastLoadGameUseList = FALSE;
7034 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
7035 ChessMove lastLoadGameStart = EndOfFile;
7037 Boolean addToBookFlag;
7038 static Board rightsBoard, nullBoard;
7041 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
7045 int ff=fromX, rf=fromY, ft=toX, rt=toY;
7047 /* Check if the user is playing in turn. This is complicated because we
7048 let the user "pick up" a piece before it is his turn. So the piece he
7049 tried to pick up may have been captured by the time he puts it down!
7050 Therefore we use the color the user is supposed to be playing in this
7051 test, not the color of the piece that is currently on the starting
7052 square---except in EditGame mode, where the user is playing both
7053 sides; fortunately there the capture race can't happen. (It can
7054 now happen in IcsExamining mode, but that's just too bad. The user
7055 will get a somewhat confusing message in that case.)
7060 case TwoMachinesPlay:
7064 /* We switched into a game mode where moves are not accepted,
7065 perhaps while the mouse button was down. */
7068 case MachinePlaysWhite:
7069 /* User is moving for Black */
7070 if (WhiteOnMove(currentMove)) {
7071 DisplayMoveError(_("It is White's turn"));
7076 case MachinePlaysBlack:
7077 /* User is moving for White */
7078 if (!WhiteOnMove(currentMove)) {
7079 DisplayMoveError(_("It is Black's turn"));
7084 case PlayFromGameFile:
7085 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7088 case BeginningOfGame:
7091 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7092 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7093 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7094 /* User is moving for Black */
7095 if (WhiteOnMove(currentMove)) {
7096 DisplayMoveError(_("It is White's turn"));
7100 /* User is moving for White */
7101 if (!WhiteOnMove(currentMove)) {
7102 DisplayMoveError(_("It is Black's turn"));
7108 case IcsPlayingBlack:
7109 /* User is moving for Black */
7110 if (WhiteOnMove(currentMove)) {
7111 if (!appData.premove) {
7112 DisplayMoveError(_("It is White's turn"));
7113 } else if (toX >= 0 && toY >= 0) {
7116 premoveFromX = fromX;
7117 premoveFromY = fromY;
7118 premovePromoChar = promoChar;
7120 if (appData.debugMode)
7121 fprintf(debugFP, "Got premove: fromX %d,"
7122 "fromY %d, toX %d, toY %d\n",
7123 fromX, fromY, toX, toY);
7125 DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7130 case IcsPlayingWhite:
7131 /* User is moving for White */
7132 if (!WhiteOnMove(currentMove)) {
7133 if (!appData.premove) {
7134 DisplayMoveError(_("It is Black's turn"));
7135 } else if (toX >= 0 && toY >= 0) {
7138 premoveFromX = fromX;
7139 premoveFromY = fromY;
7140 premovePromoChar = promoChar;
7142 if (appData.debugMode)
7143 fprintf(debugFP, "Got premove: fromX %d,"
7144 "fromY %d, toX %d, toY %d\n",
7145 fromX, fromY, toX, toY);
7147 DrawPosition(TRUE, boards[currentMove]);
7156 /* EditPosition, empty square, or different color piece;
7157 click-click move is possible */
7158 if (toX == -2 || toY == -2) {
7159 boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7160 DrawPosition(FALSE, boards[currentMove]);
7162 } else if (toX >= 0 && toY >= 0) {
7163 if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7164 ChessSquare p = boards[0][rf][ff];
7165 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7166 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7167 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
7168 int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7169 DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7172 } else rightsBoard[toY][toX] = 0; // revoke rights on moving
7173 boards[0][toY][toX] = boards[0][fromY][fromX];
7174 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7175 if(boards[0][fromY][0] != EmptySquare) {
7176 if(boards[0][fromY][1]) boards[0][fromY][1]--;
7177 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
7180 if(fromX == BOARD_RGHT+1) {
7181 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7182 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7183 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7186 boards[0][fromY][fromX] = gatingPiece;
7188 DrawPosition(FALSE, boards[currentMove]);
7194 if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7195 pup = boards[currentMove][toY][toX];
7197 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7198 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7199 if( pup != EmptySquare ) return;
7200 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7201 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7202 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7203 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7204 if(fromX == 0) fromY = handSize-1 - fromY; // black holdings upside-down
7205 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7206 while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7210 /* [HGM] always test for legality, to get promotion info */
7211 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7212 fromY, fromX, toY, toX, promoChar);
7214 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7216 if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7218 /* [HGM] but possibly ignore an IllegalMove result */
7219 if (appData.testLegality) {
7220 if (moveType == IllegalMove || moveType == ImpossibleMove) {
7221 DisplayMoveError(_("Illegal move"));
7226 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7227 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7228 ClearPremoveHighlights(); // was included
7229 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
7230 DrawPosition(FALSE, NULL);
7234 if(addToBookFlag) { // adding moves to book
7235 char buf[MSG_SIZ], move[MSG_SIZ];
7236 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7237 if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7238 killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7239 snprintf(buf, MSG_SIZ, " 0.0%% 1 %s\n", move);
7241 addToBookFlag = FALSE;
7246 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7249 /* Common tail of UserMoveEvent and DropMenuEvent */
7251 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7255 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7256 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7257 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7258 if(WhiteOnMove(currentMove)) {
7259 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7261 if(!boards[currentMove][handSize-1-k][1]) return 0;
7265 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7266 move type in caller when we know the move is a legal promotion */
7267 if(moveType == NormalMove && promoChar)
7268 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7270 /* [HGM] <popupFix> The following if has been moved here from
7271 UserMoveEvent(). Because it seemed to belong here (why not allow
7272 piece drops in training games?), and because it can only be
7273 performed after it is known to what we promote. */
7274 if (gameMode == Training) {
7275 /* compare the move played on the board to the next move in the
7276 * game. If they match, display the move and the opponent's response.
7277 * If they don't match, display an error message.
7281 CopyBoard(testBoard, boards[currentMove]);
7282 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7284 if (CompareBoards(testBoard, boards[currentMove+1])) {
7285 ForwardInner(currentMove+1);
7287 /* Autoplay the opponent's response.
7288 * if appData.animate was TRUE when Training mode was entered,
7289 * the response will be animated.
7291 saveAnimate = appData.animate;
7292 appData.animate = animateTraining;
7293 ForwardInner(currentMove+1);
7294 appData.animate = saveAnimate;
7296 /* check for the end of the game */
7297 if (currentMove >= forwardMostMove) {
7298 gameMode = PlayFromGameFile;
7300 SetTrainingModeOff();
7301 DisplayInformation(_("End of game"));
7304 DisplayError(_("Incorrect move"), 0);
7309 /* Ok, now we know that the move is good, so we can kill
7310 the previous line in Analysis Mode */
7311 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7312 && currentMove < forwardMostMove) {
7313 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7314 else forwardMostMove = currentMove;
7319 /* If we need the chess program but it's dead, restart it */
7320 ResurrectChessProgram();
7322 /* A user move restarts a paused game*/
7326 thinkOutput[0] = NULLCHAR;
7328 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7330 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7331 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7335 if (gameMode == BeginningOfGame) {
7336 if (appData.noChessProgram) {
7337 gameMode = EditGame;
7341 gameMode = MachinePlaysBlack;
7344 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7346 if (first.sendName) {
7347 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7348 SendToProgram(buf, &first);
7355 /* Relay move to ICS or chess engine */
7356 if (appData.icsActive) {
7357 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7358 gameMode == IcsExamining) {
7359 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7360 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7362 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7364 // also send plain move, in case ICS does not understand atomic claims
7365 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7369 if (first.sendTime && (gameMode == BeginningOfGame ||
7370 gameMode == MachinePlaysWhite ||
7371 gameMode == MachinePlaysBlack)) {
7372 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7374 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7375 // [HGM] book: if program might be playing, let it use book
7376 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7377 first.maybeThinking = TRUE;
7378 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7379 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7380 SendBoard(&first, currentMove+1);
7381 if(second.analyzing) {
7382 if(!second.useSetboard) SendToProgram("undo\n", &second);
7383 SendBoard(&second, currentMove+1);
7386 SendMoveToProgram(forwardMostMove-1, &first);
7387 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7389 if (currentMove == cmailOldMove + 1) {
7390 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7394 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7398 if(appData.testLegality)
7399 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7405 if (WhiteOnMove(currentMove)) {
7406 GameEnds(BlackWins, "Black mates", GE_PLAYER);
7408 GameEnds(WhiteWins, "White mates", GE_PLAYER);
7412 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7417 case MachinePlaysBlack:
7418 case MachinePlaysWhite:
7419 /* disable certain menu options while machine is thinking */
7420 SetMachineThinkingEnables();
7427 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7428 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7430 if(bookHit) { // [HGM] book: simulate book reply
7431 static char bookMove[MSG_SIZ]; // a bit generous?
7433 programStats.nodes = programStats.depth = programStats.time =
7434 programStats.score = programStats.got_only_move = 0;
7435 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7437 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7438 strcat(bookMove, bookHit);
7439 HandleMachineMove(bookMove, &first);
7445 MarkByFEN(char *fen)
7448 if(!appData.markers || !appData.highlightDragging) return;
7449 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = marker[r][f] = 0;
7450 r=BOARD_HEIGHT-1-deadRanks; f=BOARD_LEFT;
7453 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7454 if(*fen == 'B') legal[r][f] = 4; else // request auto-promotion to victim
7455 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 6; else
7456 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7457 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7458 if(*fen == 'T') marker[r][f++] = 0; else
7459 if(*fen == 'Y') marker[r][f++] = 1; else
7460 if(*fen == 'G') marker[r][f++] = 3; else
7461 if(*fen == 'B') marker[r][f++] = 4; else
7462 if(*fen == 'C') marker[r][f++] = 5; else
7463 if(*fen == 'M') marker[r][f++] = 6; else
7464 if(*fen == 'W') marker[r][f++] = 7; else
7465 if(*fen == 'D') marker[r][f++] = 8; else
7466 if(*fen == 'R') marker[r][f++] = 2; else {
7467 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7470 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7474 DrawPosition(TRUE, NULL);
7477 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7480 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7482 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7483 Markers *m = (Markers *) closure;
7484 if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7485 kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7486 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7487 || kind == WhiteCapturesEnPassant
7488 || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7489 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7492 static int hoverSavedValid;
7495 MarkTargetSquares (int clear)
7498 if(clear) { // no reason to ever suppress clearing
7499 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7500 hoverSavedValid = 0;
7501 if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7504 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7505 !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7506 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7507 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7508 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7510 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = legal[y][x] = 0;
7513 DrawPosition(FALSE, NULL);
7517 Explode (Board board, int fromX, int fromY, int toX, int toY)
7519 if(gameInfo.variant == VariantAtomic &&
7520 (board[toY][toX] != EmptySquare || // capture?
7521 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7522 board[fromY][fromX] == BlackPawn )
7524 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7530 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7533 CanPromote (ChessSquare piece, int y)
7535 int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7536 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7537 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7538 if(IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantXiangqi ||
7539 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7540 (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7541 gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7542 return (piece == BlackPawn && y <= zone ||
7543 piece == WhitePawn && y >= BOARD_HEIGHT-1-deadRanks-zone ||
7544 piece == BlackLance && y <= zone ||
7545 piece == WhiteLance && y >= BOARD_HEIGHT-1-deadRanks-zone );
7549 HoverEvent (int xPix, int yPix, int x, int y)
7551 static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7553 if(!first.highlight) return;
7554 if(fromX != oldFromX || fromY != oldFromY) oldX = oldY = -1; // kludge to fake entry on from-click
7555 if(x == oldX && y == oldY) return; // only do something if we enter new square
7556 oldFromX = fromX; oldFromY = fromY;
7557 if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7558 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7559 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7560 hoverSavedValid = 1;
7561 } else if(oldX != x || oldY != y) {
7562 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7563 if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7564 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7565 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7566 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7568 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7569 SendToProgram(buf, &first);
7572 // SetHighlights(fromX, fromY, x, y);
7576 void ReportClick(char *action, int x, int y)
7578 char buf[MSG_SIZ]; // Inform engine of what user does
7580 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7581 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7582 legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7583 if(!first.highlight || gameMode == EditPosition) return;
7584 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7585 SendToProgram(buf, &first);
7588 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7589 Boolean deferChoice;
7590 int createX = -1, createY = -1; // square where we last created a piece in EditPosition mode
7593 LeftClick (ClickType clickType, int xPix, int yPix)
7596 static Boolean saveAnimate;
7597 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7598 char promoChoice = NULLCHAR;
7600 static TimeMark lastClickTime, prevClickTime;
7602 if(flashing) return;
7604 if(!deferChoice) { // when called for a retry, skip everything to the point where we left off
7605 x = EventToSquare(xPix, BOARD_WIDTH);
7606 y = EventToSquare(yPix, BOARD_HEIGHT);
7607 if (!flipView && y >= 0) {
7608 y = BOARD_HEIGHT - 1 - y;
7610 if (flipView && x >= 0) {
7611 x = BOARD_WIDTH - 1 - x;
7614 // map clicks in offsetted holdings back to true coords (or switch the offset)
7615 if(x == BOARD_RGHT+1) {
7616 if(handOffsets & 1) {
7617 if(y == 0) { handOffsets &= ~1; DrawPosition(TRUE, boards[currentMove]); return; }
7618 y += handSize - BOARD_HEIGHT;
7619 } else if(y == BOARD_HEIGHT-1) { handOffsets |= 1; DrawPosition(TRUE, boards[currentMove]); return; }
7621 if(x == BOARD_LEFT-2) {
7622 if(!(handOffsets & 2)) {
7623 if(y == 0) { handOffsets |= 2; DrawPosition(TRUE, boards[currentMove]); return; }
7624 y += handSize - BOARD_HEIGHT;
7625 } else if(y == BOARD_HEIGHT-1) { handOffsets &= ~2; DrawPosition(TRUE, boards[currentMove]); return; }
7628 if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press &&
7629 (boards[currentMove][y][x] == EmptySquare || x == createX && y == createY) ) {
7631 RightClick(clickType, xPix, yPix, &dummy, &dummy);
7636 createX = createY = -1;
7638 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7640 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7642 if (clickType == Press) ErrorPopDown();
7643 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7645 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7646 defaultPromoChoice = promoSweep;
7647 promoSweep = EmptySquare; // terminate sweep
7648 promoDefaultAltered = TRUE;
7649 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7652 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7653 if(clickType == Release) return; // ignore upclick of click-click destination
7654 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7655 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7656 if(gameInfo.holdingsWidth &&
7657 (WhiteOnMove(currentMove)
7658 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7659 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7660 // click in right holdings, for determining promotion piece
7661 ChessSquare p = boards[currentMove][y][x];
7662 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7663 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7664 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7665 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7670 DrawPosition(FALSE, boards[currentMove]);
7674 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7675 if(clickType == Press
7676 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7677 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7678 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7681 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7682 // could be static click on premove from-square: abort premove
7684 ClearPremoveHighlights();
7687 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7688 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7690 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7691 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7692 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7693 defaultPromoChoice = DefaultPromoChoice(side);
7696 autoQueen = appData.alwaysPromoteToQueen;
7700 gatingPiece = EmptySquare;
7701 if (clickType != Press) {
7702 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7703 DragPieceEnd(xPix, yPix); dragging = 0;
7704 DrawPosition(FALSE, NULL);
7708 doubleClick = FALSE;
7709 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7710 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7712 fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7713 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7714 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7715 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7717 if (OKToStartUserMove(fromX, fromY)) {
7719 ReportClick("lift", x, y);
7720 MarkTargetSquares(0);
7721 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7722 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7723 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7724 promoSweep = defaultPromoChoice;
7725 selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7726 Sweep(0); // Pawn that is going to promote: preview promotion piece
7727 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7729 if (appData.highlightDragging) {
7730 SetHighlights(fromX, fromY, -1, -1);
7734 } else fromX = fromY = -1;
7740 if (clickType == Press && gameMode != EditPosition) {
7745 // ignore off-board to clicks
7746 if(y < 0 || x < 0) return;
7748 /* Check if clicking again on the same color piece */
7749 fromP = boards[currentMove][fromY][fromX];
7750 toP = boards[currentMove][y][x];
7751 frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7752 if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7753 marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7754 ((WhitePawn <= fromP && fromP <= WhiteKing &&
7755 WhitePawn <= toP && toP <= WhiteKing &&
7756 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7757 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7758 (BlackPawn <= fromP && fromP <= BlackKing &&
7759 BlackPawn <= toP && toP <= BlackKing &&
7760 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7761 !(fromP == BlackKing && toP == BlackRook && frc)))) {
7762 /* Clicked again on same color piece -- changed his mind */
7763 second = (x == fromX && y == fromY);
7764 killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7765 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7766 second = FALSE; // first double-click rather than scond click
7767 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7769 promoDefaultAltered = FALSE;
7770 if(!second) MarkTargetSquares(1);
7771 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7772 if (appData.highlightDragging) {
7773 SetHighlights(x, y, -1, -1);
7777 if (OKToStartUserMove(x, y)) {
7778 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7779 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7780 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7781 gatingPiece = boards[currentMove][fromY][fromX];
7782 else gatingPiece = doubleClick ? fromP : EmptySquare;
7784 fromY = y; dragging = 1;
7785 if(!second) ReportClick("lift", x, y);
7786 MarkTargetSquares(0);
7787 DragPieceBegin(xPix, yPix, FALSE);
7788 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7789 promoSweep = defaultPromoChoice;
7790 selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7791 Sweep(0); // Pawn that is going to promote: preview promotion piece
7795 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7798 // ignore clicks on holdings
7799 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7802 if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7803 gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7804 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7808 if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7809 DragPieceEnd(xPix, yPix); dragging = 0;
7811 // a deferred attempt to click-click move an empty square on top of a piece
7812 boards[currentMove][y][x] = EmptySquare;
7814 DrawPosition(FALSE, boards[currentMove]);
7815 fromX = fromY = -1; clearFlag = 0;
7818 if (appData.animateDragging) {
7819 /* Undo animation damage if any */
7820 DrawPosition(FALSE, NULL);
7823 /* Second up/down in same square; just abort move */
7826 gatingPiece = EmptySquare;
7829 ClearPremoveHighlights();
7830 MarkTargetSquares(-1);
7831 DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7833 /* First upclick in same square; start click-click mode */
7834 SetHighlights(x, y, -1, -1);
7841 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7842 fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7843 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7844 DisplayMessage(_("only marked squares are legal"),"");
7845 DrawPosition(TRUE, NULL);
7846 return; // ignore to-click
7849 /* we now have a different from- and (possibly off-board) to-square */
7850 /* Completed move */
7851 if(!sweepSelecting) {
7856 piece = boards[currentMove][fromY][fromX];
7858 saveAnimate = appData.animate;
7859 if (clickType == Press) {
7860 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7861 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7862 // must be Edit Position mode with empty-square selected
7863 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7864 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7867 if(dragging == 2) { // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7870 if(x == killX && y == killY) { // second click on this square, which was selected as first-leg target
7871 killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1; // this informs us no second leg is coming, so treat as to-click without intermediate
7873 if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7874 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7875 if(appData.sweepSelect) {
7876 promoSweep = defaultPromoChoice;
7877 if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7878 selectFlag = 0; lastX = xPix; lastY = yPix;
7879 ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7880 saveFlash = appData.flashCount; appData.flashCount = 0;
7881 Sweep(0); // Pawn that is going to promote: preview promotion piece
7883 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7884 MarkTargetSquares(1);
7886 return; // promo popup appears on up-click
7888 /* Finish clickclick move */
7889 if (appData.animate || appData.highlightLastMove) {
7890 SetHighlights(fromX, fromY, toX, toY);
7894 MarkTargetSquares(1);
7895 } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7896 sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7897 *promoRestrict = 0; appData.flashCount = saveFlash;
7898 if (appData.animate || appData.highlightLastMove) {
7899 SetHighlights(fromX, fromY, toX, toY);
7903 MarkTargetSquares(1);
7906 // [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
7907 /* Finish drag move */
7908 if (appData.highlightLastMove) {
7909 SetHighlights(fromX, fromY, toX, toY);
7914 if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7915 defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7916 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7917 if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7918 dragging *= 2; // flag button-less dragging if we are dragging
7919 MarkTargetSquares(1);
7920 if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7922 kill2X = killX; kill2Y = killY;
7923 killX = x; killY = y; // remember this square as intermediate
7924 ReportClick("put", x, y); // and inform engine
7925 ReportClick("lift", x, y);
7926 MarkTargetSquares(0);
7930 DragPieceEnd(xPix, yPix); dragging = 0;
7931 /* Don't animate move and drag both */
7932 appData.animate = FALSE;
7933 MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7936 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7937 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7938 ChessSquare piece = boards[currentMove][fromY][fromX];
7939 if(gameMode == EditPosition && piece != EmptySquare &&
7940 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7943 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7944 n = PieceToNumber(piece - (int)BlackPawn);
7945 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7946 boards[currentMove][handSize-1 - n][0] = piece;
7947 boards[currentMove][handSize-1 - n][1]++;
7949 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7950 n = PieceToNumber(piece);
7951 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7952 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7953 boards[currentMove][n][BOARD_WIDTH-2]++;
7955 boards[currentMove][fromY][fromX] = EmptySquare;
7959 MarkTargetSquares(1);
7960 DrawPosition(TRUE, boards[currentMove]);
7964 // off-board moves should not be highlighted
7965 if(x < 0 || y < 0) {
7967 DrawPosition(FALSE, NULL);
7968 } else ReportClick("put", x, y);
7970 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7973 if(legal[toY][toX] == 2) { // highlight-induced promotion
7974 if(piece == defaultPromoChoice) promoChoice = NULLCHAR; // deferral
7975 else promoChoice = ToLower(PieceToChar(defaultPromoChoice));
7976 } else if(legal[toY][toX] == 4) { // blue target square: engine must supply promotion choice
7977 if(!*promoRestrict) { // but has not done that yet
7978 deferChoice = TRUE; // set up retry for when it does
7979 return; // and wait for that
7981 promoChoice = ToLower(*promoRestrict); // force engine's choice
7982 deferChoice = FALSE;
7985 if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7986 SetHighlights(fromX, fromY, toX, toY);
7987 MarkTargetSquares(1);
7988 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7989 // [HGM] super: promotion to captured piece selected from holdings
7990 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7991 promotionChoice = TRUE;
7992 // kludge follows to temporarily execute move on display, without promoting yet
7993 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7994 boards[currentMove][toY][toX] = p;
7995 DrawPosition(FALSE, boards[currentMove]);
7996 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7997 boards[currentMove][toY][toX] = q;
7998 DisplayMessage("Click in holdings to choose piece", "");
8001 DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
8002 PromotionPopUp(promoChoice);
8004 int oldMove = currentMove;
8005 flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
8006 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
8007 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
8008 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
8009 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
8010 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
8011 DrawPosition(TRUE, boards[currentMove]);
8012 else DrawPosition(FALSE, NULL);
8016 appData.animate = saveAnimate;
8017 if (appData.animate || appData.animateDragging) {
8018 /* Undo animation damage if needed */
8019 // DrawPosition(FALSE, NULL);
8024 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
8025 { // front-end-free part taken out of PieceMenuPopup
8026 int whichMenu; int xSqr, ySqr;
8028 if(seekGraphUp) { // [HGM] seekgraph
8029 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
8030 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
8034 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
8035 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
8036 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
8037 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
8038 if(action == Press) {
8039 originalFlip = flipView;
8040 flipView = !flipView; // temporarily flip board to see game from partners perspective
8041 DrawPosition(TRUE, partnerBoard);
8042 DisplayMessage(partnerStatus, "");
8044 } else if(action == Release) {
8045 flipView = originalFlip;
8046 DrawPosition(TRUE, boards[currentMove]);
8052 xSqr = EventToSquare(x, BOARD_WIDTH);
8053 ySqr = EventToSquare(y, BOARD_HEIGHT);
8054 if (action == Release) {
8055 if(pieceSweep != EmptySquare) {
8056 EditPositionMenuEvent(pieceSweep, toX, toY);
8057 pieceSweep = EmptySquare;
8058 } else UnLoadPV(); // [HGM] pv
8060 if (action != Press) return -2; // return code to be ignored
8063 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
8065 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
8066 if (xSqr < 0 || ySqr < 0) return -1;
8067 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
8068 if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8069 if(xSqr == createX && ySqr == createY && xSqr != BOARD_LEFT-2 && xSqr != BOARD_RGHT+1) {
8070 ChessSquare p = boards[currentMove][ySqr][xSqr];
8071 do { if(++p == EmptySquare) p = WhitePawn; } while(PieceToChar(p) == '.');
8072 boards[currentMove][ySqr][xSqr] = p; DrawPosition(FALSE, boards[currentMove]);
8075 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
8076 createX = toX = xSqr; createY = toY = ySqr; lastX = x, lastY = y;
8080 if(!appData.icsEngineAnalyze) return -1;
8081 case IcsPlayingWhite:
8082 case IcsPlayingBlack:
8083 if(!appData.zippyPlay) goto noZip;
8086 case MachinePlaysWhite:
8087 case MachinePlaysBlack:
8088 case TwoMachinesPlay: // [HGM] pv: use for showing PV
8089 if (!appData.dropMenu) {
8091 return 2; // flag front-end to grab mouse events
8093 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8094 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8097 if (xSqr < 0 || ySqr < 0) return -1;
8098 if (!appData.dropMenu || appData.testLegality &&
8099 gameInfo.variant != VariantBughouse &&
8100 gameInfo.variant != VariantCrazyhouse) return -1;
8101 whichMenu = 1; // drop menu
8107 if (((*fromX = xSqr) < 0) ||
8108 ((*fromY = ySqr) < 0)) {
8109 *fromX = *fromY = -1;
8113 *fromX = BOARD_WIDTH - 1 - *fromX;
8115 *fromY = BOARD_HEIGHT - 1 - *fromY;
8121 Wheel (int dir, int x, int y)
8123 if(gameMode == EditPosition) {
8124 int xSqr = EventToSquare(x, BOARD_WIDTH);
8125 int ySqr = EventToSquare(y, BOARD_HEIGHT);
8126 if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8127 if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8129 boards[currentMove][ySqr][xSqr] += dir;
8130 if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8131 if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8132 } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8133 DrawPosition(FALSE, boards[currentMove]);
8134 } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8138 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8140 // char * hint = lastHint;
8141 FrontEndProgramStats stats;
8143 stats.which = cps == &first ? 0 : 1;
8144 stats.depth = cpstats->depth;
8145 stats.nodes = cpstats->nodes;
8146 stats.score = cpstats->score;
8147 stats.time = cpstats->time;
8148 stats.pv = cpstats->movelist;
8149 stats.hint = lastHint;
8150 stats.an_move_index = 0;
8151 stats.an_move_count = 0;
8153 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8154 stats.hint = cpstats->move_name;
8155 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8156 stats.an_move_count = cpstats->nr_moves;
8159 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
8161 if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8162 && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8164 SetProgramStats( &stats );
8168 ClearEngineOutputPane (int which)
8170 static FrontEndProgramStats dummyStats;
8171 dummyStats.which = which;
8172 dummyStats.pv = "#";
8173 SetProgramStats( &dummyStats );
8176 #define MAXPLAYERS 500
8179 TourneyStandings (int display)
8181 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8182 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8183 char result, *p, *names[MAXPLAYERS];
8185 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8186 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8187 names[0] = p = strdup(appData.participants);
8188 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8190 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8192 while(result = appData.results[nr]) {
8193 color = Pairing(nr, nPlayers, &w, &b, &dummy);
8194 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8195 wScore = bScore = 0;
8197 case '+': wScore = 2; break;
8198 case '-': bScore = 2; break;
8199 case '=': wScore = bScore = 1; break;
8201 case '*': return strdup("busy"); // tourney not finished
8209 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8210 for(w=0; w<nPlayers; w++) {
8212 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8213 ranking[w] = b; points[w] = bScore; score[b] = -2;
8215 p = malloc(nPlayers*34+1);
8216 for(w=0; w<nPlayers && w<display; w++)
8217 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8223 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8224 { // count all piece types
8226 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8227 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8228 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8231 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8232 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8233 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8234 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8235 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
8236 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8241 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8243 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8244 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8246 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8247 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8248 if(myPawns == 2 && nMine == 3) // KPP
8249 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8250 if(myPawns == 1 && nMine == 2) // KP
8251 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8252 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8253 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8254 if(myPawns) return FALSE;
8255 if(pCnt[WhiteRook+side])
8256 return pCnt[BlackRook-side] ||
8257 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8258 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8259 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8260 if(pCnt[WhiteCannon+side]) {
8261 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8262 return majorDefense || pCnt[BlackAlfil-side] >= 2;
8264 if(pCnt[WhiteKnight+side])
8265 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8270 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8272 VariantClass v = gameInfo.variant;
8274 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8275 if(v == VariantShatranj) return TRUE; // always winnable through baring
8276 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8277 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8279 if(v == VariantXiangqi) {
8280 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8282 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8283 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8284 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8285 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8286 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8287 if(stale) // we have at least one last-rank P plus perhaps C
8288 return majors // KPKX
8289 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8291 return pCnt[WhiteFerz+side] // KCAK
8292 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8293 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8294 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8296 } else if(v == VariantKnightmate) {
8297 if(nMine == 1) return FALSE;
8298 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8299 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8300 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8302 if(nMine == 1) return FALSE; // bare King
8303 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
8304 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8305 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8306 // by now we have King + 1 piece (or multiple Bishops on the same color)
8307 if(pCnt[WhiteKnight+side])
8308 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8309 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8310 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8312 return (pCnt[BlackKnight-side]); // KBKN, KFKN
8313 if(pCnt[WhiteAlfil+side])
8314 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8315 if(pCnt[WhiteWazir+side])
8316 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8323 CompareWithRights (Board b1, Board b2)
8326 if(!CompareBoards(b1, b2)) return FALSE;
8327 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8328 /* compare castling rights */
8329 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8330 rights++; /* King lost rights, while rook still had them */
8331 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8332 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8333 rights++; /* but at least one rook lost them */
8335 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8337 if( b1[CASTLING][5] != NoRights ) {
8338 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8345 Adjudicate (ChessProgramState *cps)
8346 { // [HGM] some adjudications useful with buggy engines
8347 // [HGM] adjudicate: made into separate routine, which now can be called after every move
8348 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8349 // Actually ending the game is now based on the additional internal condition canAdjudicate.
8350 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8351 int k, drop, count = 0; static int bare = 1;
8352 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8353 Boolean canAdjudicate = !appData.icsActive;
8355 // most tests only when we understand the game, i.e. legality-checking on
8356 if( appData.testLegality )
8357 { /* [HGM] Some more adjudications for obstinate engines */
8358 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8359 static int moveCount = 6;
8361 char *reason = NULL;
8363 /* Count what is on board. */
8364 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8366 /* Some material-based adjudications that have to be made before stalemate test */
8367 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8368 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8369 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8370 if(canAdjudicate && appData.checkMates) {
8372 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8373 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8374 "Xboard adjudication: King destroyed", GE_XBOARD );
8379 /* Bare King in Shatranj (loses) or Losers (wins) */
8380 if( nrW == 1 || nrB == 1) {
8381 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8382 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
8383 if(canAdjudicate && appData.checkMates) {
8385 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8386 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8387 "Xboard adjudication: Bare king", GE_XBOARD );
8391 if( gameInfo.variant == VariantShatranj && --bare < 0)
8393 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8394 if(canAdjudicate && appData.checkMates) {
8395 /* but only adjudicate if adjudication enabled */
8397 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8398 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8399 "Xboard adjudication: Bare king", GE_XBOARD );
8406 // don't wait for engine to announce game end if we can judge ourselves
8407 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8409 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8410 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
8411 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8412 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8415 reason = "Xboard adjudication: 3rd check";
8416 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8427 reason = "Xboard adjudication: Stalemate";
8428 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8429 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
8430 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8431 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
8432 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8433 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8434 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8435 EP_CHECKMATE : EP_WINS);
8436 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8437 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8441 reason = "Xboard adjudication: Checkmate";
8442 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8443 if(gameInfo.variant == VariantShogi) {
8444 if(forwardMostMove > backwardMostMove
8445 && moveList[forwardMostMove-1][1] == '@'
8446 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8447 reason = "XBoard adjudication: pawn-drop mate";
8448 boards[forwardMostMove][EP_STATUS] = EP_WINS;
8454 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8456 result = GameIsDrawn; break;
8458 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8460 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8464 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8466 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8467 GameEnds( result, reason, GE_XBOARD );
8471 /* Next absolutely insufficient mating material. */
8472 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8473 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8474 { /* includes KBK, KNK, KK of KBKB with like Bishops */
8476 /* always flag draws, for judging claims */
8477 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8479 if(canAdjudicate && appData.materialDraws) {
8480 /* but only adjudicate them if adjudication enabled */
8481 if(engineOpponent) {
8482 SendToProgram("force\n", engineOpponent); // suppress reply
8483 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8485 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8490 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8491 if(gameInfo.variant == VariantXiangqi ?
8492 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8494 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8495 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
8496 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
8497 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8499 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8500 { /* if the first 3 moves do not show a tactical win, declare draw */
8501 if(engineOpponent) {
8502 SendToProgram("force\n", engineOpponent); // suppress reply
8503 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8505 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8508 } else moveCount = 6;
8511 // Repetition draws and 50-move rule can be applied independently of legality testing
8513 /* Check for rep-draws */
8515 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8516 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8517 for(k = forwardMostMove-2;
8518 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8519 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8520 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8523 if(CompareBoards(boards[k], boards[forwardMostMove])) {
8524 /* compare castling rights */
8525 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8526 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8527 rights++; /* King lost rights, while rook still had them */
8528 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8529 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8530 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8531 rights++; /* but at least one rook lost them */
8533 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8534 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8536 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8537 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8538 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8541 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8542 && appData.drawRepeats > 1) {
8543 /* adjudicate after user-specified nr of repeats */
8544 int result = GameIsDrawn;
8545 char *details = "XBoard adjudication: repetition draw";
8546 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8547 // [HGM] xiangqi: check for forbidden perpetuals
8548 int m, ourPerpetual = 1, hisPerpetual = 1;
8549 for(m=forwardMostMove; m>k; m-=2) {
8550 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8551 ourPerpetual = 0; // the current mover did not always check
8552 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8553 hisPerpetual = 0; // the opponent did not always check
8555 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8556 ourPerpetual, hisPerpetual);
8557 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8558 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8559 details = "Xboard adjudication: perpetual checking";
8561 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8562 break; // (or we would have caught him before). Abort repetition-checking loop.
8564 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8565 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8567 details = "Xboard adjudication: repetition";
8569 } else // it must be XQ
8570 // Now check for perpetual chases
8571 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8572 hisPerpetual = PerpetualChase(k, forwardMostMove);
8573 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8574 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8575 static char resdet[MSG_SIZ];
8576 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8578 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8580 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8581 break; // Abort repetition-checking loop.
8583 // if neither of us is checking or chasing all the time, or both are, it is draw
8585 if(engineOpponent) {
8586 SendToProgram("force\n", engineOpponent); // suppress reply
8587 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8589 GameEnds( result, details, GE_XBOARD );
8592 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8593 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8597 /* Now we test for 50-move draws. Determine ply count */
8598 count = forwardMostMove;
8599 /* look for last irreversble move */
8600 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8602 /* if we hit starting position, add initial plies */
8603 if( count == backwardMostMove )
8604 count -= initialRulePlies;
8605 count = forwardMostMove - count;
8606 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8607 // adjust reversible move counter for checks in Xiangqi
8608 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8609 if(i < backwardMostMove) i = backwardMostMove;
8610 while(i <= forwardMostMove) {
8611 lastCheck = inCheck; // check evasion does not count
8612 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8613 if(inCheck || lastCheck) count--; // check does not count
8618 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8619 /* this is used to judge if draw claims are legal */
8620 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8621 if(engineOpponent) {
8622 SendToProgram("force\n", engineOpponent); // suppress reply
8623 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8625 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8629 /* if draw offer is pending, treat it as a draw claim
8630 * when draw condition present, to allow engines a way to
8631 * claim draws before making their move to avoid a race
8632 * condition occurring after their move
8634 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8636 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8637 p = "Draw claim: 50-move rule";
8638 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8639 p = "Draw claim: 3-fold repetition";
8640 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8641 p = "Draw claim: insufficient mating material";
8642 if( p != NULL && canAdjudicate) {
8643 if(engineOpponent) {
8644 SendToProgram("force\n", engineOpponent); // suppress reply
8645 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8647 GameEnds( GameIsDrawn, p, GE_XBOARD );
8652 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8653 if(engineOpponent) {
8654 SendToProgram("force\n", engineOpponent); // suppress reply
8655 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8657 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8663 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8664 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8665 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8670 int pieces[10], squares[10], cnt=0, r, f, res;
8672 static PPROBE_EGBB probeBB;
8673 if(!appData.testLegality) return 10;
8674 if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8675 if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8676 if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8677 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8678 ChessSquare piece = boards[forwardMostMove][r][f];
8679 int black = (piece >= BlackPawn);
8680 int type = piece - black*BlackPawn;
8681 if(piece == EmptySquare) continue;
8682 if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8683 if(type == WhiteKing) type = WhiteQueen + 1;
8684 type = egbbCode[type];
8685 squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8686 pieces[cnt] = type + black*6;
8687 if(++cnt > 5) return 11;
8689 pieces[cnt] = squares[cnt] = 0;
8691 if(loaded == 2) return 13; // loading failed before
8693 char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8696 loaded = 2; // prepare for failure
8697 if(!path) return 13; // no egbb installed
8698 strncpy(buf, path + 8, MSG_SIZ);
8699 if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8700 snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8701 lib = LoadLibrary(buf);
8702 if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8703 loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8704 probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8705 if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8706 p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8707 loaded = 1; // success!
8709 res = probeBB(forwardMostMove & 1, pieces, squares);
8710 return res > 0 ? 1 : res < 0 ? -1 : 0;
8714 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8715 { // [HGM] book: this routine intercepts moves to simulate book replies
8716 char *bookHit = NULL;
8718 if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8720 snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8721 SendToProgram(buf, cps);
8723 //first determine if the incoming move brings opponent into his book
8724 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8725 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8726 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8727 if(bookHit != NULL && !cps->bookSuspend) {
8728 // make sure opponent is not going to reply after receiving move to book position
8729 SendToProgram("force\n", cps);
8730 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8732 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8733 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8734 // now arrange restart after book miss
8736 // after a book hit we never send 'go', and the code after the call to this routine
8737 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8738 char buf[MSG_SIZ], *move = bookHit;
8740 int fromX, fromY, toX, toY;
8744 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8745 &fromX, &fromY, &toX, &toY, &promoChar)) {
8746 (void) CoordsToAlgebraic(boards[forwardMostMove],
8747 PosFlags(forwardMostMove),
8748 fromY, fromX, toY, toX, promoChar, move);
8750 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8754 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8755 SendToProgram(buf, cps);
8756 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8757 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8758 SendToProgram("go\n", cps);
8759 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8760 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8761 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8762 SendToProgram("go\n", cps);
8763 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8765 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8769 LoadError (char *errmess, ChessProgramState *cps)
8770 { // unloads engine and switches back to -ncp mode if it was first
8771 if(cps->initDone) return FALSE;
8772 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8773 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8776 appData.noChessProgram = TRUE;
8777 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8778 gameMode = BeginningOfGame; ModeHighlight();
8781 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8782 DisplayMessage("", ""); // erase waiting message
8783 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8788 ChessProgramState *savedState;
8790 DeferredBookMove (void)
8792 if(savedState->lastPing != savedState->lastPong)
8793 ScheduleDelayedEvent(DeferredBookMove, 10);
8795 HandleMachineMove(savedMessage, savedState);
8798 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8799 static ChessProgramState *stalledEngine;
8800 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8803 HandleMachineMove (char *message, ChessProgramState *cps)
8805 static char firstLeg[20], legs;
8806 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8807 char realname[MSG_SIZ];
8808 int fromX, fromY, toX, toY;
8810 char promoChar, roar;
8815 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8816 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8817 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8818 DisplayError(_("Invalid pairing from pairing engine"), 0);
8821 pairingReceived = 1;
8823 return; // Skim the pairing messages here.
8826 oldError = cps->userError; cps->userError = 0;
8828 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8830 * Kludge to ignore BEL characters
8832 while (*message == '\007') message++;
8835 * [HGM] engine debug message: ignore lines starting with '#' character
8837 if(cps->debug && *message == '#') return;
8840 * Look for book output
8842 if (cps == &first && bookRequested) {
8843 if (message[0] == '\t' || message[0] == ' ') {
8844 /* Part of the book output is here; append it */
8845 strcat(bookOutput, message);
8846 strcat(bookOutput, " \n");
8848 } else if (bookOutput[0] != NULLCHAR) {
8849 /* All of book output has arrived; display it */
8850 char *p = bookOutput;
8851 while (*p != NULLCHAR) {
8852 if (*p == '\t') *p = ' ';
8855 DisplayInformation(bookOutput);
8856 bookRequested = FALSE;
8857 /* Fall through to parse the current output */
8862 * Look for machine move.
8864 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8865 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8867 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8868 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8869 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8870 stalledEngine = cps;
8871 if(appData.ponderNextMove) { // bring opponent out of ponder
8872 if(gameMode == TwoMachinesPlay) {
8873 if(cps->other->pause)
8874 PauseEngine(cps->other);
8876 SendToProgram("easy\n", cps->other);
8885 /* This method is only useful on engines that support ping */
8886 if(abortEngineThink) {
8887 if (appData.debugMode) {
8888 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8890 SendToProgram("undo\n", cps);
8894 if (cps->lastPing != cps->lastPong) {
8895 /* Extra move from before last new; ignore */
8896 if (appData.debugMode) {
8897 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8904 int machineWhite = FALSE;
8907 case BeginningOfGame:
8908 /* Extra move from before last reset; ignore */
8909 if (appData.debugMode) {
8910 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8917 /* Extra move after we tried to stop. The mode test is
8918 not a reliable way of detecting this problem, but it's
8919 the best we can do on engines that don't support ping.
8921 if (appData.debugMode) {
8922 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8923 cps->which, gameMode);
8925 SendToProgram("undo\n", cps);
8928 case MachinePlaysWhite:
8929 case IcsPlayingWhite:
8930 machineWhite = TRUE;
8933 case MachinePlaysBlack:
8934 case IcsPlayingBlack:
8935 machineWhite = FALSE;
8938 case TwoMachinesPlay:
8939 machineWhite = (cps->twoMachinesColor[0] == 'w');
8942 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8943 if (appData.debugMode) {
8945 "Ignoring move out of turn by %s, gameMode %d"
8946 ", forwardMost %d\n",
8947 cps->which, gameMode, forwardMostMove);
8953 if(cps->alphaRank) AlphaRank(machineMove, 4);
8955 // [HGM] lion: (some very limited) support for Alien protocol
8956 killX = killY = kill2X = kill2Y = -1;
8957 if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8958 if(legs++) return; // middle leg contains only redundant info, ignore (but count it)
8959 safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8962 if(p = strchr(machineMove, ',')) { // we got both legs in one (happens on book move)
8963 char *q = strchr(p+1, ','); // second comma?
8964 safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8965 if(q) legs = 2, p = q; else legs = 1; // with 3-leg move we clipof first two legs!
8966 safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8968 if(firstLeg[0]) { // there was a previous leg;
8969 // only support case where same piece makes two step
8970 char buf[20], *p = machineMove+1, *q = buf+1, f;
8971 safeStrCpy(buf, machineMove, 20);
8972 while(isdigit(*q)) q++; // find start of to-square
8973 safeStrCpy(machineMove, firstLeg, 20);
8974 while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8975 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
8976 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)
8977 safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8978 sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8979 firstLeg[0] = NULLCHAR; legs = 0;
8982 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8983 &fromX, &fromY, &toX, &toY, &promoChar)) {
8984 /* Machine move could not be parsed; ignore it. */
8985 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8986 machineMove, _(cps->which));
8987 DisplayMoveError(buf1);
8988 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8989 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8990 if (gameMode == TwoMachinesPlay) {
8991 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8997 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8998 /* So we have to redo legality test with true e.p. status here, */
8999 /* to make sure an illegal e.p. capture does not slip through, */
9000 /* to cause a forfeit on a justified illegal-move complaint */
9001 /* of the opponent. */
9002 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
9004 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
9005 fromY, fromX, toY, toX, promoChar);
9006 if(moveType == IllegalMove) {
9007 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
9008 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
9009 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9012 } else if(!appData.fischerCastling)
9013 /* [HGM] Kludge to handle engines that send FRC-style castling
9014 when they shouldn't (like TSCP-Gothic) */
9016 case WhiteASideCastleFR:
9017 case BlackASideCastleFR:
9019 currentMoveString[2]++;
9021 case WhiteHSideCastleFR:
9022 case BlackHSideCastleFR:
9024 currentMoveString[2]--;
9026 default: ; // nothing to do, but suppresses warning of pedantic compilers
9029 hintRequested = FALSE;
9030 lastHint[0] = NULLCHAR;
9031 bookRequested = FALSE;
9032 /* Program may be pondering now */
9033 cps->maybeThinking = TRUE;
9034 if (cps->sendTime == 2) cps->sendTime = 1;
9035 if (cps->offeredDraw) cps->offeredDraw--;
9037 /* [AS] Save move info*/
9038 pvInfoList[ forwardMostMove ].score = programStats.score;
9039 pvInfoList[ forwardMostMove ].depth = programStats.depth;
9040 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
9042 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
9044 /* Test suites abort the 'game' after one move */
9045 if(*appData.finger) {
9047 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
9048 if(!f) f = fopen(appData.finger, "w");
9049 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
9050 else { DisplayFatalError("Bad output file", errno, 0); return; }
9052 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9055 if(solvingTime >= 0) {
9056 snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
9057 totalTime += solvingTime; first.matchWins++; solvingTime = -1;
9059 snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
9060 if(solvingTime == -2) second.matchWins++;
9062 OutputKibitz(2, buf1);
9063 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9066 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
9067 if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
9070 while( count < adjudicateLossPlies ) {
9071 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
9074 score = -score; /* Flip score for winning side */
9077 if( score > appData.adjudicateLossThreshold ) {
9084 if( count >= adjudicateLossPlies ) {
9085 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9087 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
9088 "Xboard adjudication",
9095 if(Adjudicate(cps)) {
9096 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9097 return; // [HGM] adjudicate: for all automatic game ends
9101 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9103 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9104 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9106 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9108 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9110 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9111 char buf[3*MSG_SIZ];
9113 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9114 programStats.score / 100.,
9116 programStats.time / 100.,
9117 (unsigned int)programStats.nodes,
9118 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9119 programStats.movelist);
9125 /* [AS] Clear stats for next move */
9126 ClearProgramStats();
9127 thinkOutput[0] = NULLCHAR;
9128 hiddenThinkOutputState = 0;
9131 if (gameMode == TwoMachinesPlay) {
9132 /* [HGM] relaying draw offers moved to after reception of move */
9133 /* and interpreting offer as claim if it brings draw condition */
9134 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9135 SendToProgram("draw\n", cps->other);
9137 if (cps->other->sendTime) {
9138 SendTimeRemaining(cps->other,
9139 cps->other->twoMachinesColor[0] == 'w');
9141 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9142 if (firstMove && !bookHit) {
9144 if (cps->other->useColors) {
9145 SendToProgram(cps->other->twoMachinesColor, cps->other);
9147 SendToProgram("go\n", cps->other);
9149 cps->other->maybeThinking = TRUE;
9152 roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9154 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9156 if (!pausing && appData.ringBellAfterMoves) {
9157 if(!roar) RingBell();
9161 * Reenable menu items that were disabled while
9162 * machine was thinking
9164 if (gameMode != TwoMachinesPlay)
9165 SetUserThinkingEnables();
9167 // [HGM] book: after book hit opponent has received move and is now in force mode
9168 // force the book reply into it, and then fake that it outputted this move by jumping
9169 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9171 static char bookMove[MSG_SIZ]; // a bit generous?
9173 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9174 strcat(bookMove, bookHit);
9177 programStats.nodes = programStats.depth = programStats.time =
9178 programStats.score = programStats.got_only_move = 0;
9179 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9181 if(cps->lastPing != cps->lastPong) {
9182 savedMessage = message; // args for deferred call
9184 ScheduleDelayedEvent(DeferredBookMove, 10);
9193 /* Set special modes for chess engines. Later something general
9194 * could be added here; for now there is just one kludge feature,
9195 * needed because Crafty 15.10 and earlier don't ignore SIGINT
9196 * when "xboard" is given as an interactive command.
9198 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9199 cps->useSigint = FALSE;
9200 cps->useSigterm = FALSE;
9202 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9203 ParseFeatures(message+8, cps);
9204 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9207 if (!strncmp(message, "setup ", 6) &&
9208 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9209 NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9210 ) { // [HGM] allow first engine to define opening position
9211 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9212 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9214 if(sscanf(message, "setup (%s", buf) == 1) {
9215 s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9216 ASSIGN(appData.pieceToCharTable, buf);
9218 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9220 while(message[s] && message[s++] != ' ');
9221 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9222 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9223 // if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
9224 if(hand > h) handSize = hand; else handSize = h;
9225 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9226 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
9227 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9228 if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9229 startedFromSetupPosition = FALSE;
9232 if(startedFromSetupPosition) return;
9233 ParseFEN(boards[0], &dummy, message+s, FALSE);
9234 DrawPosition(TRUE, boards[0]);
9235 CopyBoard(initialPosition, boards[0]);
9236 startedFromSetupPosition = TRUE;
9239 if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9240 ChessSquare piece = WhitePawn;
9241 char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0;
9242 if(*p == '+') promoted++, ID = *++p;
9243 if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9244 piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece);
9245 if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9246 /* always accept definition of */ && piece != WhiteFalcon && piece != BlackFalcon
9247 /* wild-card pieces. */ && piece != WhiteCobra && piece != BlackCobra
9248 /* For variants we don't have */ && gameInfo.variant != VariantBerolina
9249 /* correct rules for, we cannot */ && gameInfo.variant != VariantCylinder
9250 /* enforce legality on our own! */ && gameInfo.variant != VariantUnknown
9251 && gameInfo.variant != VariantGreat
9252 && gameInfo.variant != VariantFairy ) return;
9253 if(piece < EmptySquare) {
9255 ASSIGN(pieceDesc[piece], buf1);
9256 if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9260 if(!appData.testLegality && sscanf(message, "choice %s", promoRestrict) == 1) {
9262 LeftClick(Press, 0, 0); // finish the click that was interrupted
9263 } else if(promoSweep != EmptySquare) {
9264 promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9265 if(strlen(promoRestrict) > 1) Sweep(0);
9269 /* [HGM] Allow engine to set up a position. Don't ask me why one would
9270 * want this, I was asked to put it in, and obliged.
9272 if (!strncmp(message, "setboard ", 9)) {
9273 Board initial_position;
9275 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9277 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9278 DisplayError(_("Bad FEN received from engine"), 0);
9282 CopyBoard(boards[0], initial_position);
9283 initialRulePlies = FENrulePlies;
9284 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9285 else gameMode = MachinePlaysBlack;
9286 DrawPosition(FALSE, boards[currentMove]);
9292 * Look for communication commands
9294 if (!strncmp(message, "telluser ", 9)) {
9295 if(message[9] == '\\' && message[10] == '\\')
9296 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9298 DisplayNote(message + 9);
9301 if (!strncmp(message, "tellusererror ", 14)) {
9303 if(message[14] == '\\' && message[15] == '\\')
9304 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9306 DisplayError(message + 14, 0);
9309 if (!strncmp(message, "tellopponent ", 13)) {
9310 if (appData.icsActive) {
9312 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9316 DisplayNote(message + 13);
9320 if (!strncmp(message, "tellothers ", 11)) {
9321 if (appData.icsActive) {
9323 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9326 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9329 if (!strncmp(message, "tellall ", 8)) {
9330 if (appData.icsActive) {
9332 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9336 DisplayNote(message + 8);
9340 if (strncmp(message, "warning", 7) == 0) {
9341 /* Undocumented feature, use tellusererror in new code */
9342 DisplayError(message, 0);
9345 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9346 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9347 strcat(realname, " query");
9348 AskQuestion(realname, buf2, buf1, cps->pr);
9351 /* Commands from the engine directly to ICS. We don't allow these to be
9352 * sent until we are logged on. Crafty kibitzes have been known to
9353 * interfere with the login process.
9356 if (!strncmp(message, "tellics ", 8)) {
9357 SendToICS(message + 8);
9361 if (!strncmp(message, "tellicsnoalias ", 15)) {
9362 SendToICS(ics_prefix);
9363 SendToICS(message + 15);
9367 /* The following are for backward compatibility only */
9368 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9369 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9370 SendToICS(ics_prefix);
9376 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9377 if(initPing == cps->lastPong) {
9378 if(gameInfo.variant == VariantUnknown) {
9379 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9380 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9381 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9385 if(cps->lastPing == cps->lastPong && abortEngineThink) {
9386 abortEngineThink = FALSE;
9387 DisplayMessage("", "");
9392 if(!strncmp(message, "highlight ", 10)) {
9393 if(appData.testLegality && !*engineVariant && appData.markers) return;
9394 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9397 if(!strncmp(message, "click ", 6)) {
9398 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9399 if(appData.testLegality || !appData.oneClick) return;
9400 sscanf(message+6, "%c%d%c", &f, &y, &c);
9401 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9402 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9403 x = x*squareSize + (x+1)*lineGap + squareSize/2;
9404 y = y*squareSize + (y+1)*lineGap + squareSize/2;
9405 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9406 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9407 LeftClick(Release, lastLeftX, lastLeftY);
9408 controlKey = (c == ',');
9409 LeftClick(Press, x, y);
9410 LeftClick(Release, x, y);
9411 first.highlight = f;
9415 * If the move is illegal, cancel it and redraw the board.
9416 * Also deal with other error cases. Matching is rather loose
9417 * here to accommodate engines written before the spec.
9419 if (strncmp(message + 1, "llegal move", 11) == 0 ||
9420 strncmp(message, "Error", 5) == 0) {
9421 if (StrStr(message, "name") ||
9422 StrStr(message, "rating") || StrStr(message, "?") ||
9423 StrStr(message, "result") || StrStr(message, "board") ||
9424 StrStr(message, "bk") || StrStr(message, "computer") ||
9425 StrStr(message, "variant") || StrStr(message, "hint") ||
9426 StrStr(message, "random") || StrStr(message, "depth") ||
9427 StrStr(message, "accepted")) {
9430 if (StrStr(message, "protover")) {
9431 /* Program is responding to input, so it's apparently done
9432 initializing, and this error message indicates it is
9433 protocol version 1. So we don't need to wait any longer
9434 for it to initialize and send feature commands. */
9435 FeatureDone(cps, 1);
9436 cps->protocolVersion = 1;
9439 cps->maybeThinking = FALSE;
9441 if (StrStr(message, "draw")) {
9442 /* Program doesn't have "draw" command */
9443 cps->sendDrawOffers = 0;
9446 if (cps->sendTime != 1 &&
9447 (StrStr(message, "time") || StrStr(message, "otim"))) {
9448 /* Program apparently doesn't have "time" or "otim" command */
9452 if (StrStr(message, "analyze")) {
9453 cps->analysisSupport = FALSE;
9454 cps->analyzing = FALSE;
9455 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9456 EditGameEvent(); // [HGM] try to preserve loaded game
9457 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9458 DisplayError(buf2, 0);
9461 if (StrStr(message, "(no matching move)st")) {
9462 /* Special kludge for GNU Chess 4 only */
9463 cps->stKludge = TRUE;
9464 SendTimeControl(cps, movesPerSession, timeControl,
9465 timeIncrement, appData.searchDepth,
9469 if (StrStr(message, "(no matching move)sd")) {
9470 /* Special kludge for GNU Chess 4 only */
9471 cps->sdKludge = TRUE;
9472 SendTimeControl(cps, movesPerSession, timeControl,
9473 timeIncrement, appData.searchDepth,
9477 if (!StrStr(message, "llegal")) {
9480 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9481 gameMode == IcsIdle) return;
9482 if (forwardMostMove <= backwardMostMove) return;
9483 if (pausing) PauseEvent();
9484 if(appData.forceIllegal) {
9485 // [HGM] illegal: machine refused move; force position after move into it
9486 SendToProgram("force\n", cps);
9487 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9488 // we have a real problem now, as SendBoard will use the a2a3 kludge
9489 // when black is to move, while there might be nothing on a2 or black
9490 // might already have the move. So send the board as if white has the move.
9491 // But first we must change the stm of the engine, as it refused the last move
9492 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9493 if(WhiteOnMove(forwardMostMove)) {
9494 SendToProgram("a7a6\n", cps); // for the engine black still had the move
9495 SendBoard(cps, forwardMostMove); // kludgeless board
9497 SendToProgram("a2a3\n", cps); // for the engine white still had the move
9498 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9499 SendBoard(cps, forwardMostMove+1); // kludgeless board
9501 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9502 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9503 gameMode == TwoMachinesPlay)
9504 SendToProgram("go\n", cps);
9507 if (gameMode == PlayFromGameFile) {
9508 /* Stop reading this game file */
9509 gameMode = EditGame;
9512 /* [HGM] illegal-move claim should forfeit game when Xboard */
9513 /* only passes fully legal moves */
9514 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9515 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9516 "False illegal-move claim", GE_XBOARD );
9517 return; // do not take back move we tested as valid
9519 currentMove = forwardMostMove-1;
9520 DisplayMove(currentMove-1); /* before DisplayMoveError */
9521 SwitchClocks(forwardMostMove-1); // [HGM] race
9522 DisplayBothClocks();
9523 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9524 parseList[currentMove], _(cps->which));
9525 DisplayMoveError(buf1);
9526 DrawPosition(FALSE, boards[currentMove]);
9528 SetUserThinkingEnables();
9531 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9532 /* Program has a broken "time" command that
9533 outputs a string not ending in newline.
9537 if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9538 if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9539 sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1 ) return;
9543 * If chess program startup fails, exit with an error message.
9544 * Attempts to recover here are futile. [HGM] Well, we try anyway
9546 if ((StrStr(message, "unknown host") != NULL)
9547 || (StrStr(message, "No remote directory") != NULL)
9548 || (StrStr(message, "not found") != NULL)
9549 || (StrStr(message, "No such file") != NULL)
9550 || (StrStr(message, "can't alloc") != NULL)
9551 || (StrStr(message, "Permission denied") != NULL)) {
9553 cps->maybeThinking = FALSE;
9554 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9555 _(cps->which), cps->program, cps->host, message);
9556 RemoveInputSource(cps->isr);
9557 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9558 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9559 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9565 * Look for hint output
9567 if (sscanf(message, "Hint: %s", buf1) == 1) {
9568 if (cps == &first && hintRequested) {
9569 hintRequested = FALSE;
9570 if (ParseOneMove(buf1, forwardMostMove, &moveType,
9571 &fromX, &fromY, &toX, &toY, &promoChar)) {
9572 (void) CoordsToAlgebraic(boards[forwardMostMove],
9573 PosFlags(forwardMostMove),
9574 fromY, fromX, toY, toX, promoChar, buf1);
9575 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9576 DisplayInformation(buf2);
9578 /* Hint move could not be parsed!? */
9579 snprintf(buf2, sizeof(buf2),
9580 _("Illegal hint move \"%s\"\nfrom %s chess program"),
9581 buf1, _(cps->which));
9582 DisplayError(buf2, 0);
9585 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9591 * Ignore other messages if game is not in progress
9593 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9594 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9597 * look for win, lose, draw, or draw offer
9599 if (strncmp(message, "1-0", 3) == 0) {
9600 char *p, *q, *r = "";
9601 p = strchr(message, '{');
9609 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9611 } else if (strncmp(message, "0-1", 3) == 0) {
9612 char *p, *q, *r = "";
9613 p = strchr(message, '{');
9621 /* Kludge for Arasan 4.1 bug */
9622 if (strcmp(r, "Black resigns") == 0) {
9623 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9626 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9628 } else if (strncmp(message, "1/2", 3) == 0) {
9629 char *p, *q, *r = "";
9630 p = strchr(message, '{');
9639 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9642 } else if (strncmp(message, "White resign", 12) == 0) {
9643 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9645 } else if (strncmp(message, "Black resign", 12) == 0) {
9646 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9648 } else if (strncmp(message, "White matches", 13) == 0 ||
9649 strncmp(message, "Black matches", 13) == 0 ) {
9650 /* [HGM] ignore GNUShogi noises */
9652 } else if (strncmp(message, "White", 5) == 0 &&
9653 message[5] != '(' &&
9654 StrStr(message, "Black") == NULL) {
9655 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9657 } else if (strncmp(message, "Black", 5) == 0 &&
9658 message[5] != '(') {
9659 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9661 } else if (strcmp(message, "resign") == 0 ||
9662 strcmp(message, "computer resigns") == 0) {
9664 case MachinePlaysBlack:
9665 case IcsPlayingBlack:
9666 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9668 case MachinePlaysWhite:
9669 case IcsPlayingWhite:
9670 GameEnds(BlackWins, "White resigns", GE_ENGINE);
9672 case TwoMachinesPlay:
9673 if (cps->twoMachinesColor[0] == 'w')
9674 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9676 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9683 } else if (strncmp(message, "opponent mates", 14) == 0) {
9685 case MachinePlaysBlack:
9686 case IcsPlayingBlack:
9687 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9689 case MachinePlaysWhite:
9690 case IcsPlayingWhite:
9691 GameEnds(BlackWins, "Black mates", GE_ENGINE);
9693 case TwoMachinesPlay:
9694 if (cps->twoMachinesColor[0] == 'w')
9695 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9697 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9704 } else if (strncmp(message, "computer mates", 14) == 0) {
9706 case MachinePlaysBlack:
9707 case IcsPlayingBlack:
9708 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9710 case MachinePlaysWhite:
9711 case IcsPlayingWhite:
9712 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9714 case TwoMachinesPlay:
9715 if (cps->twoMachinesColor[0] == 'w')
9716 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9718 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9725 } else if (strncmp(message, "checkmate", 9) == 0) {
9726 if (WhiteOnMove(forwardMostMove)) {
9727 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9729 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9732 } else if (strstr(message, "Draw") != NULL ||
9733 strstr(message, "game is a draw") != NULL) {
9734 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9736 } else if (strstr(message, "offer") != NULL &&
9737 strstr(message, "draw") != NULL) {
9739 if (appData.zippyPlay && first.initDone) {
9740 /* Relay offer to ICS */
9741 SendToICS(ics_prefix);
9742 SendToICS("draw\n");
9745 cps->offeredDraw = 2; /* valid until this engine moves twice */
9746 if (gameMode == TwoMachinesPlay) {
9747 if (cps->other->offeredDraw) {
9748 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9749 /* [HGM] in two-machine mode we delay relaying draw offer */
9750 /* until after we also have move, to see if it is really claim */
9752 } else if (gameMode == MachinePlaysWhite ||
9753 gameMode == MachinePlaysBlack) {
9754 if (userOfferedDraw) {
9755 DisplayInformation(_("Machine accepts your draw offer"));
9756 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9758 DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9765 * Look for thinking output
9767 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9768 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9770 int plylev, mvleft, mvtot, curscore, time;
9771 char mvname[MOVE_LEN];
9775 int prefixHint = FALSE;
9776 mvname[0] = NULLCHAR;
9779 case MachinePlaysBlack:
9780 case IcsPlayingBlack:
9781 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9783 case MachinePlaysWhite:
9784 case IcsPlayingWhite:
9785 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9790 case IcsObserving: /* [DM] icsEngineAnalyze */
9791 if (!appData.icsEngineAnalyze) ignore = TRUE;
9793 case TwoMachinesPlay:
9794 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9804 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9807 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9808 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9809 char score_buf[MSG_SIZ];
9811 if(nodes>>32 == u64Const(0xFFFFFFFF)) // [HGM] negative node count read
9812 nodes += u64Const(0x100000000);
9814 if (plyext != ' ' && plyext != '\t') {
9818 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9819 if( cps->scoreIsAbsolute &&
9820 ( gameMode == MachinePlaysBlack ||
9821 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9822 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9823 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9824 !WhiteOnMove(currentMove)
9827 curscore = -curscore;
9830 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9832 if(*bestMove) { // rememer time best EPD move was first found
9833 int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9834 ChessMove mt; char *p = bestMove;
9835 int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9837 while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9838 if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9839 solvingTime = (solvingTime < 0 ? time : solvingTime);
9843 while(*p && *p != ' ') p++;
9844 while(*p == ' ') p++;
9846 if(!solved) solvingTime = -1;
9848 if(*avoidMove && !solved) {
9849 int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9850 ChessMove mt; char *p = avoidMove, solved = 1;
9851 int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9852 while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9853 if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9854 solved = 0; solvingTime = -2;
9857 while(*p && *p != ' ') p++;
9858 while(*p == ' ') p++;
9860 if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9863 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9866 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9867 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9868 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9869 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9870 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9871 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9875 /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9876 DisplayError(_("failed writing PV"), 0);
9879 tempStats.depth = plylev;
9880 tempStats.nodes = nodes;
9881 tempStats.time = time;
9882 tempStats.score = curscore;
9883 tempStats.got_only_move = 0;
9885 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9888 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9889 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9890 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9891 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9892 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9893 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9894 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9895 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9898 /* Buffer overflow protection */
9899 if (pv[0] != NULLCHAR) {
9900 if (strlen(pv) >= sizeof(tempStats.movelist)
9901 && appData.debugMode) {
9903 "PV is too long; using the first %u bytes.\n",
9904 (unsigned) sizeof(tempStats.movelist) - 1);
9907 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9909 sprintf(tempStats.movelist, " no PV\n");
9912 if (tempStats.seen_stat) {
9913 tempStats.ok_to_send = 1;
9916 if (strchr(tempStats.movelist, '(') != NULL) {
9917 tempStats.line_is_book = 1;
9918 tempStats.nr_moves = 0;
9919 tempStats.moves_left = 0;
9921 tempStats.line_is_book = 0;
9924 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9925 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9927 SendProgramStatsToFrontend( cps, &tempStats );
9930 [AS] Protect the thinkOutput buffer from overflow... this
9931 is only useful if buf1 hasn't overflowed first!
9933 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9934 if(curscore >= MATE_SCORE)
9935 snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9936 else if(curscore <= -MATE_SCORE)
9937 snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9939 snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9940 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9942 (gameMode == TwoMachinesPlay ?
9943 ToUpper(cps->twoMachinesColor[0]) : ' '),
9945 prefixHint ? lastHint : "",
9946 prefixHint ? " " : "" );
9948 if( buf1[0] != NULLCHAR ) {
9949 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9951 if( strlen(pv) > max_len ) {
9952 if( appData.debugMode) {
9953 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9955 pv[max_len+1] = '\0';
9958 strcat( thinkOutput, pv);
9961 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9962 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9963 DisplayMove(currentMove - 1);
9967 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9968 /* crafty (9.25+) says "(only move) <move>"
9969 * if there is only 1 legal move
9971 sscanf(p, "(only move) %s", buf1);
9972 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9973 sprintf(programStats.movelist, "%s (only move)", buf1);
9974 programStats.depth = 1;
9975 programStats.nr_moves = 1;
9976 programStats.moves_left = 1;
9977 programStats.nodes = 1;
9978 programStats.time = 1;
9979 programStats.got_only_move = 1;
9981 /* Not really, but we also use this member to
9982 mean "line isn't going to change" (Crafty
9983 isn't searching, so stats won't change) */
9984 programStats.line_is_book = 1;
9986 SendProgramStatsToFrontend( cps, &programStats );
9988 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9989 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9990 DisplayMove(currentMove - 1);
9993 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9994 &time, &nodes, &plylev, &mvleft,
9995 &mvtot, mvname) >= 5) {
9996 /* The stat01: line is from Crafty (9.29+) in response
9997 to the "." command */
9998 programStats.seen_stat = 1;
9999 cps->maybeThinking = TRUE;
10001 if (programStats.got_only_move || !appData.periodicUpdates)
10004 programStats.depth = plylev;
10005 programStats.time = time;
10006 programStats.nodes = nodes;
10007 programStats.moves_left = mvleft;
10008 programStats.nr_moves = mvtot;
10009 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
10010 programStats.ok_to_send = 1;
10011 programStats.movelist[0] = '\0';
10013 SendProgramStatsToFrontend( cps, &programStats );
10017 } else if (strncmp(message,"++",2) == 0) {
10018 /* Crafty 9.29+ outputs this */
10019 programStats.got_fail = 2;
10022 } else if (strncmp(message,"--",2) == 0) {
10023 /* Crafty 9.29+ outputs this */
10024 programStats.got_fail = 1;
10027 } else if (thinkOutput[0] != NULLCHAR &&
10028 strncmp(message, " ", 4) == 0) {
10029 unsigned message_len;
10032 while (*p && *p == ' ') p++;
10034 message_len = strlen( p );
10036 /* [AS] Avoid buffer overflow */
10037 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
10038 strcat(thinkOutput, " ");
10039 strcat(thinkOutput, p);
10042 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
10043 strcat(programStats.movelist, " ");
10044 strcat(programStats.movelist, p);
10047 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
10048 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
10049 DisplayMove(currentMove - 1);
10055 buf1[0] = NULLCHAR;
10057 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
10058 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
10060 ChessProgramStats cpstats;
10062 if (plyext != ' ' && plyext != '\t') {
10066 /* [AS] Negate score if machine is playing black and reporting absolute scores */
10067 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
10068 curscore = -curscore;
10071 cpstats.depth = plylev;
10072 cpstats.nodes = nodes;
10073 cpstats.time = time;
10074 cpstats.score = curscore;
10075 cpstats.got_only_move = 0;
10076 cpstats.movelist[0] = '\0';
10078 if (buf1[0] != NULLCHAR) {
10079 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
10082 cpstats.ok_to_send = 0;
10083 cpstats.line_is_book = 0;
10084 cpstats.nr_moves = 0;
10085 cpstats.moves_left = 0;
10087 SendProgramStatsToFrontend( cps, &cpstats );
10094 /* Parse a game score from the character string "game", and
10095 record it as the history of the current game. The game
10096 score is NOT assumed to start from the standard position.
10097 The display is not updated in any way.
10100 ParseGameHistory (char *game)
10102 ChessMove moveType;
10103 int fromX, fromY, toX, toY, boardIndex, mask;
10108 if (appData.debugMode)
10109 fprintf(debugFP, "Parsing game history: %s\n", game);
10111 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10112 gameInfo.site = StrSave(appData.icsHost);
10113 gameInfo.date = PGNDate();
10114 gameInfo.round = StrSave("-");
10116 /* Parse out names of players */
10117 while (*game == ' ') game++;
10119 while (*game != ' ') *p++ = *game++;
10121 gameInfo.white = StrSave(buf);
10122 while (*game == ' ') game++;
10124 while (*game != ' ' && *game != '\n') *p++ = *game++;
10126 gameInfo.black = StrSave(buf);
10129 boardIndex = blackPlaysFirst ? 1 : 0;
10132 yyboardindex = boardIndex;
10133 moveType = (ChessMove) Myylex();
10134 switch (moveType) {
10135 case IllegalMove: /* maybe suicide chess, etc. */
10136 if (appData.debugMode) {
10137 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10138 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10139 setbuf(debugFP, NULL);
10141 case WhitePromotion:
10142 case BlackPromotion:
10143 case WhiteNonPromotion:
10144 case BlackNonPromotion:
10147 case WhiteCapturesEnPassant:
10148 case BlackCapturesEnPassant:
10149 case WhiteKingSideCastle:
10150 case WhiteQueenSideCastle:
10151 case BlackKingSideCastle:
10152 case BlackQueenSideCastle:
10153 case WhiteKingSideCastleWild:
10154 case WhiteQueenSideCastleWild:
10155 case BlackKingSideCastleWild:
10156 case BlackQueenSideCastleWild:
10158 case WhiteHSideCastleFR:
10159 case WhiteASideCastleFR:
10160 case BlackHSideCastleFR:
10161 case BlackASideCastleFR:
10163 fromX = currentMoveString[0] - AAA;
10164 fromY = currentMoveString[1] - ONE;
10165 toX = currentMoveString[2] - AAA;
10166 toY = currentMoveString[3] - ONE;
10167 promoChar = currentMoveString[4];
10171 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10172 fromX = moveType == WhiteDrop ?
10173 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10174 (int) CharToPiece(ToLower(currentMoveString[0]));
10176 toX = currentMoveString[2] - AAA;
10177 toY = currentMoveString[3] - ONE;
10178 promoChar = NULLCHAR;
10180 case AmbiguousMove:
10182 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10183 if (appData.debugMode) {
10184 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10185 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10186 setbuf(debugFP, NULL);
10188 DisplayError(buf, 0);
10190 case ImpossibleMove:
10192 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10193 if (appData.debugMode) {
10194 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10195 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10196 setbuf(debugFP, NULL);
10198 DisplayError(buf, 0);
10201 if (boardIndex < backwardMostMove) {
10202 /* Oops, gap. How did that happen? */
10203 DisplayError(_("Gap in move list"), 0);
10206 backwardMostMove = blackPlaysFirst ? 1 : 0;
10207 if (boardIndex > forwardMostMove) {
10208 forwardMostMove = boardIndex;
10212 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10213 strcat(parseList[boardIndex-1], " ");
10214 strcat(parseList[boardIndex-1], yy_text);
10226 case GameUnfinished:
10227 if (gameMode == IcsExamining) {
10228 if (boardIndex < backwardMostMove) {
10229 /* Oops, gap. How did that happen? */
10232 backwardMostMove = blackPlaysFirst ? 1 : 0;
10235 gameInfo.result = moveType;
10236 p = strchr(yy_text, '{');
10237 if (p == NULL) p = strchr(yy_text, '(');
10240 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10242 q = strchr(p, *p == '{' ? '}' : ')');
10243 if (q != NULL) *q = NULLCHAR;
10246 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10247 gameInfo.resultDetails = StrSave(p);
10250 if (boardIndex >= forwardMostMove &&
10251 !(gameMode == IcsObserving && ics_gamenum == -1)) {
10252 backwardMostMove = blackPlaysFirst ? 1 : 0;
10255 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10256 fromY, fromX, toY, toX, promoChar,
10257 parseList[boardIndex]);
10258 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10259 /* currentMoveString is set as a side-effect of yylex */
10260 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10261 strcat(moveList[boardIndex], "\n");
10263 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10264 mask = (WhiteOnMove(boardIndex) ? 0xFF : 0xFF00);
10265 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10271 if(boards[boardIndex][CHECK_COUNT]) boards[boardIndex][CHECK_COUNT] -= mask & 0x101;
10272 if(!boards[boardIndex][CHECK_COUNT] || boards[boardIndex][CHECK_COUNT] & mask) {
10273 if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[boardIndex - 1], "+");
10278 strcat(parseList[boardIndex - 1], "#");
10285 /* Apply a move to the given board */
10287 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10289 ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, epFile, lastFile, lastRank, berolina = 0;
10290 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10292 /* [HGM] compute & store e.p. status and castling rights for new position */
10293 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10295 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10296 oldEP = (signed char)board[EP_STATUS]; epRank = board[EP_RANK]; epFile = board[EP_FILE]; lastFile = board[LAST_TO] & 255,lastRank = board[LAST_TO] >> 8;
10297 board[EP_STATUS] = EP_NONE;
10298 board[EP_FILE] = board[EP_RANK] = 100, board[LAST_TO] = 0x4040;
10300 if (fromY == DROP_RANK) {
10301 /* must be first */
10302 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10303 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10306 piece = board[toY][toX] = (ChessSquare) fromX;
10308 // ChessSquare victim;
10311 if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10312 // victim = board[killY][killX],
10313 killed = board[killY][killX],
10314 board[killY][killX] = EmptySquare,
10315 board[EP_STATUS] = EP_CAPTURE;
10316 if( kill2X >= 0 && kill2Y >= 0)
10317 killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10320 if( board[toY][toX] != EmptySquare ) {
10321 board[EP_STATUS] = EP_CAPTURE;
10322 if( (fromX != toX || fromY != toY) && // not igui!
10323 (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10324 captured == BlackLion && board[fromY][fromX] != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules
10325 board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10329 pawn = board[fromY][fromX];
10330 if(pieceDesc[pawn] && strchr(pieceDesc[pawn], 'e')) { // piece with user-defined e.p. capture
10331 if(captured == EmptySquare && toX == epFile && (toY == (epRank & 127) || toY + (pawn < BlackPawn ? -1 : 1) == epRank - 128)) {
10332 captured = board[lastRank][lastFile]; // remove victim
10333 board[lastRank][lastFile] = EmptySquare;
10334 pawn = EmptySquare; // kludge to suppress old e.p. code
10337 if( pawn == WhiteLance || pawn == BlackLance ) {
10338 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10339 if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10340 else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10343 if( pawn == WhitePawn ) {
10344 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10345 board[EP_STATUS] = EP_PAWN_MOVE;
10346 if( toY-fromY>=2) {
10347 board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10348 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
10349 gameInfo.variant != VariantBerolina || toX < fromX)
10350 board[EP_STATUS] = toX | berolina;
10351 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10352 gameInfo.variant != VariantBerolina || toX > fromX)
10353 board[EP_STATUS] = toX;
10354 board[LAST_TO] = toX + 256*toY;
10357 if( pawn == BlackPawn ) {
10358 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10359 board[EP_STATUS] = EP_PAWN_MOVE;
10360 if( toY-fromY<= -2) {
10361 board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10362 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
10363 gameInfo.variant != VariantBerolina || toX < fromX)
10364 board[EP_STATUS] = toX | berolina;
10365 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10366 gameInfo.variant != VariantBerolina || toX > fromX)
10367 board[EP_STATUS] = toX;
10368 board[LAST_TO] = toX + 256*toY;
10372 if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10373 if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10374 if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10375 if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10377 for(i=0; i<nrCastlingRights; i++) {
10378 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10379 board[CASTLING][i] == toX && castlingRank[i] == toY
10380 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10383 if(gameInfo.variant == VariantSChess) { // update virginity
10384 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10385 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10386 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
10387 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
10390 if (fromX == toX && fromY == toY && killX < 0) return;
10392 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10393 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10394 if(gameInfo.variant == VariantKnightmate)
10395 king += (int) WhiteUnicorn - (int) WhiteKing;
10397 if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10398 && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) { // and tramples own
10399 board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10400 board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10401 board[EP_STATUS] = EP_NONE; // capture was fake!
10403 if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10404 board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10405 board[toY][toX] = piece;
10406 board[EP_STATUS] = EP_NONE; // capture was fake!
10408 /* Code added by Tord: */
10409 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10410 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10411 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10412 board[EP_STATUS] = EP_NONE; // capture was fake!
10413 board[fromY][fromX] = EmptySquare;
10414 board[toY][toX] = EmptySquare;
10415 if((toX > fromX) != (piece == WhiteRook)) {
10416 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10418 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10420 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10421 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10422 board[EP_STATUS] = EP_NONE;
10423 board[fromY][fromX] = EmptySquare;
10424 board[toY][toX] = EmptySquare;
10425 if((toX > fromX) != (piece == BlackRook)) {
10426 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10428 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10430 /* End of code added by Tord */
10432 } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10433 board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10434 board[toY][toX] = piece;
10435 } else if (board[fromY][fromX] == king
10436 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10437 && toY == fromY && toX > fromX+1) {
10438 for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10439 ; // castle with nearest piece
10440 board[fromY][toX-1] = board[fromY][rookX];
10441 board[fromY][rookX] = EmptySquare;
10442 board[fromY][fromX] = EmptySquare;
10443 board[toY][toX] = king;
10444 } else if (board[fromY][fromX] == king
10445 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10446 && toY == fromY && toX < fromX-1) {
10447 for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10448 ; // castle with nearest piece
10449 board[fromY][toX+1] = board[fromY][rookX];
10450 board[fromY][rookX] = EmptySquare;
10451 board[fromY][fromX] = EmptySquare;
10452 board[toY][toX] = king;
10453 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10454 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10455 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10457 /* white pawn promotion */
10458 board[toY][toX] = CharToPiece(ToUpper(promoChar));
10459 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10460 board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10461 board[fromY][fromX] = EmptySquare;
10462 } else if ((fromY >= BOARD_HEIGHT>>1)
10463 && (epFile == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10465 && gameInfo.variant != VariantXiangqi
10466 && gameInfo.variant != VariantBerolina
10467 && (pawn == WhitePawn)
10468 && (board[toY][toX] == EmptySquare)) {
10469 if(lastFile == 100) lastFile = (board[fromY][toX] == BlackPawn ? toX : fromX), lastRank = fromY; // assume FIDE e.p. if victim present
10470 board[fromY][fromX] = EmptySquare;
10471 board[toY][toX] = piece;
10472 captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10473 } else if ((fromY == BOARD_HEIGHT-4)
10475 && gameInfo.variant == VariantBerolina
10476 && (board[fromY][fromX] == WhitePawn)
10477 && (board[toY][toX] == EmptySquare)) {
10478 board[fromY][fromX] = EmptySquare;
10479 board[toY][toX] = WhitePawn;
10480 if(oldEP & EP_BEROLIN_A) {
10481 captured = board[fromY][fromX-1];
10482 board[fromY][fromX-1] = EmptySquare;
10483 }else{ captured = board[fromY][fromX+1];
10484 board[fromY][fromX+1] = EmptySquare;
10486 } else if (board[fromY][fromX] == king
10487 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10488 && toY == fromY && toX > fromX+1) {
10489 for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10491 board[fromY][toX-1] = board[fromY][rookX];
10492 board[fromY][rookX] = EmptySquare;
10493 board[fromY][fromX] = EmptySquare;
10494 board[toY][toX] = king;
10495 } else if (board[fromY][fromX] == king
10496 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10497 && toY == fromY && toX < fromX-1) {
10498 for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10500 board[fromY][toX+1] = board[fromY][rookX];
10501 board[fromY][rookX] = EmptySquare;
10502 board[fromY][fromX] = EmptySquare;
10503 board[toY][toX] = king;
10504 } else if (fromY == 7 && fromX == 3
10505 && board[fromY][fromX] == BlackKing
10506 && toY == 7 && toX == 5) {
10507 board[fromY][fromX] = EmptySquare;
10508 board[toY][toX] = BlackKing;
10509 board[fromY][7] = EmptySquare;
10510 board[toY][4] = BlackRook;
10511 } else if (fromY == 7 && fromX == 3
10512 && board[fromY][fromX] == BlackKing
10513 && toY == 7 && toX == 1) {
10514 board[fromY][fromX] = EmptySquare;
10515 board[toY][toX] = BlackKing;
10516 board[fromY][0] = EmptySquare;
10517 board[toY][2] = BlackRook;
10518 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10519 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10520 && toY < promoRank && promoChar
10522 /* black pawn promotion */
10523 board[toY][toX] = CharToPiece(ToLower(promoChar));
10524 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10525 board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10526 board[fromY][fromX] = EmptySquare;
10527 } else if ((fromY < BOARD_HEIGHT>>1)
10528 && (epFile == toX && epRank == toY || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10530 && gameInfo.variant != VariantXiangqi
10531 && gameInfo.variant != VariantBerolina
10532 && (pawn == BlackPawn)
10533 && (board[toY][toX] == EmptySquare)) {
10534 if(lastFile == 100) lastFile = (board[fromY][toX] == WhitePawn ? toX : fromX), lastRank = fromY;
10535 board[fromY][fromX] = EmptySquare;
10536 board[toY][toX] = piece;
10537 captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10538 } else if ((fromY == 3)
10540 && gameInfo.variant == VariantBerolina
10541 && (board[fromY][fromX] == BlackPawn)
10542 && (board[toY][toX] == EmptySquare)) {
10543 board[fromY][fromX] = EmptySquare;
10544 board[toY][toX] = BlackPawn;
10545 if(oldEP & EP_BEROLIN_A) {
10546 captured = board[fromY][fromX-1];
10547 board[fromY][fromX-1] = EmptySquare;
10548 }else{ captured = board[fromY][fromX+1];
10549 board[fromY][fromX+1] = EmptySquare;
10552 ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10553 board[fromY][fromX] = EmptySquare;
10554 board[toY][toX] = piece;
10558 if (gameInfo.holdingsWidth != 0) {
10560 /* !!A lot more code needs to be written to support holdings */
10561 /* [HGM] OK, so I have written it. Holdings are stored in the */
10562 /* penultimate board files, so they are automaticlly stored */
10563 /* in the game history. */
10564 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10565 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10566 /* Delete from holdings, by decreasing count */
10567 /* and erasing image if necessary */
10568 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10569 if(p < (int) BlackPawn) { /* white drop */
10570 p -= (int)WhitePawn;
10571 p = PieceToNumber((ChessSquare)p);
10572 if(p >= gameInfo.holdingsSize) p = 0;
10573 if(--board[p][BOARD_WIDTH-2] <= 0)
10574 board[p][BOARD_WIDTH-1] = EmptySquare;
10575 if((int)board[p][BOARD_WIDTH-2] < 0)
10576 board[p][BOARD_WIDTH-2] = 0;
10577 } else { /* black drop */
10578 p -= (int)BlackPawn;
10579 p = PieceToNumber((ChessSquare)p);
10580 if(p >= gameInfo.holdingsSize) p = 0;
10581 if(--board[handSize-1-p][1] <= 0)
10582 board[handSize-1-p][0] = EmptySquare;
10583 if((int)board[handSize-1-p][1] < 0)
10584 board[handSize-1-p][1] = 0;
10587 if (captured != EmptySquare && gameInfo.holdingsSize > 0
10588 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
10589 /* [HGM] holdings: Add to holdings, if holdings exist */
10590 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10591 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10592 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10594 p = (int) captured;
10595 if (p >= (int) BlackPawn) {
10596 p -= (int)BlackPawn;
10597 if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10598 /* Restore shogi-promoted piece to its original first */
10599 captured = (ChessSquare) (DEMOTED(captured));
10602 p = PieceToNumber((ChessSquare)p);
10603 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10604 board[p][BOARD_WIDTH-2]++;
10605 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10607 p -= (int)WhitePawn;
10608 if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10609 captured = (ChessSquare) (DEMOTED(captured));
10612 p = PieceToNumber((ChessSquare)p);
10613 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10614 board[handSize-1-p][1]++;
10615 board[handSize-1-p][0] = WHITE_TO_BLACK captured;
10618 } else if (gameInfo.variant == VariantAtomic) {
10619 if (captured != EmptySquare) {
10621 for (y = toY-1; y <= toY+1; y++) {
10622 for (x = toX-1; x <= toX+1; x++) {
10623 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10624 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10625 board[y][x] = EmptySquare;
10629 board[toY][toX] = EmptySquare;
10633 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10634 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10636 if(promoChar == '+') {
10637 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10638 board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10639 if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10640 board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10641 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10642 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10643 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10644 && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10645 board[toY][toX] = newPiece;
10647 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10648 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10649 // [HGM] superchess: take promotion piece out of holdings
10650 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10651 if((int)piece < (int)BlackPawn) { // determine stm from piece color
10652 if(!--board[k][BOARD_WIDTH-2])
10653 board[k][BOARD_WIDTH-1] = EmptySquare;
10655 if(!--board[handSize-1-k][1])
10656 board[handSize-1-k][0] = EmptySquare;
10661 /* Updates forwardMostMove */
10663 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10665 int x = toX, y = toY, mask;
10666 char *s = parseList[forwardMostMove];
10667 ChessSquare p = boards[forwardMostMove][toY][toX];
10668 // forwardMostMove++; // [HGM] bare: moved downstream
10670 if(kill2X >= 0) x = kill2X, y = kill2Y; else
10671 if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10672 (void) CoordsToAlgebraic(boards[forwardMostMove],
10673 PosFlags(forwardMostMove),
10674 fromY, fromX, y, x, (killX < 0)*promoChar,
10676 if(kill2X >= 0 && kill2Y >= 0)
10677 sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10678 if(killX >= 0 && killY >= 0)
10679 sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10680 toX + AAA, toY + ONE - '0', promoChar);
10682 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10683 int timeLeft; static int lastLoadFlag=0; int king, piece;
10684 piece = boards[forwardMostMove][fromY][fromX];
10685 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10686 if(gameInfo.variant == VariantKnightmate)
10687 king += (int) WhiteUnicorn - (int) WhiteKing;
10688 if(forwardMostMove == 0) {
10689 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10690 fprintf(serverMoves, "%s;", UserName());
10691 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10692 fprintf(serverMoves, "%s;", second.tidy);
10693 fprintf(serverMoves, "%s;", first.tidy);
10694 if(gameMode == MachinePlaysWhite)
10695 fprintf(serverMoves, "%s;", UserName());
10696 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10697 fprintf(serverMoves, "%s;", second.tidy);
10698 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10699 lastLoadFlag = loadFlag;
10701 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10702 // print castling suffix
10703 if( toY == fromY && piece == king ) {
10705 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10707 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10710 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10711 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
10712 boards[forwardMostMove][toY][toX] == EmptySquare
10713 && fromX != toX && fromY != toY)
10714 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10715 // promotion suffix
10716 if(promoChar != NULLCHAR) {
10717 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10718 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10719 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10720 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10723 char buf[MOVE_LEN*2], *p; int len;
10724 fprintf(serverMoves, "/%d/%d",
10725 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10726 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10727 else timeLeft = blackTimeRemaining/1000;
10728 fprintf(serverMoves, "/%d", timeLeft);
10729 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10730 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10731 if(p = strchr(buf, '=')) *p = NULLCHAR;
10732 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10733 fprintf(serverMoves, "/%s", buf);
10735 fflush(serverMoves);
10738 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10739 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10742 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10743 if (commentList[forwardMostMove+1] != NULL) {
10744 free(commentList[forwardMostMove+1]);
10745 commentList[forwardMostMove+1] = NULL;
10747 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10748 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10749 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10750 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10751 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10752 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10753 adjustedClock = FALSE;
10754 gameInfo.result = GameUnfinished;
10755 if (gameInfo.resultDetails != NULL) {
10756 free(gameInfo.resultDetails);
10757 gameInfo.resultDetails = NULL;
10759 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10760 moveList[forwardMostMove - 1]);
10761 mask = (WhiteOnMove(forwardMostMove) ? 0xFF : 0xFF00);
10762 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10768 if(boards[forwardMostMove][CHECK_COUNT]) boards[forwardMostMove][CHECK_COUNT] -= mask & 0x101;
10769 if(!boards[forwardMostMove][CHECK_COUNT] || boards[forwardMostMove][CHECK_COUNT] & mask) {
10770 if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[forwardMostMove - 1], "+");
10775 strcat(parseList[forwardMostMove - 1], "#");
10780 /* Updates currentMove if not pausing */
10782 ShowMove (int fromX, int fromY, int toX, int toY)
10784 int instant = (gameMode == PlayFromGameFile) ?
10785 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10786 if(appData.noGUI) return;
10787 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10789 if (forwardMostMove == currentMove + 1) {
10790 AnimateMove(boards[forwardMostMove - 1],
10791 fromX, fromY, toX, toY);
10794 currentMove = forwardMostMove;
10797 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10799 if (instant) return;
10801 DisplayMove(currentMove - 1);
10802 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10803 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10804 SetHighlights(fromX, fromY, toX, toY);
10807 DrawPosition(FALSE, boards[currentMove]);
10808 DisplayBothClocks();
10809 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10813 SendEgtPath (ChessProgramState *cps)
10814 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10815 char buf[MSG_SIZ], name[MSG_SIZ], *p;
10817 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10820 char c, *q = name+1, *r, *s;
10822 name[0] = ','; // extract next format name from feature and copy with prefixed ','
10823 while(*p && *p != ',') *q++ = *p++;
10824 *q++ = ':'; *q = 0;
10825 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10826 strcmp(name, ",nalimov:") == 0 ) {
10827 // take nalimov path from the menu-changeable option first, if it is defined
10828 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10829 SendToProgram(buf,cps); // send egtbpath command for nalimov
10831 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10832 (s = StrStr(appData.egtFormats, name)) != NULL) {
10833 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10834 s = r = StrStr(s, ":") + 1; // beginning of path info
10835 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10836 c = *r; *r = 0; // temporarily null-terminate path info
10837 *--q = 0; // strip of trailig ':' from name
10838 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10840 SendToProgram(buf,cps); // send egtbpath command for this format
10842 if(*p == ',') p++; // read away comma to position for next format name
10847 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10849 int width = 8, height = 8, holdings = 0; // most common sizes
10850 if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10851 // correct the deviations default for each variant
10852 if( v == VariantXiangqi ) width = 9, height = 10;
10853 if( v == VariantShogi ) width = 9, height = 9, holdings = 7;
10854 if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10855 if( v == VariantCapablanca || v == VariantCapaRandom ||
10856 v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10858 if( v == VariantCourier ) width = 12;
10859 if( v == VariantSuper ) holdings = 8;
10860 if( v == VariantGreat ) width = 10, holdings = 8;
10861 if( v == VariantSChess ) holdings = 7;
10862 if( v == VariantGrand ) width = 10, height = 10, holdings = 7;
10863 if( v == VariantChuChess) width = 10, height = 10;
10864 if( v == VariantChu ) width = 12, height = 12;
10865 return boardWidth >= 0 && boardWidth != width || // -1 is default,
10866 boardHeight >= 0 && boardHeight != height || // and thus by definition OK
10867 holdingsSize >= 0 && holdingsSize != holdings;
10870 char variantError[MSG_SIZ];
10873 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10874 { // returns error message (recognizable by upper-case) if engine does not support the variant
10875 char *p, *variant = VariantName(v);
10876 static char b[MSG_SIZ];
10877 if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10878 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10879 holdingsSize, variant); // cook up sized variant name
10880 /* [HGM] varsize: try first if this deviant size variant is specifically known */
10881 if(StrStr(list, b) == NULL) {
10882 // specific sized variant not known, check if general sizing allowed
10883 if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10884 snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10885 boardWidth, boardHeight, holdingsSize, engine);
10888 /* [HGM] here we really should compare with the maximum supported board size */
10890 } else snprintf(b, MSG_SIZ,"%s", variant);
10891 if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10892 p = StrStr(list, b);
10893 while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10895 // occurs not at all in list, or only as sub-string
10896 snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10897 if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10898 int l = strlen(variantError);
10900 while(p != list && p[-1] != ',') p--;
10901 q = strchr(p, ',');
10902 if(q) *q = NULLCHAR;
10903 snprintf(variantError + l, MSG_SIZ - l, _(", but %s is"), p);
10912 InitChessProgram (ChessProgramState *cps, int setup)
10913 /* setup needed to setup FRC opening position */
10915 char buf[MSG_SIZ], *b;
10916 if (appData.noChessProgram) return;
10917 hintRequested = FALSE;
10918 bookRequested = FALSE;
10920 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10921 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10922 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10923 if(cps->memSize) { /* [HGM] memory */
10924 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10925 SendToProgram(buf, cps);
10927 SendEgtPath(cps); /* [HGM] EGT */
10928 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10929 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10930 SendToProgram(buf, cps);
10933 setboardSpoiledMachineBlack = FALSE;
10934 SendToProgram(cps->initString, cps);
10935 if (gameInfo.variant != VariantNormal &&
10936 gameInfo.variant != VariantLoadable
10937 /* [HGM] also send variant if board size non-standard */
10938 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10940 b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10941 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10945 char c, *q = cps->variants, *p = strchr(q, ',');
10946 if(p) *p = NULLCHAR;
10947 v = StringToVariant(q);
10948 DisplayError(variantError, 0);
10949 if(v != VariantUnknown && cps == &first) {
10951 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10952 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10953 ASSIGN(appData.variant, q);
10954 Reset(TRUE, FALSE);
10960 snprintf(buf, MSG_SIZ, "variant %s\n", b);
10961 SendToProgram(buf, cps);
10963 currentlyInitializedVariant = gameInfo.variant;
10965 /* [HGM] send opening position in FRC to first engine */
10967 SendToProgram("force\n", cps);
10969 /* engine is now in force mode! Set flag to wake it up after first move. */
10970 setboardSpoiledMachineBlack = 1;
10973 if (cps->sendICS) {
10974 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10975 SendToProgram(buf, cps);
10977 cps->maybeThinking = FALSE;
10978 cps->offeredDraw = 0;
10979 if (!appData.icsActive) {
10980 SendTimeControl(cps, movesPerSession, timeControl,
10981 timeIncrement, appData.searchDepth,
10984 if (appData.showThinking
10985 // [HGM] thinking: four options require thinking output to be sent
10986 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10988 SendToProgram("post\n", cps);
10990 SendToProgram("hard\n", cps);
10991 if (!appData.ponderNextMove) {
10992 /* Warning: "easy" is a toggle in GNU Chess, so don't send
10993 it without being sure what state we are in first. "hard"
10994 is not a toggle, so that one is OK.
10996 SendToProgram("easy\n", cps);
10998 if (cps->usePing) {
10999 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
11000 SendToProgram(buf, cps);
11002 cps->initDone = TRUE;
11003 ClearEngineOutputPane(cps == &second);
11008 ResendOptions (ChessProgramState *cps, int toEngine)
11009 { // send the stored value of the options
11011 static char buf2[MSG_SIZ*10];
11012 char buf[MSG_SIZ], *p = buf2;
11013 Option *opt = cps->option;
11015 for(i=0; i<cps->nrOptions; i++, opt++) {
11017 switch(opt->type) {
11021 if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
11022 snprintf(buf, MSG_SIZ, "%s=%d", opt->name, opt->value);
11025 if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
11026 snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->choice[opt->value]);
11029 if(strcmp(opt->textValue, opt->name + MSG_SIZ - 100))
11030 snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->textValue);
11038 snprintf(buf2, MSG_SIZ, "option %s\n", buf);
11039 SendToProgram(buf2, cps);
11041 if(p != buf2) *p++ = ',';
11042 strncpy(p, buf, 10*MSG_SIZ-1 - (p - buf2));
11051 StartChessProgram (ChessProgramState *cps)
11056 if (appData.noChessProgram) return;
11057 cps->initDone = FALSE;
11059 if (strcmp(cps->host, "localhost") == 0) {
11060 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
11061 } else if (*appData.remoteShell == NULLCHAR) {
11062 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
11064 if (*appData.remoteUser == NULLCHAR) {
11065 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
11068 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
11069 cps->host, appData.remoteUser, cps->program);
11071 err = StartChildProcess(buf, "", &cps->pr);
11075 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
11076 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
11077 if(cps != &first) return;
11078 appData.noChessProgram = TRUE;
11081 // DisplayFatalError(buf, err, 1);
11082 // cps->pr = NoProc;
11083 // cps->isr = NULL;
11087 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
11088 if (cps->protocolVersion > 1) {
11089 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
11090 if(!cps->reload) { // do not clear options when reloading because of -xreuse
11091 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
11092 cps->comboCnt = 0; // and values of combo boxes
11094 SendToProgram(buf, cps);
11095 if(cps->reload) ResendOptions(cps, TRUE);
11097 SendToProgram("xboard\n", cps);
11102 TwoMachinesEventIfReady P((void))
11104 static int curMess = 0;
11105 if (first.lastPing != first.lastPong) {
11106 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
11107 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11110 if (second.lastPing != second.lastPong) {
11111 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
11112 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11115 DisplayMessage("", ""); curMess = 0;
11116 TwoMachinesEvent();
11120 MakeName (char *template)
11124 static char buf[MSG_SIZ];
11128 clock = time((time_t *)NULL);
11129 tm = localtime(&clock);
11131 while(*p++ = *template++) if(p[-1] == '%') {
11132 switch(*template++) {
11133 case 0: *p = 0; return buf;
11134 case 'Y': i = tm->tm_year+1900; break;
11135 case 'y': i = tm->tm_year-100; break;
11136 case 'M': i = tm->tm_mon+1; break;
11137 case 'd': i = tm->tm_mday; break;
11138 case 'h': i = tm->tm_hour; break;
11139 case 'm': i = tm->tm_min; break;
11140 case 's': i = tm->tm_sec; break;
11143 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11149 CountPlayers (char *p)
11152 while(p = strchr(p, '\n')) p++, n++; // count participants
11157 WriteTourneyFile (char *results, FILE *f)
11158 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11159 if(f == NULL) f = fopen(appData.tourneyFile, "w");
11160 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11161 // create a file with tournament description
11162 fprintf(f, "-participants {%s}\n", appData.participants);
11163 fprintf(f, "-seedBase %d\n", appData.seedBase);
11164 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11165 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11166 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11167 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11168 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11169 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11170 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11171 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11172 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11173 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11174 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11175 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11176 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11177 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11178 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11179 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11180 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11181 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11182 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11183 fprintf(f, "-smpCores %d\n", appData.smpCores);
11185 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11187 fprintf(f, "-mps %d\n", appData.movesPerSession);
11188 fprintf(f, "-tc %s\n", appData.timeControl);
11189 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11191 fprintf(f, "-results \"%s\"\n", results);
11196 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11199 Substitute (char *participants, int expunge)
11201 int i, changed, changes=0, nPlayers=0;
11202 char *p, *q, *r, buf[MSG_SIZ];
11203 if(participants == NULL) return;
11204 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11205 r = p = participants; q = appData.participants;
11206 while(*p && *p == *q) {
11207 if(*p == '\n') r = p+1, nPlayers++;
11210 if(*p) { // difference
11211 while(*p && *p++ != '\n')
11213 while(*q && *q++ != '\n')
11215 changed = nPlayers;
11216 changes = 1 + (strcmp(p, q) != 0);
11218 if(changes == 1) { // a single engine mnemonic was changed
11219 q = r; while(*q) nPlayers += (*q++ == '\n');
11220 p = buf; while(*r && (*p = *r++) != '\n') p++;
11222 NamesToList(firstChessProgramNames, command, mnemonic, "all");
11223 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11224 if(mnemonic[i]) { // The substitute is valid
11226 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11227 flock(fileno(f), LOCK_EX);
11228 ParseArgsFromFile(f);
11229 fseek(f, 0, SEEK_SET);
11230 FREE(appData.participants); appData.participants = participants;
11231 if(expunge) { // erase results of replaced engine
11232 int len = strlen(appData.results), w, b, dummy;
11233 for(i=0; i<len; i++) {
11234 Pairing(i, nPlayers, &w, &b, &dummy);
11235 if((w == changed || b == changed) && appData.results[i] == '*') {
11236 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11241 for(i=0; i<len; i++) {
11242 Pairing(i, nPlayers, &w, &b, &dummy);
11243 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11246 WriteTourneyFile(appData.results, f);
11247 fclose(f); // release lock
11250 } else DisplayError(_("No engine with the name you gave is installed"), 0);
11252 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11253 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
11254 free(participants);
11259 CheckPlayers (char *participants)
11262 char buf[MSG_SIZ], *p;
11263 NamesToList(firstChessProgramNames, command, mnemonic, "all");
11264 while(p = strchr(participants, '\n')) {
11266 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11268 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11270 DisplayError(buf, 0);
11274 participants = p + 1;
11280 CreateTourney (char *name)
11283 if(matchMode && strcmp(name, appData.tourneyFile)) {
11284 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11286 if(name[0] == NULLCHAR) {
11287 if(appData.participants[0])
11288 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11291 f = fopen(name, "r");
11292 if(f) { // file exists
11293 ASSIGN(appData.tourneyFile, name);
11294 ParseArgsFromFile(f); // parse it
11296 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11297 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11298 DisplayError(_("Not enough participants"), 0);
11301 if(CheckPlayers(appData.participants)) return 0;
11302 ASSIGN(appData.tourneyFile, name);
11303 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11304 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11307 appData.noChessProgram = FALSE;
11308 appData.clockMode = TRUE;
11314 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11316 char buf[2*MSG_SIZ], *p, *q;
11317 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11318 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11319 skip = !all && group[0]; // if group requested, we start in skip mode
11320 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11321 p = names; q = buf; header = 0;
11322 while(*p && *p != '\n') *q++ = *p++;
11324 if(*p == '\n') p++;
11325 if(buf[0] == '#') {
11326 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11327 depth++; // we must be entering a new group
11328 if(all) continue; // suppress printing group headers when complete list requested
11330 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11332 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11333 if(engineList[i]) free(engineList[i]);
11334 engineList[i] = strdup(buf);
11335 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11336 if(engineMnemonic[i]) free(engineMnemonic[i]);
11337 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11339 sscanf(q + 8, "%s", buf + strlen(buf));
11342 engineMnemonic[i] = strdup(buf);
11345 engineList[i] = engineMnemonic[i] = NULL;
11350 SaveEngineSettings (int n)
11352 int len; char *p, *q, *s, buf[MSG_SIZ], *optionSettings;
11353 if(!currentEngine[n] || !currentEngine[n][0]) { DisplayMessage("saving failed: engine not from list", ""); return; } // no engine from list is loaded
11354 p = strstr(firstChessProgramNames, currentEngine[n]);
11355 if(!p) { DisplayMessage("saving failed: engine not found in list", ""); return; } // sanity check; engine could be deleted from list after loading
11356 optionSettings = ResendOptions(n ? &second : &first, FALSE);
11357 len = strlen(currentEngine[n]);
11358 q = p + len; *p = 0; // cut list into head and tail piece
11359 s = strstr(currentEngine[n], "firstOptions");
11360 if(s && (s[-1] == '-' || s[-1] == '/') && (s[12] == ' ' || s[12] == '=') && (s[13] == '"' || s[13] == '\'')) {
11362 while(*r && *r != s[13]) r++;
11363 s[14] = 0; // cut currentEngine into head and tail part, removing old settings
11364 snprintf(buf, MSG_SIZ, "%s%s%s", currentEngine[n], optionSettings, *r ? r : "\""); // synthesize new engine line
11365 } else if(*optionSettings) {
11366 snprintf(buf, MSG_SIZ, "%s -firstOptions \"%s\"", currentEngine[n], optionSettings);
11368 ASSIGN(currentEngine[n], buf); // updated engine line
11369 len = p - firstChessProgramNames + strlen(q) + strlen(currentEngine[n]) + 1;
11371 snprintf(s, len, "%s%s%s", firstChessProgramNames, currentEngine[n], q);
11372 FREE(firstChessProgramNames); firstChessProgramNames = s; // new list
11375 // following implemented as macro to avoid type limitations
11376 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11379 SwapEngines (int n)
11380 { // swap settings for first engine and other engine (so far only some selected options)
11385 SWAP(chessProgram, p)
11387 SWAP(hasOwnBookUCI, h)
11388 SWAP(protocolVersion, h)
11390 SWAP(scoreIsAbsolute, h)
11395 SWAP(engOptions, p)
11396 SWAP(engInitString, p)
11397 SWAP(computerString, p)
11399 SWAP(fenOverride, p)
11401 SWAP(accumulateTC, h)
11408 GetEngineLine (char *s, int n)
11412 extern char *icsNames;
11413 if(!s || !*s) return 0;
11414 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11415 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11416 if(!mnemonic[i]) return 0;
11417 if(n == 11) return 1; // just testing if there was a match
11418 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11419 if(n == 1) SwapEngines(n);
11420 ParseArgsFromString(buf);
11421 if(n == 1) SwapEngines(n);
11422 if(n < 2) { ASSIGN(currentEngine[n], command[i]); }
11423 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11424 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11425 ParseArgsFromString(buf);
11431 SetPlayer (int player, char *p)
11432 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
11434 char buf[MSG_SIZ], *engineName;
11435 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11436 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11437 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11439 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11440 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11441 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11442 ParseArgsFromString(buf);
11443 } else { // no engine with this nickname is installed!
11444 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11445 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11446 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11448 DisplayError(buf, 0);
11455 char *recentEngines;
11458 RecentEngineEvent (int nr)
11461 // SwapEngines(1); // bump first to second
11462 // ReplaceEngine(&second, 1); // and load it there
11463 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11464 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11465 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11466 ReplaceEngine(&first, 0);
11467 FloatToFront(&appData.recentEngineList, command[n]);
11468 ASSIGN(currentEngine[0], command[n]);
11473 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11474 { // determine players from game number
11475 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11477 if(appData.tourneyType == 0) {
11478 roundsPerCycle = (nPlayers - 1) | 1;
11479 pairingsPerRound = nPlayers / 2;
11480 } else if(appData.tourneyType > 0) {
11481 roundsPerCycle = nPlayers - appData.tourneyType;
11482 pairingsPerRound = appData.tourneyType;
11484 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11485 gamesPerCycle = gamesPerRound * roundsPerCycle;
11486 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11487 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11488 curRound = nr / gamesPerRound; nr %= gamesPerRound;
11489 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11490 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11491 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11493 if(appData.cycleSync) *syncInterval = gamesPerCycle;
11494 if(appData.roundSync) *syncInterval = gamesPerRound;
11496 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11498 if(appData.tourneyType == 0) {
11499 if(curPairing == (nPlayers-1)/2 ) {
11500 *whitePlayer = curRound;
11501 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11503 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11504 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11505 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11506 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11508 } else if(appData.tourneyType > 1) {
11509 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11510 *whitePlayer = curRound + appData.tourneyType;
11511 } else if(appData.tourneyType > 0) {
11512 *whitePlayer = curPairing;
11513 *blackPlayer = curRound + appData.tourneyType;
11516 // take care of white/black alternation per round.
11517 // For cycles and games this is already taken care of by default, derived from matchGame!
11518 return curRound & 1;
11522 NextTourneyGame (int nr, int *swapColors)
11523 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11525 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11527 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11528 tf = fopen(appData.tourneyFile, "r");
11529 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11530 ParseArgsFromFile(tf); fclose(tf);
11531 InitTimeControls(); // TC might be altered from tourney file
11533 nPlayers = CountPlayers(appData.participants); // count participants
11534 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11535 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11538 p = q = appData.results;
11539 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11540 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11541 DisplayMessage(_("Waiting for other game(s)"),"");
11542 waitingForGame = TRUE;
11543 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11546 waitingForGame = FALSE;
11549 if(appData.tourneyType < 0) {
11550 if(nr>=0 && !pairingReceived) {
11552 if(pairing.pr == NoProc) {
11553 if(!appData.pairingEngine[0]) {
11554 DisplayFatalError(_("No pairing engine specified"), 0, 1);
11557 StartChessProgram(&pairing); // starts the pairing engine
11559 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11560 SendToProgram(buf, &pairing);
11561 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11562 SendToProgram(buf, &pairing);
11563 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11565 pairingReceived = 0; // ... so we continue here
11567 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11568 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11569 matchGame = 1; roundNr = nr / syncInterval + 1;
11572 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11574 // redefine engines, engine dir, etc.
11575 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11576 if(first.pr == NoProc) {
11577 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11578 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
11580 if(second.pr == NoProc) {
11582 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11583 SwapEngines(1); // and make that valid for second engine by swapping
11584 InitEngine(&second, 1);
11586 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
11587 UpdateLogos(FALSE); // leave display to ModeHiglight()
11593 { // performs game initialization that does not invoke engines, and then tries to start the game
11594 int res, firstWhite, swapColors = 0;
11595 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11596 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
11598 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11599 if(strcmp(buf, currentDebugFile)) { // name has changed
11600 FILE *f = fopen(buf, "w");
11601 if(f) { // if opening the new file failed, just keep using the old one
11602 ASSIGN(currentDebugFile, buf);
11606 if(appData.serverFileName) {
11607 if(serverFP) fclose(serverFP);
11608 serverFP = fopen(appData.serverFileName, "w");
11609 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11610 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11614 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11615 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11616 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
11617 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11618 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11619 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11620 Reset(FALSE, first.pr != NoProc);
11621 res = LoadGameOrPosition(matchGame); // setup game
11622 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11623 if(!res) return; // abort when bad game/pos file
11624 if(appData.epd) {// in EPD mode we make sure first engine is to move
11625 firstWhite = !(forwardMostMove & 1);
11626 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
11627 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11629 TwoMachinesEvent();
11633 UserAdjudicationEvent (int result)
11635 ChessMove gameResult = GameIsDrawn;
11638 gameResult = WhiteWins;
11640 else if( result < 0 ) {
11641 gameResult = BlackWins;
11644 if( gameMode == TwoMachinesPlay ) {
11645 GameEnds( gameResult, "User adjudication", GE_XBOARD );
11650 // [HGM] save: calculate checksum of game to make games easily identifiable
11652 StringCheckSum (char *s)
11655 if(s==NULL) return 0;
11656 while(*s) i = i*259 + *s++;
11664 for(i=backwardMostMove; i<forwardMostMove; i++) {
11665 sum += pvInfoList[i].depth;
11666 sum += StringCheckSum(parseList[i]);
11667 sum += StringCheckSum(commentList[i]);
11670 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11671 return sum + StringCheckSum(commentList[i]);
11672 } // end of save patch
11675 GameEnds (ChessMove result, char *resultDetails, int whosays)
11677 GameMode nextGameMode;
11679 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11681 if(endingGame) return; /* [HGM] crash: forbid recursion */
11683 if(twoBoards) { // [HGM] dual: switch back to one board
11684 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11685 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11687 if (appData.debugMode) {
11688 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11689 result, resultDetails ? resultDetails : "(null)", whosays);
11692 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11694 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11696 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11697 /* If we are playing on ICS, the server decides when the
11698 game is over, but the engine can offer to draw, claim
11702 if (appData.zippyPlay && first.initDone) {
11703 if (result == GameIsDrawn) {
11704 /* In case draw still needs to be claimed */
11705 SendToICS(ics_prefix);
11706 SendToICS("draw\n");
11707 } else if (StrCaseStr(resultDetails, "resign")) {
11708 SendToICS(ics_prefix);
11709 SendToICS("resign\n");
11713 endingGame = 0; /* [HGM] crash */
11717 /* If we're loading the game from a file, stop */
11718 if (whosays == GE_FILE) {
11719 (void) StopLoadGameTimer();
11723 /* Cancel draw offers */
11724 first.offeredDraw = second.offeredDraw = 0;
11726 /* If this is an ICS game, only ICS can really say it's done;
11727 if not, anyone can. */
11728 isIcsGame = (gameMode == IcsPlayingWhite ||
11729 gameMode == IcsPlayingBlack ||
11730 gameMode == IcsObserving ||
11731 gameMode == IcsExamining);
11733 if (!isIcsGame || whosays == GE_ICS) {
11734 /* OK -- not an ICS game, or ICS said it was done */
11736 if (!isIcsGame && !appData.noChessProgram)
11737 SetUserThinkingEnables();
11739 /* [HGM] if a machine claims the game end we verify this claim */
11740 if(gameMode == TwoMachinesPlay && appData.testClaims) {
11741 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11743 ChessMove trueResult = (ChessMove) -1;
11745 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
11746 first.twoMachinesColor[0] :
11747 second.twoMachinesColor[0] ;
11749 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11750 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11751 /* [HGM] verify: engine mate claims accepted if they were flagged */
11752 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11754 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11755 /* [HGM] verify: engine mate claims accepted if they were flagged */
11756 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11758 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11759 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11762 // now verify win claims, but not in drop games, as we don't understand those yet
11763 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11764 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11765 (result == WhiteWins && claimer == 'w' ||
11766 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
11767 if (appData.debugMode) {
11768 fprintf(debugFP, "result=%d sp=%d move=%d\n",
11769 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11771 if(result != trueResult) {
11772 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11773 result = claimer == 'w' ? BlackWins : WhiteWins;
11774 resultDetails = buf;
11777 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11778 && (forwardMostMove <= backwardMostMove ||
11779 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11780 (claimer=='b')==(forwardMostMove&1))
11782 /* [HGM] verify: draws that were not flagged are false claims */
11783 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11784 result = claimer == 'w' ? BlackWins : WhiteWins;
11785 resultDetails = buf;
11787 /* (Claiming a loss is accepted no questions asked!) */
11788 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11789 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11790 result = GameUnfinished;
11791 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11793 /* [HGM] bare: don't allow bare King to win */
11794 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11795 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11796 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11797 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11798 && result != GameIsDrawn)
11799 { int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11800 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11801 int p = (int)boards[forwardMostMove][i][j] - color;
11802 if(p >= 0 && p <= (int)WhiteKing) k++;
11803 oppoKings += (p + color == WhiteKing + BlackPawn - color);
11805 if (appData.debugMode) {
11806 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11807 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11809 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11810 result = GameIsDrawn;
11811 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11812 resultDetails = buf;
11818 if(serverMoves != NULL && !loadFlag) { char c = '=';
11819 if(result==WhiteWins) c = '+';
11820 if(result==BlackWins) c = '-';
11821 if(resultDetails != NULL)
11822 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11824 if (resultDetails != NULL) {
11825 gameInfo.result = result;
11826 gameInfo.resultDetails = StrSave(resultDetails);
11828 /* display last move only if game was not loaded from file */
11829 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11830 DisplayMove(currentMove - 1);
11832 if (forwardMostMove != 0) {
11833 if (gameMode != PlayFromGameFile && gameMode != EditGame
11834 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11836 if (*appData.saveGameFile != NULLCHAR) {
11837 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11838 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11840 SaveGameToFile(appData.saveGameFile, TRUE);
11841 } else if (appData.autoSaveGames) {
11842 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11844 if (*appData.savePositionFile != NULLCHAR) {
11845 SavePositionToFile(appData.savePositionFile);
11847 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11851 /* Tell program how game ended in case it is learning */
11852 /* [HGM] Moved this to after saving the PGN, just in case */
11853 /* engine died and we got here through time loss. In that */
11854 /* case we will get a fatal error writing the pipe, which */
11855 /* would otherwise lose us the PGN. */
11856 /* [HGM] crash: not needed anymore, but doesn't hurt; */
11857 /* output during GameEnds should never be fatal anymore */
11858 if (gameMode == MachinePlaysWhite ||
11859 gameMode == MachinePlaysBlack ||
11860 gameMode == TwoMachinesPlay ||
11861 gameMode == IcsPlayingWhite ||
11862 gameMode == IcsPlayingBlack ||
11863 gameMode == BeginningOfGame) {
11865 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11867 if (first.pr != NoProc) {
11868 SendToProgram(buf, &first);
11870 if (second.pr != NoProc &&
11871 gameMode == TwoMachinesPlay) {
11872 SendToProgram(buf, &second);
11877 if (appData.icsActive) {
11878 if (appData.quietPlay &&
11879 (gameMode == IcsPlayingWhite ||
11880 gameMode == IcsPlayingBlack)) {
11881 SendToICS(ics_prefix);
11882 SendToICS("set shout 1\n");
11884 nextGameMode = IcsIdle;
11885 ics_user_moved = FALSE;
11886 /* clean up premove. It's ugly when the game has ended and the
11887 * premove highlights are still on the board.
11890 gotPremove = FALSE;
11891 ClearPremoveHighlights();
11892 DrawPosition(FALSE, boards[currentMove]);
11894 if (whosays == GE_ICS) {
11897 if (gameMode == IcsPlayingWhite)
11899 else if(gameMode == IcsPlayingBlack)
11900 PlayIcsLossSound();
11903 if (gameMode == IcsPlayingBlack)
11905 else if(gameMode == IcsPlayingWhite)
11906 PlayIcsLossSound();
11909 PlayIcsDrawSound();
11912 PlayIcsUnfinishedSound();
11915 if(appData.quitNext) { ExitEvent(0); return; }
11916 } else if (gameMode == EditGame ||
11917 gameMode == PlayFromGameFile ||
11918 gameMode == AnalyzeMode ||
11919 gameMode == AnalyzeFile) {
11920 nextGameMode = gameMode;
11922 nextGameMode = EndOfGame;
11927 nextGameMode = gameMode;
11930 if (appData.noChessProgram) {
11931 gameMode = nextGameMode;
11933 endingGame = 0; /* [HGM] crash */
11938 /* Put first chess program into idle state */
11939 if (first.pr != NoProc &&
11940 (gameMode == MachinePlaysWhite ||
11941 gameMode == MachinePlaysBlack ||
11942 gameMode == TwoMachinesPlay ||
11943 gameMode == IcsPlayingWhite ||
11944 gameMode == IcsPlayingBlack ||
11945 gameMode == BeginningOfGame)) {
11946 SendToProgram("force\n", &first);
11947 if (first.usePing) {
11949 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11950 SendToProgram(buf, &first);
11953 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11954 /* Kill off first chess program */
11955 if (first.isr != NULL)
11956 RemoveInputSource(first.isr);
11959 if (first.pr != NoProc) {
11961 DoSleep( appData.delayBeforeQuit );
11962 SendToProgram("quit\n", &first);
11963 DestroyChildProcess(first.pr, 4 + first.useSigterm);
11964 first.reload = TRUE;
11968 if (second.reuse) {
11969 /* Put second chess program into idle state */
11970 if (second.pr != NoProc &&
11971 gameMode == TwoMachinesPlay) {
11972 SendToProgram("force\n", &second);
11973 if (second.usePing) {
11975 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11976 SendToProgram(buf, &second);
11979 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11980 /* Kill off second chess program */
11981 if (second.isr != NULL)
11982 RemoveInputSource(second.isr);
11985 if (second.pr != NoProc) {
11986 DoSleep( appData.delayBeforeQuit );
11987 SendToProgram("quit\n", &second);
11988 DestroyChildProcess(second.pr, 4 + second.useSigterm);
11989 second.reload = TRUE;
11991 second.pr = NoProc;
11994 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11995 char resChar = '=';
11999 if (first.twoMachinesColor[0] == 'w') {
12002 second.matchWins++;
12007 if (first.twoMachinesColor[0] == 'b') {
12010 second.matchWins++;
12013 case GameUnfinished:
12019 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
12020 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
12021 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
12022 ReserveGame(nextGame, resChar); // sets nextGame
12023 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
12024 else ranking = strdup("busy"); //suppress popup when aborted but not finished
12025 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
12027 if (nextGame <= appData.matchGames && !abortMatch) {
12028 gameMode = nextGameMode;
12029 matchGame = nextGame; // this will be overruled in tourney mode!
12030 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
12031 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
12032 endingGame = 0; /* [HGM] crash */
12035 gameMode = nextGameMode;
12037 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
12038 OutputKibitz(2, buf);
12039 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
12040 OutputKibitz(2, buf);
12041 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
12042 if(second.matchWins) OutputKibitz(2, buf);
12043 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
12044 OutputKibitz(2, buf);
12046 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
12047 first.tidy, second.tidy,
12048 first.matchWins, second.matchWins,
12049 appData.matchGames - (first.matchWins + second.matchWins));
12050 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
12051 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
12052 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
12053 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
12054 first.twoMachinesColor = "black\n";
12055 second.twoMachinesColor = "white\n";
12057 first.twoMachinesColor = "white\n";
12058 second.twoMachinesColor = "black\n";
12062 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
12063 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
12065 gameMode = nextGameMode;
12067 endingGame = 0; /* [HGM] crash */
12068 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
12069 if(matchMode == TRUE) { // match through command line: exit with or without popup
12071 ToNrEvent(forwardMostMove);
12072 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
12074 } else DisplayFatalError(buf, 0, 0);
12075 } else { // match through menu; just stop, with or without popup
12076 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
12079 if(strcmp(ranking, "busy")) DisplayNote(ranking);
12080 } else DisplayNote(buf);
12082 if(ranking) free(ranking);
12086 /* Assumes program was just initialized (initString sent).
12087 Leaves program in force mode. */
12089 FeedMovesToProgram (ChessProgramState *cps, int upto)
12093 if (appData.debugMode)
12094 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
12095 startedFromSetupPosition ? "position and " : "",
12096 backwardMostMove, upto, cps->which);
12097 if(currentlyInitializedVariant != gameInfo.variant) {
12099 // [HGM] variantswitch: make engine aware of new variant
12100 if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
12101 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
12102 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
12103 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
12104 SendToProgram(buf, cps);
12105 currentlyInitializedVariant = gameInfo.variant;
12107 SendToProgram("force\n", cps);
12108 if (startedFromSetupPosition) {
12109 SendBoard(cps, backwardMostMove);
12110 if (appData.debugMode) {
12111 fprintf(debugFP, "feedMoves\n");
12114 for (i = backwardMostMove; i < upto; i++) {
12115 SendMoveToProgram(i, cps);
12121 ResurrectChessProgram ()
12123 /* The chess program may have exited.
12124 If so, restart it and feed it all the moves made so far. */
12125 static int doInit = 0;
12127 if (appData.noChessProgram) return 1;
12129 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
12130 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
12131 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
12132 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
12134 if (first.pr != NoProc) return 1;
12135 StartChessProgram(&first);
12137 InitChessProgram(&first, FALSE);
12138 FeedMovesToProgram(&first, currentMove);
12140 if (!first.sendTime) {
12141 /* can't tell gnuchess what its clock should read,
12142 so we bow to its notion. */
12144 timeRemaining[0][currentMove] = whiteTimeRemaining;
12145 timeRemaining[1][currentMove] = blackTimeRemaining;
12148 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
12149 appData.icsEngineAnalyze) && first.analysisSupport) {
12150 SendToProgram("analyze\n", &first);
12151 first.analyzing = TRUE;
12157 * Button procedures
12160 Reset (int redraw, int init)
12164 if (appData.debugMode) {
12165 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12166 redraw, init, gameMode);
12168 pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12169 deadRanks = 0; // assume entire board is used
12171 for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12172 CleanupTail(); // [HGM] vari: delete any stored variations
12173 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12174 pausing = pauseExamInvalid = FALSE;
12175 startedFromSetupPosition = blackPlaysFirst = FALSE;
12177 whiteFlag = blackFlag = FALSE;
12178 userOfferedDraw = FALSE;
12179 hintRequested = bookRequested = FALSE;
12180 first.maybeThinking = FALSE;
12181 second.maybeThinking = FALSE;
12182 first.bookSuspend = FALSE; // [HGM] book
12183 second.bookSuspend = FALSE;
12184 thinkOutput[0] = NULLCHAR;
12185 lastHint[0] = NULLCHAR;
12186 ClearGameInfo(&gameInfo);
12187 gameInfo.variant = StringToVariant(appData.variant);
12188 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12189 gameInfo.variant = VariantUnknown;
12190 strncpy(engineVariant, appData.variant, MSG_SIZ);
12192 ics_user_moved = ics_clock_paused = FALSE;
12193 ics_getting_history = H_FALSE;
12195 white_holding[0] = black_holding[0] = NULLCHAR;
12196 ClearProgramStats();
12197 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12201 flipView = appData.flipView;
12202 ClearPremoveHighlights();
12203 gotPremove = FALSE;
12204 alarmSounded = FALSE;
12205 killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12207 GameEnds(EndOfFile, NULL, GE_PLAYER);
12208 if(appData.serverMovesName != NULL) {
12209 /* [HGM] prepare to make moves file for broadcasting */
12210 clock_t t = clock();
12211 if(serverMoves != NULL) fclose(serverMoves);
12212 serverMoves = fopen(appData.serverMovesName, "r");
12213 if(serverMoves != NULL) {
12214 fclose(serverMoves);
12215 /* delay 15 sec before overwriting, so all clients can see end */
12216 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12218 serverMoves = fopen(appData.serverMovesName, "w");
12222 gameMode = BeginningOfGame;
12224 if(appData.icsActive) gameInfo.variant = VariantNormal;
12225 currentMove = forwardMostMove = backwardMostMove = 0;
12226 MarkTargetSquares(1);
12227 InitPosition(redraw);
12228 for (i = 0; i < MAX_MOVES; i++) {
12229 if (commentList[i] != NULL) {
12230 free(commentList[i]);
12231 commentList[i] = NULL;
12235 timeRemaining[0][0] = whiteTimeRemaining;
12236 timeRemaining[1][0] = blackTimeRemaining;
12238 if (first.pr == NoProc) {
12239 StartChessProgram(&first);
12242 InitChessProgram(&first, startedFromSetupPosition);
12245 DisplayMessage("", "");
12246 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12247 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12248 ClearMap(); // [HGM] exclude: invalidate map
12252 AutoPlayGameLoop ()
12255 if (!AutoPlayOneMove())
12257 if (matchMode || appData.timeDelay == 0)
12259 if (appData.timeDelay < 0)
12261 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12269 ReloadGame(1); // next game
12275 int fromX, fromY, toX, toY;
12277 if (appData.debugMode) {
12278 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12281 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12284 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12285 pvInfoList[currentMove].depth = programStats.depth;
12286 pvInfoList[currentMove].score = programStats.score;
12287 pvInfoList[currentMove].time = 0;
12288 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12289 else { // append analysis of final position as comment
12291 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12292 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12294 programStats.depth = 0;
12297 if (currentMove >= forwardMostMove) {
12298 if(gameMode == AnalyzeFile) {
12299 if(appData.loadGameIndex == -1) {
12300 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12301 ScheduleDelayedEvent(AnalyzeNextGame, 10);
12303 ExitAnalyzeMode(); SendToProgram("force\n", &first);
12306 // gameMode = EndOfGame;
12307 // ModeHighlight();
12309 /* [AS] Clear current move marker at the end of a game */
12310 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12315 toX = moveList[currentMove][2] - AAA;
12316 toY = moveList[currentMove][3] - ONE;
12318 if (moveList[currentMove][1] == '@') {
12319 if (appData.highlightLastMove) {
12320 SetHighlights(-1, -1, toX, toY);
12323 fromX = moveList[currentMove][0] - AAA;
12324 fromY = moveList[currentMove][1] - ONE;
12326 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12328 if(moveList[currentMove][4] == ';') { // multi-leg
12329 killX = moveList[currentMove][5] - AAA;
12330 killY = moveList[currentMove][6] - ONE;
12332 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12333 killX = killY = -1;
12335 if (appData.highlightLastMove) {
12336 SetHighlights(fromX, fromY, toX, toY);
12339 DisplayMove(currentMove);
12340 SendMoveToProgram(currentMove++, &first);
12341 DisplayBothClocks();
12342 DrawPosition(FALSE, boards[currentMove]);
12343 // [HGM] PV info: always display, routine tests if empty
12344 DisplayComment(currentMove - 1, commentList[currentMove]);
12350 LoadGameOneMove (ChessMove readAhead)
12352 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12353 char promoChar = NULLCHAR;
12354 ChessMove moveType;
12355 char move[MSG_SIZ];
12358 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12359 gameMode != AnalyzeMode && gameMode != Training) {
12364 yyboardindex = forwardMostMove;
12365 if (readAhead != EndOfFile) {
12366 moveType = readAhead;
12368 if (gameFileFP == NULL)
12370 moveType = (ChessMove) Myylex();
12374 switch (moveType) {
12376 if (appData.debugMode)
12377 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12380 /* append the comment but don't display it */
12381 AppendComment(currentMove, p, FALSE);
12384 case WhiteCapturesEnPassant:
12385 case BlackCapturesEnPassant:
12386 case WhitePromotion:
12387 case BlackPromotion:
12388 case WhiteNonPromotion:
12389 case BlackNonPromotion:
12392 case WhiteKingSideCastle:
12393 case WhiteQueenSideCastle:
12394 case BlackKingSideCastle:
12395 case BlackQueenSideCastle:
12396 case WhiteKingSideCastleWild:
12397 case WhiteQueenSideCastleWild:
12398 case BlackKingSideCastleWild:
12399 case BlackQueenSideCastleWild:
12401 case WhiteHSideCastleFR:
12402 case WhiteASideCastleFR:
12403 case BlackHSideCastleFR:
12404 case BlackASideCastleFR:
12406 if (appData.debugMode)
12407 fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12408 fromX = currentMoveString[0] - AAA;
12409 fromY = currentMoveString[1] - ONE;
12410 toX = currentMoveString[2] - AAA;
12411 toY = currentMoveString[3] - ONE;
12412 promoChar = currentMoveString[4];
12413 if(promoChar == ';') promoChar = currentMoveString[7];
12418 if (appData.debugMode)
12419 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12420 fromX = moveType == WhiteDrop ?
12421 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12422 (int) CharToPiece(ToLower(currentMoveString[0]));
12424 toX = currentMoveString[2] - AAA;
12425 toY = currentMoveString[3] - ONE;
12431 case GameUnfinished:
12432 if (appData.debugMode)
12433 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12434 p = strchr(yy_text, '{');
12435 if (p == NULL) p = strchr(yy_text, '(');
12438 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12440 q = strchr(p, *p == '{' ? '}' : ')');
12441 if (q != NULL) *q = NULLCHAR;
12444 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12445 GameEnds(moveType, p, GE_FILE);
12447 if (cmailMsgLoaded) {
12449 flipView = WhiteOnMove(currentMove);
12450 if (moveType == GameUnfinished) flipView = !flipView;
12451 if (appData.debugMode)
12452 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12457 if (appData.debugMode)
12458 fprintf(debugFP, "Parser hit end of file\n");
12459 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12465 if (WhiteOnMove(currentMove)) {
12466 GameEnds(BlackWins, "Black mates", GE_FILE);
12468 GameEnds(WhiteWins, "White mates", GE_FILE);
12472 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12478 case MoveNumberOne:
12479 if (lastLoadGameStart == GNUChessGame) {
12480 /* GNUChessGames have numbers, but they aren't move numbers */
12481 if (appData.debugMode)
12482 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12483 yy_text, (int) moveType);
12484 return LoadGameOneMove(EndOfFile); /* tail recursion */
12486 /* else fall thru */
12491 /* Reached start of next game in file */
12492 if (appData.debugMode)
12493 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12494 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12500 if (WhiteOnMove(currentMove)) {
12501 GameEnds(BlackWins, "Black mates", GE_FILE);
12503 GameEnds(WhiteWins, "White mates", GE_FILE);
12507 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12513 case PositionDiagram: /* should not happen; ignore */
12514 case ElapsedTime: /* ignore */
12515 case NAG: /* ignore */
12516 if (appData.debugMode)
12517 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12518 yy_text, (int) moveType);
12519 return LoadGameOneMove(EndOfFile); /* tail recursion */
12522 if (appData.testLegality) {
12523 if (appData.debugMode)
12524 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12525 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12526 (forwardMostMove / 2) + 1,
12527 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12528 DisplayError(move, 0);
12531 if (appData.debugMode)
12532 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12533 yy_text, currentMoveString);
12534 if(currentMoveString[1] == '@') {
12535 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12538 fromX = currentMoveString[0] - AAA;
12539 fromY = currentMoveString[1] - ONE;
12541 toX = currentMoveString[2] - AAA;
12542 toY = currentMoveString[3] - ONE;
12543 promoChar = currentMoveString[4];
12547 case AmbiguousMove:
12548 if (appData.debugMode)
12549 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12550 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12551 (forwardMostMove / 2) + 1,
12552 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12553 DisplayError(move, 0);
12558 case ImpossibleMove:
12559 if (appData.debugMode)
12560 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12561 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12562 (forwardMostMove / 2) + 1,
12563 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12564 DisplayError(move, 0);
12570 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12571 DrawPosition(FALSE, boards[currentMove]);
12572 DisplayBothClocks();
12573 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12574 DisplayComment(currentMove - 1, commentList[currentMove]);
12576 (void) StopLoadGameTimer();
12578 cmailOldMove = forwardMostMove;
12581 /* currentMoveString is set as a side-effect of yylex */
12583 thinkOutput[0] = NULLCHAR;
12584 MakeMove(fromX, fromY, toX, toY, promoChar);
12585 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12586 currentMove = forwardMostMove;
12591 /* Load the nth game from the given file */
12593 LoadGameFromFile (char *filename, int n, char *title, int useList)
12598 if (strcmp(filename, "-") == 0) {
12602 f = fopen(filename, "rb");
12604 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12605 DisplayError(buf, errno);
12609 if (fseek(f, 0, 0) == -1) {
12610 /* f is not seekable; probably a pipe */
12613 if (useList && n == 0) {
12614 int error = GameListBuild(f);
12616 DisplayError(_("Cannot build game list"), error);
12617 } else if (!ListEmpty(&gameList) &&
12618 ((ListGame *) gameList.tailPred)->number > 1) {
12619 GameListPopUp(f, title);
12626 return LoadGame(f, n, title, FALSE);
12631 MakeRegisteredMove ()
12633 int fromX, fromY, toX, toY;
12635 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12636 switch (cmailMoveType[lastLoadGameNumber - 1]) {
12639 if (appData.debugMode)
12640 fprintf(debugFP, "Restoring %s for game %d\n",
12641 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12643 thinkOutput[0] = NULLCHAR;
12644 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12645 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12646 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12647 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12648 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12649 promoChar = cmailMove[lastLoadGameNumber - 1][4];
12650 MakeMove(fromX, fromY, toX, toY, promoChar);
12651 ShowMove(fromX, fromY, toX, toY);
12653 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12660 if (WhiteOnMove(currentMove)) {
12661 GameEnds(BlackWins, "Black mates", GE_PLAYER);
12663 GameEnds(WhiteWins, "White mates", GE_PLAYER);
12668 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12675 if (WhiteOnMove(currentMove)) {
12676 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12678 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12683 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12694 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12696 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12700 if (gameNumber > nCmailGames) {
12701 DisplayError(_("No more games in this message"), 0);
12704 if (f == lastLoadGameFP) {
12705 int offset = gameNumber - lastLoadGameNumber;
12707 cmailMsg[0] = NULLCHAR;
12708 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12709 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12710 nCmailMovesRegistered--;
12712 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12713 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12714 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12717 if (! RegisterMove()) return FALSE;
12721 retVal = LoadGame(f, gameNumber, title, useList);
12723 /* Make move registered during previous look at this game, if any */
12724 MakeRegisteredMove();
12726 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12727 commentList[currentMove]
12728 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12729 DisplayComment(currentMove - 1, commentList[currentMove]);
12735 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12737 ReloadGame (int offset)
12739 int gameNumber = lastLoadGameNumber + offset;
12740 if (lastLoadGameFP == NULL) {
12741 DisplayError(_("No game has been loaded yet"), 0);
12744 if (gameNumber <= 0) {
12745 DisplayError(_("Can't back up any further"), 0);
12748 if (cmailMsgLoaded) {
12749 return CmailLoadGame(lastLoadGameFP, gameNumber,
12750 lastLoadGameTitle, lastLoadGameUseList);
12752 return LoadGame(lastLoadGameFP, gameNumber,
12753 lastLoadGameTitle, lastLoadGameUseList);
12757 int keys[EmptySquare+1];
12760 PositionMatches (Board b1, Board b2)
12763 switch(appData.searchMode) {
12764 case 1: return CompareWithRights(b1, b2);
12766 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12767 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12771 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12772 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12773 sum += keys[b1[r][f]] - keys[b2[r][f]];
12777 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12778 sum += keys[b1[r][f]] - keys[b2[r][f]];
12790 int pieceList[256], quickBoard[256];
12791 ChessSquare pieceType[256] = { EmptySquare };
12792 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12793 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12794 int soughtTotal, turn;
12795 Boolean epOK, flipSearch;
12798 unsigned char piece, to;
12801 #define DSIZE (250000)
12803 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12804 Move *moveDatabase = initialSpace;
12805 unsigned int movePtr, dataSize = DSIZE;
12808 MakePieceList (Board board, int *counts)
12810 int r, f, n=Q_PROMO, total=0;
12811 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12812 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12813 int sq = f + (r<<4);
12814 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12815 quickBoard[sq] = ++n;
12817 pieceType[n] = board[r][f];
12818 counts[board[r][f]]++;
12819 if(board[r][f] == WhiteKing) pieceList[1] = n; else
12820 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12824 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12829 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12831 int sq = fromX + (fromY<<4);
12832 int piece = quickBoard[sq], rook;
12833 quickBoard[sq] = 0;
12834 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12835 if(piece == pieceList[1] && fromY == toY) {
12836 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12837 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12838 moveDatabase[movePtr++].piece = Q_WCASTL;
12839 quickBoard[sq] = piece;
12840 piece = quickBoard[from]; quickBoard[from] = 0;
12841 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12842 } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12843 quickBoard[sq] = 0; // remove Rook
12844 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12845 moveDatabase[movePtr++].piece = Q_WCASTL;
12846 quickBoard[sq] = pieceList[1]; // put King
12848 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12851 if(piece == pieceList[2] && fromY == toY) {
12852 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12853 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12854 moveDatabase[movePtr++].piece = Q_BCASTL;
12855 quickBoard[sq] = piece;
12856 piece = quickBoard[from]; quickBoard[from] = 0;
12857 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12858 } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12859 quickBoard[sq] = 0; // remove Rook
12860 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12861 moveDatabase[movePtr++].piece = Q_BCASTL;
12862 quickBoard[sq] = pieceList[2]; // put King
12864 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12867 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12868 quickBoard[(fromY<<4)+toX] = 0;
12869 moveDatabase[movePtr].piece = Q_EP;
12870 moveDatabase[movePtr++].to = (fromY<<4)+toX;
12871 moveDatabase[movePtr].to = sq;
12873 if(promoPiece != pieceType[piece]) {
12874 moveDatabase[movePtr++].piece = Q_PROMO;
12875 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12877 moveDatabase[movePtr].piece = piece;
12878 quickBoard[sq] = piece;
12883 PackGame (Board board)
12885 Move *newSpace = NULL;
12886 moveDatabase[movePtr].piece = 0; // terminate previous game
12887 if(movePtr > dataSize) {
12888 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12889 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12890 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12893 Move *p = moveDatabase, *q = newSpace;
12894 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
12895 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12896 moveDatabase = newSpace;
12897 } else { // calloc failed, we must be out of memory. Too bad...
12898 dataSize = 0; // prevent calloc events for all subsequent games
12899 return 0; // and signal this one isn't cached
12903 MakePieceList(board, counts);
12908 QuickCompare (Board board, int *minCounts, int *maxCounts)
12909 { // compare according to search mode
12911 switch(appData.searchMode)
12913 case 1: // exact position match
12914 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12915 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12916 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12919 case 2: // can have extra material on empty squares
12920 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12921 if(board[r][f] == EmptySquare) continue;
12922 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12925 case 3: // material with exact Pawn structure
12926 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12927 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12928 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12929 } // fall through to material comparison
12930 case 4: // exact material
12931 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12933 case 6: // material range with given imbalance
12934 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12935 // fall through to range comparison
12936 case 5: // material range
12937 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12943 QuickScan (Board board, Move *move)
12944 { // reconstruct game,and compare all positions in it
12945 int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12947 int piece = move->piece;
12948 int to = move->to, from = pieceList[piece];
12949 if(found < 0) { // if already found just scan to game end for final piece count
12950 if(QuickCompare(soughtBoard, minSought, maxSought) ||
12951 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12952 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12953 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12955 static int lastCounts[EmptySquare+1];
12957 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12958 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12959 } else stretch = 0;
12960 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12961 if(found >= 0 && !appData.minPieces) return found;
12963 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12964 if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12965 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12966 piece = (++move)->piece;
12967 from = pieceList[piece];
12968 counts[pieceType[piece]]--;
12969 pieceType[piece] = (ChessSquare) move->to;
12970 counts[move->to]++;
12971 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12972 counts[pieceType[quickBoard[to]]]--;
12973 quickBoard[to] = 0; total--;
12976 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12977 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12978 from = pieceList[piece]; // so this must be King
12979 quickBoard[from] = 0;
12980 pieceList[piece] = to;
12981 from = pieceList[(++move)->piece]; // for FRC this has to be done here
12982 quickBoard[from] = 0; // rook
12983 quickBoard[to] = piece;
12984 to = move->to; piece = move->piece;
12988 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12989 if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12990 quickBoard[from] = 0;
12992 quickBoard[to] = piece;
12993 pieceList[piece] = to;
13003 flipSearch = FALSE;
13004 CopyBoard(soughtBoard, boards[currentMove]);
13005 soughtTotal = MakePieceList(soughtBoard, maxSought);
13006 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
13007 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
13008 CopyBoard(reverseBoard, boards[currentMove]);
13009 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
13010 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
13011 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
13012 reverseBoard[r][f] = piece;
13014 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
13015 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
13016 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
13017 || (boards[currentMove][CASTLING][2] == NoRights ||
13018 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
13019 && (boards[currentMove][CASTLING][5] == NoRights ||
13020 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
13023 CopyBoard(flipBoard, soughtBoard);
13024 CopyBoard(rotateBoard, reverseBoard);
13025 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
13026 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
13027 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
13030 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
13031 if(appData.searchMode >= 5) {
13032 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
13033 MakePieceList(soughtBoard, minSought);
13034 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
13036 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
13037 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
13040 GameInfo dummyInfo;
13041 static int creatingBook;
13044 GameContainsPosition (FILE *f, ListGame *lg)
13046 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
13047 int fromX, fromY, toX, toY;
13049 static int initDone=FALSE;
13051 // weed out games based on numerical tag comparison
13052 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
13053 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
13054 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
13055 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
13057 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
13060 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
13061 else CopyBoard(boards[scratch], initialPosition); // default start position
13064 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
13065 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
13068 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13069 fseek(f, lg->offset, 0);
13072 yyboardindex = scratch;
13073 quickFlag = plyNr+1;
13078 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
13084 if(plyNr) return -1; // after we have seen moves, this is for new game
13087 case AmbiguousMove: // we cannot reconstruct the game beyond these two
13088 case ImpossibleMove:
13089 case WhiteWins: // game ends here with these four
13092 case GameUnfinished:
13096 if(appData.testLegality) return -1;
13097 case WhiteCapturesEnPassant:
13098 case BlackCapturesEnPassant:
13099 case WhitePromotion:
13100 case BlackPromotion:
13101 case WhiteNonPromotion:
13102 case BlackNonPromotion:
13105 case WhiteKingSideCastle:
13106 case WhiteQueenSideCastle:
13107 case BlackKingSideCastle:
13108 case BlackQueenSideCastle:
13109 case WhiteKingSideCastleWild:
13110 case WhiteQueenSideCastleWild:
13111 case BlackKingSideCastleWild:
13112 case BlackQueenSideCastleWild:
13113 case WhiteHSideCastleFR:
13114 case WhiteASideCastleFR:
13115 case BlackHSideCastleFR:
13116 case BlackASideCastleFR:
13117 fromX = currentMoveString[0] - AAA;
13118 fromY = currentMoveString[1] - ONE;
13119 toX = currentMoveString[2] - AAA;
13120 toY = currentMoveString[3] - ONE;
13121 promoChar = currentMoveString[4];
13125 fromX = next == WhiteDrop ?
13126 (int) CharToPiece(ToUpper(currentMoveString[0])) :
13127 (int) CharToPiece(ToLower(currentMoveString[0]));
13129 toX = currentMoveString[2] - AAA;
13130 toY = currentMoveString[3] - ONE;
13134 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
13136 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
13137 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13138 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
13139 if(appData.findMirror) {
13140 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
13141 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
13146 /* Load the nth game from open file f */
13148 LoadGame (FILE *f, int gameNumber, char *title, int useList)
13152 int gn = gameNumber;
13153 ListGame *lg = NULL;
13154 int numPGNTags = 0, i;
13156 GameMode oldGameMode;
13157 VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13158 char oldName[MSG_SIZ];
13160 safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13162 if (appData.debugMode)
13163 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13165 if (gameMode == Training )
13166 SetTrainingModeOff();
13168 oldGameMode = gameMode;
13169 if (gameMode != BeginningOfGame) {
13170 Reset(FALSE, TRUE);
13172 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13175 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13176 fclose(lastLoadGameFP);
13180 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13183 fseek(f, lg->offset, 0);
13184 GameListHighlight(gameNumber);
13185 pos = lg->position;
13189 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13190 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13192 DisplayError(_("Game number out of range"), 0);
13197 if (fseek(f, 0, 0) == -1) {
13198 if (f == lastLoadGameFP ?
13199 gameNumber == lastLoadGameNumber + 1 :
13203 DisplayError(_("Can't seek on game file"), 0);
13208 lastLoadGameFP = f;
13209 lastLoadGameNumber = gameNumber;
13210 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13211 lastLoadGameUseList = useList;
13215 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13216 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13217 lg->gameInfo.black);
13219 } else if (*title != NULLCHAR) {
13220 if (gameNumber > 1) {
13221 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13224 DisplayTitle(title);
13228 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13229 gameMode = PlayFromGameFile;
13233 currentMove = forwardMostMove = backwardMostMove = 0;
13234 CopyBoard(boards[0], initialPosition);
13238 * Skip the first gn-1 games in the file.
13239 * Also skip over anything that precedes an identifiable
13240 * start of game marker, to avoid being confused by
13241 * garbage at the start of the file. Currently
13242 * recognized start of game markers are the move number "1",
13243 * the pattern "gnuchess .* game", the pattern
13244 * "^[#;%] [^ ]* game file", and a PGN tag block.
13245 * A game that starts with one of the latter two patterns
13246 * will also have a move number 1, possibly
13247 * following a position diagram.
13248 * 5-4-02: Let's try being more lenient and allowing a game to
13249 * start with an unnumbered move. Does that break anything?
13251 cm = lastLoadGameStart = EndOfFile;
13253 yyboardindex = forwardMostMove;
13254 cm = (ChessMove) Myylex();
13257 if (cmailMsgLoaded) {
13258 nCmailGames = CMAIL_MAX_GAMES - gn;
13261 DisplayError(_("Game not found in file"), 0);
13268 lastLoadGameStart = cm;
13271 case MoveNumberOne:
13272 switch (lastLoadGameStart) {
13277 case MoveNumberOne:
13279 gn--; /* count this game */
13280 lastLoadGameStart = cm;
13289 switch (lastLoadGameStart) {
13292 case MoveNumberOne:
13294 gn--; /* count this game */
13295 lastLoadGameStart = cm;
13298 lastLoadGameStart = cm; /* game counted already */
13306 yyboardindex = forwardMostMove;
13307 cm = (ChessMove) Myylex();
13308 } while (cm == PGNTag || cm == Comment);
13315 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13316 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
13317 != CMAIL_OLD_RESULT) {
13319 cmailResult[ CMAIL_MAX_GAMES
13320 - gn - 1] = CMAIL_OLD_RESULT;
13327 /* Only a NormalMove can be at the start of a game
13328 * without a position diagram. */
13329 if (lastLoadGameStart == EndOfFile ) {
13331 lastLoadGameStart = MoveNumberOne;
13340 if (appData.debugMode)
13341 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13343 for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13345 if (cm == XBoardGame) {
13346 /* Skip any header junk before position diagram and/or move 1 */
13348 yyboardindex = forwardMostMove;
13349 cm = (ChessMove) Myylex();
13351 if (cm == EndOfFile ||
13352 cm == GNUChessGame || cm == XBoardGame) {
13353 /* Empty game; pretend end-of-file and handle later */
13358 if (cm == MoveNumberOne || cm == PositionDiagram ||
13359 cm == PGNTag || cm == Comment)
13362 } else if (cm == GNUChessGame) {
13363 if (gameInfo.event != NULL) {
13364 free(gameInfo.event);
13366 gameInfo.event = StrSave(yy_text);
13369 startedFromSetupPosition = startedFromPositionFile; // [HGM]
13370 while (cm == PGNTag) {
13371 if (appData.debugMode)
13372 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13373 err = ParsePGNTag(yy_text, &gameInfo);
13374 if (!err) numPGNTags++;
13376 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13377 if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13378 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13379 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13380 InitPosition(TRUE);
13381 oldVariant = gameInfo.variant;
13382 if (appData.debugMode)
13383 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13387 if (gameInfo.fen != NULL) {
13388 Board initial_position;
13389 startedFromSetupPosition = TRUE;
13390 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13392 DisplayError(_("Bad FEN position in file"), 0);
13395 CopyBoard(boards[0], initial_position);
13396 if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13397 CopyBoard(initialPosition, initial_position);
13398 if (blackPlaysFirst) {
13399 currentMove = forwardMostMove = backwardMostMove = 1;
13400 CopyBoard(boards[1], initial_position);
13401 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13402 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13403 timeRemaining[0][1] = whiteTimeRemaining;
13404 timeRemaining[1][1] = blackTimeRemaining;
13405 if (commentList[0] != NULL) {
13406 commentList[1] = commentList[0];
13407 commentList[0] = NULL;
13410 currentMove = forwardMostMove = backwardMostMove = 0;
13412 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13414 initialRulePlies = FENrulePlies;
13415 for( i=0; i< nrCastlingRights; i++ )
13416 initialRights[i] = initial_position[CASTLING][i];
13418 yyboardindex = forwardMostMove;
13419 free(gameInfo.fen);
13420 gameInfo.fen = NULL;
13423 yyboardindex = forwardMostMove;
13424 cm = (ChessMove) Myylex();
13426 /* Handle comments interspersed among the tags */
13427 while (cm == Comment) {
13429 if (appData.debugMode)
13430 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13432 AppendComment(currentMove, p, FALSE);
13433 yyboardindex = forwardMostMove;
13434 cm = (ChessMove) Myylex();
13438 /* don't rely on existence of Event tag since if game was
13439 * pasted from clipboard the Event tag may not exist
13441 if (numPGNTags > 0){
13443 if (gameInfo.variant == VariantNormal) {
13444 VariantClass v = StringToVariant(gameInfo.event);
13445 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13446 if(v < VariantShogi) gameInfo.variant = v;
13449 if( appData.autoDisplayTags ) {
13450 tags = PGNTags(&gameInfo);
13451 TagsPopUp(tags, CmailMsg());
13456 /* Make something up, but don't display it now */
13461 if (cm == PositionDiagram) {
13464 Board initial_position;
13466 if (appData.debugMode)
13467 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13469 if (!startedFromSetupPosition) {
13471 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13472 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13483 initial_position[i][j++] = CharToPiece(*p);
13486 while (*p == ' ' || *p == '\t' ||
13487 *p == '\n' || *p == '\r') p++;
13489 if (strncmp(p, "black", strlen("black"))==0)
13490 blackPlaysFirst = TRUE;
13492 blackPlaysFirst = FALSE;
13493 startedFromSetupPosition = TRUE;
13495 CopyBoard(boards[0], initial_position);
13496 if (blackPlaysFirst) {
13497 currentMove = forwardMostMove = backwardMostMove = 1;
13498 CopyBoard(boards[1], initial_position);
13499 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13500 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13501 timeRemaining[0][1] = whiteTimeRemaining;
13502 timeRemaining[1][1] = blackTimeRemaining;
13503 if (commentList[0] != NULL) {
13504 commentList[1] = commentList[0];
13505 commentList[0] = NULL;
13508 currentMove = forwardMostMove = backwardMostMove = 0;
13511 yyboardindex = forwardMostMove;
13512 cm = (ChessMove) Myylex();
13515 if(!creatingBook) {
13516 if (first.pr == NoProc) {
13517 StartChessProgram(&first);
13519 InitChessProgram(&first, FALSE);
13520 if(gameInfo.variant == VariantUnknown && *oldName) {
13521 safeStrCpy(engineVariant, oldName, MSG_SIZ);
13522 gameInfo.variant = v;
13524 SendToProgram("force\n", &first);
13525 if (startedFromSetupPosition) {
13526 SendBoard(&first, forwardMostMove);
13527 if (appData.debugMode) {
13528 fprintf(debugFP, "Load Game\n");
13530 DisplayBothClocks();
13534 /* [HGM] server: flag to write setup moves in broadcast file as one */
13535 loadFlag = appData.suppressLoadMoves;
13537 while (cm == Comment) {
13539 if (appData.debugMode)
13540 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13542 AppendComment(currentMove, p, FALSE);
13543 yyboardindex = forwardMostMove;
13544 cm = (ChessMove) Myylex();
13547 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13548 cm == WhiteWins || cm == BlackWins ||
13549 cm == GameIsDrawn || cm == GameUnfinished) {
13550 DisplayMessage("", _("No moves in game"));
13551 if (cmailMsgLoaded) {
13552 if (appData.debugMode)
13553 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13557 DrawPosition(FALSE, boards[currentMove]);
13558 DisplayBothClocks();
13559 gameMode = EditGame;
13566 // [HGM] PV info: routine tests if comment empty
13567 if (!matchMode && (pausing || appData.timeDelay != 0)) {
13568 DisplayComment(currentMove - 1, commentList[currentMove]);
13570 if (!matchMode && appData.timeDelay != 0)
13571 DrawPosition(FALSE, boards[currentMove]);
13573 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13574 programStats.ok_to_send = 1;
13577 /* if the first token after the PGN tags is a move
13578 * and not move number 1, retrieve it from the parser
13580 if (cm != MoveNumberOne)
13581 LoadGameOneMove(cm);
13583 /* load the remaining moves from the file */
13584 while (LoadGameOneMove(EndOfFile)) {
13585 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13586 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13589 /* rewind to the start of the game */
13590 currentMove = backwardMostMove;
13592 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13594 if (oldGameMode == AnalyzeFile) {
13595 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13596 AnalyzeFileEvent();
13598 if (oldGameMode == AnalyzeMode) {
13599 AnalyzeFileEvent();
13602 if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13603 long int w, b; // [HGM] adjourn: restore saved clock times
13604 char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13605 if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13606 timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13607 timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13611 if(creatingBook) return TRUE;
13612 if (!matchMode && pos > 0) {
13613 ToNrEvent(pos); // [HGM] no autoplay if selected on position
13615 if (matchMode || appData.timeDelay == 0) {
13617 } else if (appData.timeDelay > 0) {
13618 AutoPlayGameLoop();
13621 if (appData.debugMode)
13622 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13624 loadFlag = 0; /* [HGM] true game starts */
13628 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13630 ReloadPosition (int offset)
13632 int positionNumber = lastLoadPositionNumber + offset;
13633 if (lastLoadPositionFP == NULL) {
13634 DisplayError(_("No position has been loaded yet"), 0);
13637 if (positionNumber <= 0) {
13638 DisplayError(_("Can't back up any further"), 0);
13641 return LoadPosition(lastLoadPositionFP, positionNumber,
13642 lastLoadPositionTitle);
13645 /* Load the nth position from the given file */
13647 LoadPositionFromFile (char *filename, int n, char *title)
13652 if (strcmp(filename, "-") == 0) {
13653 return LoadPosition(stdin, n, "stdin");
13655 f = fopen(filename, "rb");
13657 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13658 DisplayError(buf, errno);
13661 return LoadPosition(f, n, title);
13666 /* Load the nth position from the given open file, and close it */
13668 LoadPosition (FILE *f, int positionNumber, char *title)
13670 char *p, line[MSG_SIZ];
13671 Board initial_position;
13672 int i, j, fenMode, pn;
13674 if (gameMode == Training )
13675 SetTrainingModeOff();
13677 if (gameMode != BeginningOfGame) {
13678 Reset(FALSE, TRUE);
13680 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13681 fclose(lastLoadPositionFP);
13683 if (positionNumber == 0) positionNumber = 1;
13684 lastLoadPositionFP = f;
13685 lastLoadPositionNumber = positionNumber;
13686 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13687 if (first.pr == NoProc && !appData.noChessProgram) {
13688 StartChessProgram(&first);
13689 InitChessProgram(&first, FALSE);
13691 pn = positionNumber;
13692 if (positionNumber < 0) {
13693 /* Negative position number means to seek to that byte offset */
13694 if (fseek(f, -positionNumber, 0) == -1) {
13695 DisplayError(_("Can't seek on position file"), 0);
13700 if (fseek(f, 0, 0) == -1) {
13701 if (f == lastLoadPositionFP ?
13702 positionNumber == lastLoadPositionNumber + 1 :
13703 positionNumber == 1) {
13706 DisplayError(_("Can't seek on position file"), 0);
13711 /* See if this file is FEN or old-style xboard */
13712 if (fgets(line, MSG_SIZ, f) == NULL) {
13713 DisplayError(_("Position not found in file"), 0);
13716 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13717 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13720 if (fenMode || line[0] == '#') pn--;
13722 /* skip positions before number pn */
13723 if (fgets(line, MSG_SIZ, f) == NULL) {
13725 DisplayError(_("Position not found in file"), 0);
13728 if (fenMode || line[0] == '#') pn--;
13734 if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13735 DisplayError(_("Bad FEN position in file"), 0);
13738 if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13739 sscanf(p+4, "%[^;]", bestMove);
13740 } else *bestMove = NULLCHAR;
13741 if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13742 sscanf(p+4, "%[^;]", avoidMove);
13743 } else *avoidMove = NULLCHAR;
13745 (void) fgets(line, MSG_SIZ, f);
13746 (void) fgets(line, MSG_SIZ, f);
13748 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13749 (void) fgets(line, MSG_SIZ, f);
13750 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13753 initial_position[i][j++] = CharToPiece(*p);
13757 blackPlaysFirst = FALSE;
13759 (void) fgets(line, MSG_SIZ, f);
13760 if (strncmp(line, "black", strlen("black"))==0)
13761 blackPlaysFirst = TRUE;
13764 startedFromSetupPosition = TRUE;
13766 CopyBoard(boards[0], initial_position);
13767 if (blackPlaysFirst) {
13768 currentMove = forwardMostMove = backwardMostMove = 1;
13769 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13770 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13771 CopyBoard(boards[1], initial_position);
13772 DisplayMessage("", _("Black to play"));
13774 currentMove = forwardMostMove = backwardMostMove = 0;
13775 DisplayMessage("", _("White to play"));
13777 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13778 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13779 SendToProgram("force\n", &first);
13780 SendBoard(&first, forwardMostMove);
13782 if (appData.debugMode) {
13784 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13785 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13786 fprintf(debugFP, "Load Position\n");
13789 if (positionNumber > 1) {
13790 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13791 DisplayTitle(line);
13793 DisplayTitle(title);
13795 gameMode = EditGame;
13798 timeRemaining[0][1] = whiteTimeRemaining;
13799 timeRemaining[1][1] = blackTimeRemaining;
13800 DrawPosition(FALSE, boards[currentMove]);
13807 CopyPlayerNameIntoFileName (char **dest, char *src)
13809 while (*src != NULLCHAR && *src != ',') {
13814 *(*dest)++ = *src++;
13820 DefaultFileName (char *ext)
13822 static char def[MSG_SIZ];
13825 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13827 CopyPlayerNameIntoFileName(&p, gameInfo.white);
13829 CopyPlayerNameIntoFileName(&p, gameInfo.black);
13831 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13838 /* Save the current game to the given file */
13840 SaveGameToFile (char *filename, int append)
13844 int result, i, t,tot=0;
13846 if (strcmp(filename, "-") == 0) {
13847 return SaveGame(stdout, 0, NULL);
13849 for(i=0; i<10; i++) { // upto 10 tries
13850 f = fopen(filename, append ? "a" : "w");
13851 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13852 if(f || errno != 13) break;
13853 DoSleep(t = 5 + random()%11); // wait 5-15 msec
13857 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13858 DisplayError(buf, errno);
13861 safeStrCpy(buf, lastMsg, MSG_SIZ);
13862 DisplayMessage(_("Waiting for access to save file"), "");
13863 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13864 DisplayMessage(_("Saving game"), "");
13865 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
13866 result = SaveGame(f, 0, NULL);
13867 DisplayMessage(buf, "");
13874 SavePart (char *str)
13876 static char buf[MSG_SIZ];
13879 p = strchr(str, ' ');
13880 if (p == NULL) return str;
13881 strncpy(buf, str, p - str);
13882 buf[p - str] = NULLCHAR;
13886 #define PGN_MAX_LINE 75
13888 #define PGN_SIDE_WHITE 0
13889 #define PGN_SIDE_BLACK 1
13892 FindFirstMoveOutOfBook (int side)
13896 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13897 int index = backwardMostMove;
13898 int has_book_hit = 0;
13900 if( (index % 2) != side ) {
13904 while( index < forwardMostMove ) {
13905 /* Check to see if engine is in book */
13906 int depth = pvInfoList[index].depth;
13907 int score = pvInfoList[index].score;
13913 else if( score == 0 && depth == 63 ) {
13914 in_book = 1; /* Zappa */
13916 else if( score == 2 && depth == 99 ) {
13917 in_book = 1; /* Abrok */
13920 has_book_hit += in_book;
13936 GetOutOfBookInfo (char * buf)
13940 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13942 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13943 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13947 if( oob[0] >= 0 || oob[1] >= 0 ) {
13948 for( i=0; i<2; i++ ) {
13952 if( i > 0 && oob[0] >= 0 ) {
13953 strcat( buf, " " );
13956 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13957 sprintf( buf+strlen(buf), "%s%.2f",
13958 pvInfoList[idx].score >= 0 ? "+" : "",
13959 pvInfoList[idx].score / 100.0 );
13965 /* Save game in PGN style */
13967 SaveGamePGN2 (FILE *f)
13969 int i, offset, linelen, newblock;
13972 int movelen, numlen, blank;
13973 char move_buffer[100]; /* [AS] Buffer for move+PV info */
13975 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13977 PrintPGNTags(f, &gameInfo);
13979 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13981 if (backwardMostMove > 0 || startedFromSetupPosition) {
13982 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13983 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13984 fprintf(f, "\n{--------------\n");
13985 PrintPosition(f, backwardMostMove);
13986 fprintf(f, "--------------}\n");
13990 /* [AS] Out of book annotation */
13991 if( appData.saveOutOfBookInfo ) {
13994 GetOutOfBookInfo( buf );
13996 if( buf[0] != '\0' ) {
13997 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
14004 i = backwardMostMove;
14008 while (i < forwardMostMove) {
14009 /* Print comments preceding this move */
14010 if (commentList[i] != NULL) {
14011 if (linelen > 0) fprintf(f, "\n");
14012 fprintf(f, "%s", commentList[i]);
14017 /* Format move number */
14019 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
14022 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
14024 numtext[0] = NULLCHAR;
14026 numlen = strlen(numtext);
14029 /* Print move number */
14030 blank = linelen > 0 && numlen > 0;
14031 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
14040 fprintf(f, "%s", numtext);
14044 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
14045 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
14048 blank = linelen > 0 && movelen > 0;
14049 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14058 fprintf(f, "%s", move_buffer);
14059 linelen += movelen;
14061 /* [AS] Add PV info if present */
14062 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
14063 /* [HGM] add time */
14064 char buf[MSG_SIZ]; int seconds;
14066 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
14072 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
14075 seconds = (seconds + 4)/10; // round to full seconds
14077 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
14079 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
14082 if(appData.cumulativeTimePGN) {
14083 snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
14086 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
14087 pvInfoList[i].score >= 0 ? "+" : "",
14088 pvInfoList[i].score / 100.0,
14089 pvInfoList[i].depth,
14092 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
14094 /* Print score/depth */
14095 blank = linelen > 0 && movelen > 0;
14096 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14105 fprintf(f, "%s", move_buffer);
14106 linelen += movelen;
14112 /* Start a new line */
14113 if (linelen > 0) fprintf(f, "\n");
14115 /* Print comments after last move */
14116 if (commentList[i] != NULL) {
14117 fprintf(f, "%s\n", commentList[i]);
14121 if (gameInfo.resultDetails != NULL &&
14122 gameInfo.resultDetails[0] != NULLCHAR) {
14123 char buf[MSG_SIZ], *p = gameInfo.resultDetails;
14124 if(gameInfo.result == GameUnfinished && appData.clockMode &&
14125 (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
14126 snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
14127 fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
14129 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14133 /* Save game in PGN style and close the file */
14135 SaveGamePGN (FILE *f)
14139 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14143 /* Save game in old style and close the file */
14145 SaveGameOldStyle (FILE *f)
14150 tm = time((time_t *) NULL);
14152 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
14155 if (backwardMostMove > 0 || startedFromSetupPosition) {
14156 fprintf(f, "\n[--------------\n");
14157 PrintPosition(f, backwardMostMove);
14158 fprintf(f, "--------------]\n");
14163 i = backwardMostMove;
14164 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14166 while (i < forwardMostMove) {
14167 if (commentList[i] != NULL) {
14168 fprintf(f, "[%s]\n", commentList[i]);
14171 if ((i % 2) == 1) {
14172 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
14175 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
14177 if (commentList[i] != NULL) {
14181 if (i >= forwardMostMove) {
14185 fprintf(f, "%s\n", parseList[i]);
14190 if (commentList[i] != NULL) {
14191 fprintf(f, "[%s]\n", commentList[i]);
14194 /* This isn't really the old style, but it's close enough */
14195 if (gameInfo.resultDetails != NULL &&
14196 gameInfo.resultDetails[0] != NULLCHAR) {
14197 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14198 gameInfo.resultDetails);
14200 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14207 /* Save the current game to open file f and close the file */
14209 SaveGame (FILE *f, int dummy, char *dummy2)
14211 if (gameMode == EditPosition) EditPositionDone(TRUE);
14212 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14213 if (appData.oldSaveStyle)
14214 return SaveGameOldStyle(f);
14216 return SaveGamePGN(f);
14219 /* Save the current position to the given file */
14221 SavePositionToFile (char *filename)
14226 if (strcmp(filename, "-") == 0) {
14227 return SavePosition(stdout, 0, NULL);
14229 f = fopen(filename, "a");
14231 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14232 DisplayError(buf, errno);
14235 safeStrCpy(buf, lastMsg, MSG_SIZ);
14236 DisplayMessage(_("Waiting for access to save file"), "");
14237 flock(fileno(f), LOCK_EX); // [HGM] lock
14238 DisplayMessage(_("Saving position"), "");
14239 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
14240 SavePosition(f, 0, NULL);
14241 DisplayMessage(buf, "");
14247 /* Save the current position to the given open file and close the file */
14249 SavePosition (FILE *f, int dummy, char *dummy2)
14254 if (gameMode == EditPosition) EditPositionDone(TRUE);
14255 if (appData.oldSaveStyle) {
14256 tm = time((time_t *) NULL);
14258 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14260 fprintf(f, "[--------------\n");
14261 PrintPosition(f, currentMove);
14262 fprintf(f, "--------------]\n");
14264 fen = PositionToFEN(currentMove, NULL, 1);
14265 fprintf(f, "%s\n", fen);
14273 ReloadCmailMsgEvent (int unregister)
14276 static char *inFilename = NULL;
14277 static char *outFilename;
14279 struct stat inbuf, outbuf;
14282 /* Any registered moves are unregistered if unregister is set, */
14283 /* i.e. invoked by the signal handler */
14285 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14286 cmailMoveRegistered[i] = FALSE;
14287 if (cmailCommentList[i] != NULL) {
14288 free(cmailCommentList[i]);
14289 cmailCommentList[i] = NULL;
14292 nCmailMovesRegistered = 0;
14295 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14296 cmailResult[i] = CMAIL_NOT_RESULT;
14300 if (inFilename == NULL) {
14301 /* Because the filenames are static they only get malloced once */
14302 /* and they never get freed */
14303 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14304 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14306 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14307 sprintf(outFilename, "%s.out", appData.cmailGameName);
14310 status = stat(outFilename, &outbuf);
14312 cmailMailedMove = FALSE;
14314 status = stat(inFilename, &inbuf);
14315 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14318 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14319 counts the games, notes how each one terminated, etc.
14321 It would be nice to remove this kludge and instead gather all
14322 the information while building the game list. (And to keep it
14323 in the game list nodes instead of having a bunch of fixed-size
14324 parallel arrays.) Note this will require getting each game's
14325 termination from the PGN tags, as the game list builder does
14326 not process the game moves. --mann
14328 cmailMsgLoaded = TRUE;
14329 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14331 /* Load first game in the file or popup game menu */
14332 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14334 #endif /* !WIN32 */
14342 char string[MSG_SIZ];
14344 if ( cmailMailedMove
14345 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14346 return TRUE; /* Allow free viewing */
14349 /* Unregister move to ensure that we don't leave RegisterMove */
14350 /* with the move registered when the conditions for registering no */
14352 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14353 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14354 nCmailMovesRegistered --;
14356 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14358 free(cmailCommentList[lastLoadGameNumber - 1]);
14359 cmailCommentList[lastLoadGameNumber - 1] = NULL;
14363 if (cmailOldMove == -1) {
14364 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14368 if (currentMove > cmailOldMove + 1) {
14369 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14373 if (currentMove < cmailOldMove) {
14374 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14378 if (forwardMostMove > currentMove) {
14379 /* Silently truncate extra moves */
14383 if ( (currentMove == cmailOldMove + 1)
14384 || ( (currentMove == cmailOldMove)
14385 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14386 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14387 if (gameInfo.result != GameUnfinished) {
14388 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14391 if (commentList[currentMove] != NULL) {
14392 cmailCommentList[lastLoadGameNumber - 1]
14393 = StrSave(commentList[currentMove]);
14395 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14397 if (appData.debugMode)
14398 fprintf(debugFP, "Saving %s for game %d\n",
14399 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14401 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14403 f = fopen(string, "w");
14404 if (appData.oldSaveStyle) {
14405 SaveGameOldStyle(f); /* also closes the file */
14407 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14408 f = fopen(string, "w");
14409 SavePosition(f, 0, NULL); /* also closes the file */
14411 fprintf(f, "{--------------\n");
14412 PrintPosition(f, currentMove);
14413 fprintf(f, "--------------}\n\n");
14415 SaveGame(f, 0, NULL); /* also closes the file*/
14418 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14419 nCmailMovesRegistered ++;
14420 } else if (nCmailGames == 1) {
14421 DisplayError(_("You have not made a move yet"), 0);
14432 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14433 FILE *commandOutput;
14434 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14435 int nBytes = 0; /* Suppress warnings on uninitialized variables */
14441 if (! cmailMsgLoaded) {
14442 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14446 if (nCmailGames == nCmailResults) {
14447 DisplayError(_("No unfinished games"), 0);
14451 #if CMAIL_PROHIBIT_REMAIL
14452 if (cmailMailedMove) {
14453 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);
14454 DisplayError(msg, 0);
14459 if (! (cmailMailedMove || RegisterMove())) return;
14461 if ( cmailMailedMove
14462 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14463 snprintf(string, MSG_SIZ, partCommandString,
14464 appData.debugMode ? " -v" : "", appData.cmailGameName);
14465 commandOutput = popen(string, "r");
14467 if (commandOutput == NULL) {
14468 DisplayError(_("Failed to invoke cmail"), 0);
14470 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14471 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14473 if (nBuffers > 1) {
14474 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14475 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14476 nBytes = MSG_SIZ - 1;
14478 (void) memcpy(msg, buffer, nBytes);
14480 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14482 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14483 cmailMailedMove = TRUE; /* Prevent >1 moves */
14486 for (i = 0; i < nCmailGames; i ++) {
14487 if (cmailResult[i] == CMAIL_NOT_RESULT) {
14492 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14494 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14496 appData.cmailGameName,
14498 LoadGameFromFile(buffer, 1, buffer, FALSE);
14499 cmailMsgLoaded = FALSE;
14503 DisplayInformation(msg);
14504 pclose(commandOutput);
14507 if ((*cmailMsg) != '\0') {
14508 DisplayInformation(cmailMsg);
14513 #endif /* !WIN32 */
14522 int prependComma = 0;
14524 char string[MSG_SIZ]; /* Space for game-list */
14527 if (!cmailMsgLoaded) return "";
14529 if (cmailMailedMove) {
14530 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14532 /* Create a list of games left */
14533 snprintf(string, MSG_SIZ, "[");
14534 for (i = 0; i < nCmailGames; i ++) {
14535 if (! ( cmailMoveRegistered[i]
14536 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14537 if (prependComma) {
14538 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14540 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14544 strcat(string, number);
14547 strcat(string, "]");
14549 if (nCmailMovesRegistered + nCmailResults == 0) {
14550 switch (nCmailGames) {
14552 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14556 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14560 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14565 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14567 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14572 if (nCmailResults == nCmailGames) {
14573 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14575 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14580 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14592 if (gameMode == Training)
14593 SetTrainingModeOff();
14596 cmailMsgLoaded = FALSE;
14597 if (appData.icsActive) {
14598 SendToICS(ics_prefix);
14599 SendToICS("refresh\n");
14604 ExitEvent (int status)
14608 /* Give up on clean exit */
14612 /* Keep trying for clean exit */
14616 if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14617 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14619 if (telnetISR != NULL) {
14620 RemoveInputSource(telnetISR);
14622 if (icsPR != NoProc) {
14623 DestroyChildProcess(icsPR, TRUE);
14626 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14627 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14629 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14630 /* make sure this other one finishes before killing it! */
14631 if(endingGame) { int count = 0;
14632 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14633 while(endingGame && count++ < 10) DoSleep(1);
14634 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14637 /* Kill off chess programs */
14638 if (first.pr != NoProc) {
14641 DoSleep( appData.delayBeforeQuit );
14642 SendToProgram("quit\n", &first);
14643 DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14645 if (second.pr != NoProc) {
14646 DoSleep( appData.delayBeforeQuit );
14647 SendToProgram("quit\n", &second);
14648 DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14650 if (first.isr != NULL) {
14651 RemoveInputSource(first.isr);
14653 if (second.isr != NULL) {
14654 RemoveInputSource(second.isr);
14657 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14658 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14660 ShutDownFrontEnd();
14665 PauseEngine (ChessProgramState *cps)
14667 SendToProgram("pause\n", cps);
14672 UnPauseEngine (ChessProgramState *cps)
14674 SendToProgram("resume\n", cps);
14681 if (appData.debugMode)
14682 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14686 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14688 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14689 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14690 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14692 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14693 HandleMachineMove(stashedInputMove, stalledEngine);
14694 stalledEngine = NULL;
14697 if (gameMode == MachinePlaysWhite ||
14698 gameMode == TwoMachinesPlay ||
14699 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14700 if(first.pause) UnPauseEngine(&first);
14701 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14702 if(second.pause) UnPauseEngine(&second);
14703 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14706 DisplayBothClocks();
14708 if (gameMode == PlayFromGameFile) {
14709 if (appData.timeDelay >= 0)
14710 AutoPlayGameLoop();
14711 } else if (gameMode == IcsExamining && pauseExamInvalid) {
14712 Reset(FALSE, TRUE);
14713 SendToICS(ics_prefix);
14714 SendToICS("refresh\n");
14715 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14716 ForwardInner(forwardMostMove);
14718 pauseExamInvalid = FALSE;
14720 switch (gameMode) {
14724 pauseExamForwardMostMove = forwardMostMove;
14725 pauseExamInvalid = FALSE;
14728 case IcsPlayingWhite:
14729 case IcsPlayingBlack:
14733 case PlayFromGameFile:
14734 (void) StopLoadGameTimer();
14738 case BeginningOfGame:
14739 if (appData.icsActive) return;
14740 /* else fall through */
14741 case MachinePlaysWhite:
14742 case MachinePlaysBlack:
14743 case TwoMachinesPlay:
14744 if (forwardMostMove == 0)
14745 return; /* don't pause if no one has moved */
14746 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14747 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14748 if(onMove->pause) { // thinking engine can be paused
14749 PauseEngine(onMove); // do it
14750 if(onMove->other->pause) // pondering opponent can always be paused immediately
14751 PauseEngine(onMove->other);
14753 SendToProgram("easy\n", onMove->other);
14755 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14756 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14758 PauseEngine(&first);
14760 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14761 } else { // human on move, pause pondering by either method
14763 PauseEngine(&first);
14764 else if(appData.ponderNextMove)
14765 SendToProgram("easy\n", &first);
14768 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14778 EditCommentEvent ()
14780 char title[MSG_SIZ];
14782 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14783 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14785 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14786 WhiteOnMove(currentMove - 1) ? " " : ".. ",
14787 parseList[currentMove - 1]);
14790 EditCommentPopUp(currentMove, title, commentList[currentMove]);
14797 char *tags = PGNTags(&gameInfo);
14799 EditTagsPopUp(tags, NULL);
14806 if(WaitForEngine(&second, StartSecond)) return;
14807 InitChessProgram(&second, FALSE);
14808 FeedMovesToProgram(&second, currentMove);
14810 SendToProgram("analyze\n", &second);
14811 second.analyzing = TRUE;
14818 if(second.analyzing) {
14819 SendToProgram("exit\n", &second);
14820 second.analyzing = FALSE;
14826 /* Toggle ShowThinking */
14828 ToggleShowThinking()
14830 appData.showThinking = !appData.showThinking;
14831 ShowThinkingEvent();
14835 AnalyzeModeEvent ()
14839 if (!first.analysisSupport) {
14840 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14841 DisplayError(buf, 0);
14844 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14845 if (appData.icsActive) {
14846 if (gameMode != IcsObserving) {
14847 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14848 DisplayError(buf, 0);
14850 if (appData.icsEngineAnalyze) {
14851 if (appData.debugMode)
14852 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14858 /* if enable, user wants to disable icsEngineAnalyze */
14859 if (appData.icsEngineAnalyze) {
14864 appData.icsEngineAnalyze = TRUE;
14865 if (appData.debugMode)
14866 fprintf(debugFP, "ICS engine analyze starting... \n");
14869 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14870 if (appData.noChessProgram || gameMode == AnalyzeMode)
14873 if (gameMode != AnalyzeFile) {
14874 if (!appData.icsEngineAnalyze) {
14876 if (gameMode != EditGame) return 0;
14878 if (!appData.showThinking) ToggleShowThinking();
14879 ResurrectChessProgram();
14880 SendToProgram("analyze\n", &first);
14881 first.analyzing = TRUE;
14882 /*first.maybeThinking = TRUE;*/
14883 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14884 EngineOutputPopUp();
14886 if (!appData.icsEngineAnalyze) {
14887 gameMode = AnalyzeMode;
14888 ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14894 StartAnalysisClock();
14895 GetTimeMark(&lastNodeCountTime);
14901 AnalyzeFileEvent ()
14903 if (appData.noChessProgram || gameMode == AnalyzeFile)
14906 if (!first.analysisSupport) {
14908 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14909 DisplayError(buf, 0);
14913 if (gameMode != AnalyzeMode) {
14914 keepInfo = 1; // mere annotating should not alter PGN tags
14917 if (gameMode != EditGame) return;
14918 if (!appData.showThinking) ToggleShowThinking();
14919 ResurrectChessProgram();
14920 SendToProgram("analyze\n", &first);
14921 first.analyzing = TRUE;
14922 /*first.maybeThinking = TRUE;*/
14923 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14924 EngineOutputPopUp();
14926 gameMode = AnalyzeFile;
14930 StartAnalysisClock();
14931 GetTimeMark(&lastNodeCountTime);
14933 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14934 AnalysisPeriodicEvent(1);
14938 MachineWhiteEvent ()
14941 char *bookHit = NULL;
14943 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14947 if (gameMode == PlayFromGameFile ||
14948 gameMode == TwoMachinesPlay ||
14949 gameMode == Training ||
14950 gameMode == AnalyzeMode ||
14951 gameMode == EndOfGame)
14954 if (gameMode == EditPosition)
14955 EditPositionDone(TRUE);
14957 if (!WhiteOnMove(currentMove)) {
14958 DisplayError(_("It is not White's turn"), 0);
14962 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14965 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14966 gameMode == AnalyzeFile)
14969 ResurrectChessProgram(); /* in case it isn't running */
14970 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14971 gameMode = MachinePlaysWhite;
14974 gameMode = MachinePlaysWhite;
14978 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14980 if (first.sendName) {
14981 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14982 SendToProgram(buf, &first);
14984 if (first.sendTime) {
14985 if (first.useColors) {
14986 SendToProgram("black\n", &first); /*gnu kludge*/
14988 SendTimeRemaining(&first, TRUE);
14990 if (first.useColors) {
14991 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14993 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14994 SetMachineThinkingEnables();
14995 first.maybeThinking = TRUE;
14999 if (appData.autoFlipView && !flipView) {
15000 flipView = !flipView;
15001 DrawPosition(FALSE, NULL);
15002 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
15005 if(bookHit) { // [HGM] book: simulate book reply
15006 static char bookMove[MSG_SIZ]; // a bit generous?
15008 programStats.nodes = programStats.depth = programStats.time =
15009 programStats.score = programStats.got_only_move = 0;
15010 sprintf(programStats.movelist, "%s (xbook)", bookHit);
15012 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15013 strcat(bookMove, bookHit);
15014 savedMessage = bookMove; // args for deferred call
15015 savedState = &first;
15016 ScheduleDelayedEvent(DeferredBookMove, 1);
15021 MachineBlackEvent ()
15024 char *bookHit = NULL;
15026 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
15030 if (gameMode == PlayFromGameFile ||
15031 gameMode == TwoMachinesPlay ||
15032 gameMode == Training ||
15033 gameMode == AnalyzeMode ||
15034 gameMode == EndOfGame)
15037 if (gameMode == EditPosition)
15038 EditPositionDone(TRUE);
15040 if (WhiteOnMove(currentMove)) {
15041 DisplayError(_("It is not Black's turn"), 0);
15045 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
15048 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15049 gameMode == AnalyzeFile)
15052 ResurrectChessProgram(); /* in case it isn't running */
15053 gameMode = MachinePlaysBlack;
15057 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15059 if (first.sendName) {
15060 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
15061 SendToProgram(buf, &first);
15063 if (first.sendTime) {
15064 if (first.useColors) {
15065 SendToProgram("white\n", &first); /*gnu kludge*/
15067 SendTimeRemaining(&first, FALSE);
15069 if (first.useColors) {
15070 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
15072 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
15073 SetMachineThinkingEnables();
15074 first.maybeThinking = TRUE;
15077 if (appData.autoFlipView && flipView) {
15078 flipView = !flipView;
15079 DrawPosition(FALSE, NULL);
15080 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
15082 if(bookHit) { // [HGM] book: simulate book reply
15083 static char bookMove[MSG_SIZ]; // a bit generous?
15085 programStats.nodes = programStats.depth = programStats.time =
15086 programStats.score = programStats.got_only_move = 0;
15087 sprintf(programStats.movelist, "%s (xbook)", bookHit);
15089 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15090 strcat(bookMove, bookHit);
15091 savedMessage = bookMove; // args for deferred call
15092 savedState = &first;
15093 ScheduleDelayedEvent(DeferredBookMove, 1);
15099 DisplayTwoMachinesTitle ()
15102 if (appData.matchGames > 0) {
15103 if(appData.tourneyFile[0]) {
15104 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
15105 gameInfo.white, _("vs."), gameInfo.black,
15106 nextGame+1, appData.matchGames+1,
15107 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
15109 if (first.twoMachinesColor[0] == 'w') {
15110 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15111 gameInfo.white, _("vs."), gameInfo.black,
15112 first.matchWins, second.matchWins,
15113 matchGame - 1 - (first.matchWins + second.matchWins));
15115 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15116 gameInfo.white, _("vs."), gameInfo.black,
15117 second.matchWins, first.matchWins,
15118 matchGame - 1 - (first.matchWins + second.matchWins));
15121 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15127 SettingsMenuIfReady ()
15129 if (second.lastPing != second.lastPong) {
15130 DisplayMessage("", _("Waiting for second chess program"));
15131 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
15135 DisplayMessage("", "");
15136 SettingsPopUp(&second);
15140 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
15143 if (cps->pr == NoProc) {
15144 StartChessProgram(cps);
15145 if (cps->protocolVersion == 1) {
15147 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
15149 /* kludge: allow timeout for initial "feature" command */
15150 if(retry != TwoMachinesEventIfReady) FreezeUI();
15151 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
15152 DisplayMessage("", buf);
15153 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
15161 TwoMachinesEvent P((void))
15163 int i, move = forwardMostMove;
15165 ChessProgramState *onmove;
15166 char *bookHit = NULL;
15167 static int stalling = 0;
15171 if (appData.noChessProgram) return;
15173 switch (gameMode) {
15174 case TwoMachinesPlay:
15176 case MachinePlaysWhite:
15177 case MachinePlaysBlack:
15178 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15179 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15183 case BeginningOfGame:
15184 case PlayFromGameFile:
15187 if (gameMode != EditGame) return;
15190 EditPositionDone(TRUE);
15201 // forwardMostMove = currentMove;
15202 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15203 startingEngine = TRUE;
15205 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15207 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15208 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15209 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15213 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15215 if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15216 gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15217 startingEngine = matchMode = FALSE;
15218 DisplayError("second engine does not play this", 0);
15219 gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15220 EditGameEvent(); // switch back to EditGame mode
15225 InitChessProgram(&second, FALSE); // unbalances ping of second engine
15226 SendToProgram("force\n", &second);
15228 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15232 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15233 if(appData.matchPause>10000 || appData.matchPause<10)
15234 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15235 wait = SubtractTimeMarks(&now, &pauseStart);
15236 if(wait < appData.matchPause) {
15237 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15240 // we are now committed to starting the game
15242 DisplayMessage("", "");
15244 if (startedFromSetupPosition) {
15245 SendBoard(&second, backwardMostMove);
15246 if (appData.debugMode) {
15247 fprintf(debugFP, "Two Machines\n");
15250 for (i = backwardMostMove; i < forwardMostMove; i++) {
15251 SendMoveToProgram(i, &second);
15255 gameMode = TwoMachinesPlay;
15256 pausing = startingEngine = FALSE;
15257 ModeHighlight(); // [HGM] logo: this triggers display update of logos
15259 DisplayTwoMachinesTitle();
15261 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15266 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15267 SendToProgram(first.computerString, &first);
15268 if (first.sendName) {
15269 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15270 SendToProgram(buf, &first);
15273 SendToProgram(second.computerString, &second);
15274 if (second.sendName) {
15275 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15276 SendToProgram(buf, &second);
15280 if (!first.sendTime || !second.sendTime || move == 0) { // [HGM] first engine changed sides from Reset, so recalc time odds
15282 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15283 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15285 if (onmove->sendTime) {
15286 if (onmove->useColors) {
15287 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15289 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15291 if (onmove->useColors) {
15292 SendToProgram(onmove->twoMachinesColor, onmove);
15294 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15295 // SendToProgram("go\n", onmove);
15296 onmove->maybeThinking = TRUE;
15297 SetMachineThinkingEnables();
15301 if(bookHit) { // [HGM] book: simulate book reply
15302 static char bookMove[MSG_SIZ]; // a bit generous?
15304 programStats.nodes = programStats.depth = programStats.time =
15305 programStats.score = programStats.got_only_move = 0;
15306 sprintf(programStats.movelist, "%s (xbook)", bookHit);
15308 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15309 strcat(bookMove, bookHit);
15310 savedMessage = bookMove; // args for deferred call
15311 savedState = onmove;
15312 ScheduleDelayedEvent(DeferredBookMove, 1);
15319 if (gameMode == Training) {
15320 SetTrainingModeOff();
15321 gameMode = PlayFromGameFile;
15322 DisplayMessage("", _("Training mode off"));
15324 gameMode = Training;
15325 animateTraining = appData.animate;
15327 /* make sure we are not already at the end of the game */
15328 if (currentMove < forwardMostMove) {
15329 SetTrainingModeOn();
15330 DisplayMessage("", _("Training mode on"));
15332 gameMode = PlayFromGameFile;
15333 DisplayError(_("Already at end of game"), 0);
15342 if (!appData.icsActive) return;
15343 switch (gameMode) {
15344 case IcsPlayingWhite:
15345 case IcsPlayingBlack:
15348 case BeginningOfGame:
15356 EditPositionDone(TRUE);
15369 gameMode = IcsIdle;
15379 switch (gameMode) {
15381 SetTrainingModeOff();
15383 case MachinePlaysWhite:
15384 case MachinePlaysBlack:
15385 case BeginningOfGame:
15386 SendToProgram("force\n", &first);
15387 if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15388 if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15390 abortEngineThink = TRUE;
15391 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15392 SendToProgram(buf, &first);
15393 DisplayMessage("Aborting engine think", "");
15397 SetUserThinkingEnables();
15399 case PlayFromGameFile:
15400 (void) StopLoadGameTimer();
15401 if (gameFileFP != NULL) {
15406 EditPositionDone(TRUE);
15411 SendToProgram("force\n", &first);
15413 case TwoMachinesPlay:
15414 GameEnds(EndOfFile, NULL, GE_PLAYER);
15415 ResurrectChessProgram();
15416 SetUserThinkingEnables();
15419 ResurrectChessProgram();
15421 case IcsPlayingBlack:
15422 case IcsPlayingWhite:
15423 DisplayError(_("Warning: You are still playing a game"), 0);
15426 DisplayError(_("Warning: You are still observing a game"), 0);
15429 DisplayError(_("Warning: You are still examining a game"), 0);
15440 first.offeredDraw = second.offeredDraw = 0;
15442 if (gameMode == PlayFromGameFile) {
15443 whiteTimeRemaining = timeRemaining[0][currentMove];
15444 blackTimeRemaining = timeRemaining[1][currentMove];
15448 if (gameMode == MachinePlaysWhite ||
15449 gameMode == MachinePlaysBlack ||
15450 gameMode == TwoMachinesPlay ||
15451 gameMode == EndOfGame) {
15452 i = forwardMostMove;
15453 while (i > currentMove) {
15454 SendToProgram("undo\n", &first);
15457 if(!adjustedClock) {
15458 whiteTimeRemaining = timeRemaining[0][currentMove];
15459 blackTimeRemaining = timeRemaining[1][currentMove];
15460 DisplayBothClocks();
15462 if (whiteFlag || blackFlag) {
15463 whiteFlag = blackFlag = 0;
15468 gameMode = EditGame;
15474 EditPositionEvent ()
15477 if (gameMode == EditPosition) {
15483 if (gameMode != EditGame) return;
15485 gameMode = EditPosition;
15488 CopyBoard(rightsBoard, nullBoard);
15489 if (currentMove > 0)
15490 CopyBoard(boards[0], boards[currentMove]);
15491 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15492 rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15494 blackPlaysFirst = !WhiteOnMove(currentMove);
15496 currentMove = forwardMostMove = backwardMostMove = 0;
15497 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15499 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15505 /* [DM] icsEngineAnalyze - possible call from other functions */
15506 if (appData.icsEngineAnalyze) {
15507 appData.icsEngineAnalyze = FALSE;
15509 DisplayMessage("",_("Close ICS engine analyze..."));
15511 if (first.analysisSupport && first.analyzing) {
15512 SendToBoth("exit\n");
15513 first.analyzing = second.analyzing = FALSE;
15515 thinkOutput[0] = NULLCHAR;
15519 EditPositionDone (Boolean fakeRights)
15521 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15523 startedFromSetupPosition = TRUE;
15524 InitChessProgram(&first, FALSE);
15525 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15527 boards[0][EP_STATUS] = EP_NONE;
15528 for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
15529 for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
15530 if(rightsBoard[r][f]) {
15531 ChessSquare p = boards[0][r][f];
15532 if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
15533 else if(p == king) boards[0][CASTLING][2] = f;
15534 else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
15535 else rightsBoard[r][f] = 2; // mark for second pass
15538 for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
15539 if(rightsBoard[r][f] == 2) {
15540 ChessSquare p = boards[0][r][f];
15541 if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
15542 if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
15546 SendToProgram("force\n", &first);
15547 if (blackPlaysFirst) {
15548 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15549 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15550 currentMove = forwardMostMove = backwardMostMove = 1;
15551 CopyBoard(boards[1], boards[0]);
15553 currentMove = forwardMostMove = backwardMostMove = 0;
15555 SendBoard(&first, forwardMostMove);
15556 if (appData.debugMode) {
15557 fprintf(debugFP, "EditPosDone\n");
15560 DisplayMessage("", "");
15561 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15562 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15563 gameMode = EditGame;
15565 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15566 ClearHighlights(); /* [AS] */
15569 /* Pause for `ms' milliseconds */
15570 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15572 TimeDelay (long ms)
15579 } while (SubtractTimeMarks(&m2, &m1) < ms);
15582 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15584 SendMultiLineToICS (char *buf)
15586 char temp[MSG_SIZ+1], *p;
15593 strncpy(temp, buf, len);
15598 if (*p == '\n' || *p == '\r')
15603 strcat(temp, "\n");
15605 SendToPlayer(temp, strlen(temp));
15609 SetWhiteToPlayEvent ()
15611 if (gameMode == EditPosition) {
15612 blackPlaysFirst = FALSE;
15613 DisplayBothClocks(); /* works because currentMove is 0 */
15614 } else if (gameMode == IcsExamining) {
15615 SendToICS(ics_prefix);
15616 SendToICS("tomove white\n");
15621 SetBlackToPlayEvent ()
15623 if (gameMode == EditPosition) {
15624 blackPlaysFirst = TRUE;
15625 currentMove = 1; /* kludge */
15626 DisplayBothClocks();
15628 } else if (gameMode == IcsExamining) {
15629 SendToICS(ics_prefix);
15630 SendToICS("tomove black\n");
15635 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15638 ChessSquare piece = boards[0][y][x];
15639 static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15640 static int lastVariant;
15641 int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15643 if (gameMode != EditPosition && gameMode != IcsExamining) return;
15645 switch (selection) {
15647 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15648 MarkTargetSquares(1);
15649 CopyBoard(currentBoard, boards[0]);
15650 CopyBoard(menuBoard, initialPosition);
15651 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15652 SendToICS(ics_prefix);
15653 SendToICS("bsetup clear\n");
15654 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15655 SendToICS(ics_prefix);
15656 SendToICS("clearboard\n");
15659 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15660 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15661 for (y = 0; y < BOARD_HEIGHT; y++) {
15662 if (gameMode == IcsExamining) {
15663 if (boards[currentMove][y][x] != EmptySquare) {
15664 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15668 } else if(boards[0][y][x] != DarkSquare) {
15669 if(boards[0][y][x] != p) nonEmpty++;
15670 boards[0][y][x] = p;
15674 CopyBoard(rightsBoard, nullBoard);
15675 if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15677 for(r = 0; r < BOARD_HEIGHT; r++) {
15678 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
15679 ChessSquare p = menuBoard[r][x];
15680 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15683 menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
15684 DisplayMessage("Clicking clock again restores position", "");
15685 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15686 if(!nonEmpty) { // asked to clear an empty board
15687 CopyBoard(boards[0], menuBoard);
15689 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15690 CopyBoard(boards[0], initialPosition);
15692 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15693 && !CompareBoards(nullBoard, erasedBoard)) {
15694 CopyBoard(boards[0], erasedBoard);
15696 CopyBoard(erasedBoard, currentBoard);
15698 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15699 rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15702 if (gameMode == EditPosition) {
15703 DrawPosition(FALSE, boards[0]);
15708 SetWhiteToPlayEvent();
15712 SetBlackToPlayEvent();
15716 if (gameMode == IcsExamining) {
15717 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15718 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15721 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15722 if(x == BOARD_LEFT-2) {
15723 if(y < handSize-1-gameInfo.holdingsSize) break;
15724 boards[0][y][1] = 0;
15726 if(x == BOARD_RGHT+1) {
15727 if(y >= gameInfo.holdingsSize) break;
15728 boards[0][y][BOARD_WIDTH-2] = 0;
15731 boards[0][y][x] = EmptySquare;
15732 DrawPosition(FALSE, boards[0]);
15737 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15738 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
15739 selection = (ChessSquare) (PROMOTED(piece));
15740 } else if(piece == EmptySquare) selection = WhiteSilver;
15741 else selection = (ChessSquare)((int)piece - 1);
15745 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15746 piece > (int)BlackMan && piece <= (int)BlackKing ) {
15747 selection = (ChessSquare) (DEMOTED(piece));
15748 } else if(piece == EmptySquare) selection = BlackSilver;
15749 else selection = (ChessSquare)((int)piece + 1);
15754 if(gameInfo.variant == VariantShatranj ||
15755 gameInfo.variant == VariantXiangqi ||
15756 gameInfo.variant == VariantCourier ||
15757 gameInfo.variant == VariantASEAN ||
15758 gameInfo.variant == VariantMakruk )
15759 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15765 if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15766 if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15772 if(gameInfo.variant == VariantXiangqi)
15773 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15774 if(gameInfo.variant == VariantKnightmate)
15775 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15776 if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15779 if (gameMode == IcsExamining) {
15780 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15781 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15782 PieceToChar(selection), AAA + x, ONE + y);
15785 rightsBoard[y][x] = hasRights;
15786 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15788 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15789 n = PieceToNumber(selection - BlackPawn);
15790 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15791 boards[0][handSize-1-n][0] = selection;
15792 boards[0][handSize-1-n][1]++;
15794 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15795 n = PieceToNumber(selection);
15796 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15797 boards[0][n][BOARD_WIDTH-1] = selection;
15798 boards[0][n][BOARD_WIDTH-2]++;
15801 boards[0][y][x] = selection;
15802 DrawPosition(TRUE, boards[0]);
15804 fromX = fromY = -1;
15812 DropMenuEvent (ChessSquare selection, int x, int y)
15814 ChessMove moveType;
15816 switch (gameMode) {
15817 case IcsPlayingWhite:
15818 case MachinePlaysBlack:
15819 if (!WhiteOnMove(currentMove)) {
15820 DisplayMoveError(_("It is Black's turn"));
15823 moveType = WhiteDrop;
15825 case IcsPlayingBlack:
15826 case MachinePlaysWhite:
15827 if (WhiteOnMove(currentMove)) {
15828 DisplayMoveError(_("It is White's turn"));
15831 moveType = BlackDrop;
15834 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15840 if (moveType == BlackDrop && selection < BlackPawn) {
15841 selection = (ChessSquare) ((int) selection
15842 + (int) BlackPawn - (int) WhitePawn);
15844 if (boards[currentMove][y][x] != EmptySquare) {
15845 DisplayMoveError(_("That square is occupied"));
15849 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15855 /* Accept a pending offer of any kind from opponent */
15857 if (appData.icsActive) {
15858 SendToICS(ics_prefix);
15859 SendToICS("accept\n");
15860 } else if (cmailMsgLoaded) {
15861 if (currentMove == cmailOldMove &&
15862 commentList[cmailOldMove] != NULL &&
15863 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15864 "Black offers a draw" : "White offers a draw")) {
15866 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15867 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15869 DisplayError(_("There is no pending offer on this move"), 0);
15870 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15873 /* Not used for offers from chess program */
15880 /* Decline a pending offer of any kind from opponent */
15882 if (appData.icsActive) {
15883 SendToICS(ics_prefix);
15884 SendToICS("decline\n");
15885 } else if (cmailMsgLoaded) {
15886 if (currentMove == cmailOldMove &&
15887 commentList[cmailOldMove] != NULL &&
15888 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15889 "Black offers a draw" : "White offers a draw")) {
15891 AppendComment(cmailOldMove, "Draw declined", TRUE);
15892 DisplayComment(cmailOldMove - 1, "Draw declined");
15895 DisplayError(_("There is no pending offer on this move"), 0);
15898 /* Not used for offers from chess program */
15905 /* Issue ICS rematch command */
15906 if (appData.icsActive) {
15907 SendToICS(ics_prefix);
15908 SendToICS("rematch\n");
15915 /* Call your opponent's flag (claim a win on time) */
15916 if (appData.icsActive) {
15917 SendToICS(ics_prefix);
15918 SendToICS("flag\n");
15920 switch (gameMode) {
15923 case MachinePlaysWhite:
15926 GameEnds(GameIsDrawn, "Both players ran out of time",
15929 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15931 DisplayError(_("Your opponent is not out of time"), 0);
15934 case MachinePlaysBlack:
15937 GameEnds(GameIsDrawn, "Both players ran out of time",
15940 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15942 DisplayError(_("Your opponent is not out of time"), 0);
15950 ClockClick (int which)
15951 { // [HGM] code moved to back-end from winboard.c
15952 if(which) { // black clock
15953 if (gameMode == EditPosition || gameMode == IcsExamining) {
15954 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15955 SetBlackToPlayEvent();
15956 } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15957 gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15958 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15959 } else if (shiftKey) {
15960 AdjustClock(which, -1);
15961 } else if (gameMode == IcsPlayingWhite ||
15962 gameMode == MachinePlaysBlack) {
15965 } else { // white clock
15966 if (gameMode == EditPosition || gameMode == IcsExamining) {
15967 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15968 SetWhiteToPlayEvent();
15969 } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15970 gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15971 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15972 } else if (shiftKey) {
15973 AdjustClock(which, -1);
15974 } else if (gameMode == IcsPlayingBlack ||
15975 gameMode == MachinePlaysWhite) {
15984 /* Offer draw or accept pending draw offer from opponent */
15986 if (appData.icsActive) {
15987 /* Note: tournament rules require draw offers to be
15988 made after you make your move but before you punch
15989 your clock. Currently ICS doesn't let you do that;
15990 instead, you immediately punch your clock after making
15991 a move, but you can offer a draw at any time. */
15993 SendToICS(ics_prefix);
15994 SendToICS("draw\n");
15995 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15996 } else if (cmailMsgLoaded) {
15997 if (currentMove == cmailOldMove &&
15998 commentList[cmailOldMove] != NULL &&
15999 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
16000 "Black offers a draw" : "White offers a draw")) {
16001 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
16002 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
16003 } else if (currentMove == cmailOldMove + 1) {
16004 char *offer = WhiteOnMove(cmailOldMove) ?
16005 "White offers a draw" : "Black offers a draw";
16006 AppendComment(currentMove, offer, TRUE);
16007 DisplayComment(currentMove - 1, offer);
16008 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
16010 DisplayError(_("You must make your move before offering a draw"), 0);
16011 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
16013 } else if (first.offeredDraw) {
16014 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
16016 if (first.sendDrawOffers) {
16017 SendToProgram("draw\n", &first);
16018 userOfferedDraw = TRUE;
16026 /* Offer Adjourn or accept pending Adjourn offer from opponent */
16028 if (appData.icsActive) {
16029 SendToICS(ics_prefix);
16030 SendToICS("adjourn\n");
16032 /* Currently GNU Chess doesn't offer or accept Adjourns */
16040 /* Offer Abort or accept pending Abort offer from opponent */
16042 if (appData.icsActive) {
16043 SendToICS(ics_prefix);
16044 SendToICS("abort\n");
16046 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
16053 /* Resign. You can do this even if it's not your turn. */
16055 if (appData.icsActive) {
16056 SendToICS(ics_prefix);
16057 SendToICS("resign\n");
16059 switch (gameMode) {
16060 case MachinePlaysWhite:
16061 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
16063 case MachinePlaysBlack:
16064 GameEnds(BlackWins, "White resigns", GE_PLAYER);
16067 if (cmailMsgLoaded) {
16069 if (WhiteOnMove(cmailOldMove)) {
16070 GameEnds(BlackWins, "White resigns", GE_PLAYER);
16072 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
16074 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
16085 StopObservingEvent ()
16087 /* Stop observing current games */
16088 SendToICS(ics_prefix);
16089 SendToICS("unobserve\n");
16093 StopExaminingEvent ()
16095 /* Stop observing current game */
16096 SendToICS(ics_prefix);
16097 SendToICS("unexamine\n");
16101 ForwardInner (int target)
16103 int limit; int oldSeekGraphUp = seekGraphUp;
16105 if (appData.debugMode)
16106 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
16107 target, currentMove, forwardMostMove);
16109 if (gameMode == EditPosition)
16112 seekGraphUp = FALSE;
16113 MarkTargetSquares(1);
16114 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16116 if (gameMode == PlayFromGameFile && !pausing)
16119 if (gameMode == IcsExamining && pausing)
16120 limit = pauseExamForwardMostMove;
16122 limit = forwardMostMove;
16124 if (target > limit) target = limit;
16126 if (target > 0 && moveList[target - 1][0]) {
16127 int fromX, fromY, toX, toY;
16128 toX = moveList[target - 1][2] - AAA;
16129 toY = moveList[target - 1][3] - ONE;
16130 if (moveList[target - 1][1] == '@') {
16131 if (appData.highlightLastMove) {
16132 SetHighlights(-1, -1, toX, toY);
16135 fromX = moveList[target - 1][0] - AAA;
16136 fromY = moveList[target - 1][1] - ONE;
16137 if (target == currentMove + 1) {
16138 if(moveList[target - 1][4] == ';') { // multi-leg
16139 killX = moveList[target - 1][5] - AAA;
16140 killY = moveList[target - 1][6] - ONE;
16142 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
16143 killX = killY = -1;
16145 if (appData.highlightLastMove) {
16146 SetHighlights(fromX, fromY, toX, toY);
16150 if (gameMode == EditGame || gameMode == AnalyzeMode ||
16151 gameMode == Training || gameMode == PlayFromGameFile ||
16152 gameMode == AnalyzeFile) {
16153 while (currentMove < target) {
16154 if(second.analyzing) SendMoveToProgram(currentMove, &second);
16155 SendMoveToProgram(currentMove++, &first);
16158 currentMove = target;
16161 if (gameMode == EditGame || gameMode == EndOfGame) {
16162 whiteTimeRemaining = timeRemaining[0][currentMove];
16163 blackTimeRemaining = timeRemaining[1][currentMove];
16165 DisplayBothClocks();
16166 DisplayMove(currentMove - 1);
16167 DrawPosition(oldSeekGraphUp, boards[currentMove]);
16168 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16169 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
16170 DisplayComment(currentMove - 1, commentList[currentMove]);
16172 ClearMap(); // [HGM] exclude: invalidate map
16179 if (gameMode == IcsExamining && !pausing) {
16180 SendToICS(ics_prefix);
16181 SendToICS("forward\n");
16183 ForwardInner(currentMove + 1);
16190 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16191 /* to optimze, we temporarily turn off analysis mode while we feed
16192 * the remaining moves to the engine. Otherwise we get analysis output
16195 if (first.analysisSupport) {
16196 SendToProgram("exit\nforce\n", &first);
16197 first.analyzing = FALSE;
16201 if (gameMode == IcsExamining && !pausing) {
16202 SendToICS(ics_prefix);
16203 SendToICS("forward 999999\n");
16205 ForwardInner(forwardMostMove);
16208 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16209 /* we have fed all the moves, so reactivate analysis mode */
16210 SendToProgram("analyze\n", &first);
16211 first.analyzing = TRUE;
16212 /*first.maybeThinking = TRUE;*/
16213 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16218 BackwardInner (int target)
16220 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16222 if (appData.debugMode)
16223 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16224 target, currentMove, forwardMostMove);
16226 if (gameMode == EditPosition) return;
16227 seekGraphUp = FALSE;
16228 MarkTargetSquares(1);
16229 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16230 if (currentMove <= backwardMostMove) {
16232 DrawPosition(full_redraw, boards[currentMove]);
16235 if (gameMode == PlayFromGameFile && !pausing)
16238 if (moveList[target][0]) {
16239 int fromX, fromY, toX, toY;
16240 toX = moveList[target][2] - AAA;
16241 toY = moveList[target][3] - ONE;
16242 if (moveList[target][1] == '@') {
16243 if (appData.highlightLastMove) {
16244 SetHighlights(-1, -1, toX, toY);
16247 fromX = moveList[target][0] - AAA;
16248 fromY = moveList[target][1] - ONE;
16249 if (target == currentMove - 1) {
16250 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16252 if (appData.highlightLastMove) {
16253 SetHighlights(fromX, fromY, toX, toY);
16257 if (gameMode == EditGame || gameMode==AnalyzeMode ||
16258 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16259 while (currentMove > target) {
16260 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16261 // null move cannot be undone. Reload program with move history before it.
16263 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16264 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16266 SendBoard(&first, i);
16267 if(second.analyzing) SendBoard(&second, i);
16268 for(currentMove=i; currentMove<target; currentMove++) {
16269 SendMoveToProgram(currentMove, &first);
16270 if(second.analyzing) SendMoveToProgram(currentMove, &second);
16274 SendToBoth("undo\n");
16278 currentMove = target;
16281 if (gameMode == EditGame || gameMode == EndOfGame) {
16282 whiteTimeRemaining = timeRemaining[0][currentMove];
16283 blackTimeRemaining = timeRemaining[1][currentMove];
16285 DisplayBothClocks();
16286 DisplayMove(currentMove - 1);
16287 DrawPosition(full_redraw, boards[currentMove]);
16288 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16289 // [HGM] PV info: routine tests if comment empty
16290 DisplayComment(currentMove - 1, commentList[currentMove]);
16291 ClearMap(); // [HGM] exclude: invalidate map
16297 if (gameMode == IcsExamining && !pausing) {
16298 SendToICS(ics_prefix);
16299 SendToICS("backward\n");
16301 BackwardInner(currentMove - 1);
16308 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16309 /* to optimize, we temporarily turn off analysis mode while we undo
16310 * all the moves. Otherwise we get analysis output after each undo.
16312 if (first.analysisSupport) {
16313 SendToProgram("exit\nforce\n", &first);
16314 first.analyzing = FALSE;
16318 if (gameMode == IcsExamining && !pausing) {
16319 SendToICS(ics_prefix);
16320 SendToICS("backward 999999\n");
16322 BackwardInner(backwardMostMove);
16325 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16326 /* we have fed all the moves, so reactivate analysis mode */
16327 SendToProgram("analyze\n", &first);
16328 first.analyzing = TRUE;
16329 /*first.maybeThinking = TRUE;*/
16330 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16337 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16338 if (to >= forwardMostMove) to = forwardMostMove;
16339 if (to <= backwardMostMove) to = backwardMostMove;
16340 if (to < currentMove) {
16348 RevertEvent (Boolean annotate)
16350 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16353 if (gameMode != IcsExamining) {
16354 DisplayError(_("You are not examining a game"), 0);
16358 DisplayError(_("You can't revert while pausing"), 0);
16361 SendToICS(ics_prefix);
16362 SendToICS("revert\n");
16366 RetractMoveEvent ()
16368 switch (gameMode) {
16369 case MachinePlaysWhite:
16370 case MachinePlaysBlack:
16371 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16372 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16375 if (forwardMostMove < 2) return;
16376 currentMove = forwardMostMove = forwardMostMove - 2;
16377 whiteTimeRemaining = timeRemaining[0][currentMove];
16378 blackTimeRemaining = timeRemaining[1][currentMove];
16379 DisplayBothClocks();
16380 DisplayMove(currentMove - 1);
16381 ClearHighlights();/*!! could figure this out*/
16382 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16383 SendToProgram("remove\n", &first);
16384 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16387 case BeginningOfGame:
16391 case IcsPlayingWhite:
16392 case IcsPlayingBlack:
16393 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16394 SendToICS(ics_prefix);
16395 SendToICS("takeback 2\n");
16397 SendToICS(ics_prefix);
16398 SendToICS("takeback 1\n");
16407 ChessProgramState *cps;
16409 switch (gameMode) {
16410 case MachinePlaysWhite:
16411 if (!WhiteOnMove(forwardMostMove)) {
16412 DisplayError(_("It is your turn"), 0);
16417 case MachinePlaysBlack:
16418 if (WhiteOnMove(forwardMostMove)) {
16419 DisplayError(_("It is your turn"), 0);
16424 case TwoMachinesPlay:
16425 if (WhiteOnMove(forwardMostMove) ==
16426 (first.twoMachinesColor[0] == 'w')) {
16432 case BeginningOfGame:
16436 SendToProgram("?\n", cps);
16440 TruncateGameEvent ()
16443 if (gameMode != EditGame) return;
16450 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16451 if (forwardMostMove > currentMove) {
16452 if (gameInfo.resultDetails != NULL) {
16453 free(gameInfo.resultDetails);
16454 gameInfo.resultDetails = NULL;
16455 gameInfo.result = GameUnfinished;
16457 forwardMostMove = currentMove;
16458 HistorySet(parseList, backwardMostMove, forwardMostMove,
16466 if (appData.noChessProgram) return;
16467 switch (gameMode) {
16468 case MachinePlaysWhite:
16469 if (WhiteOnMove(forwardMostMove)) {
16470 DisplayError(_("Wait until your turn."), 0);
16474 case BeginningOfGame:
16475 case MachinePlaysBlack:
16476 if (!WhiteOnMove(forwardMostMove)) {
16477 DisplayError(_("Wait until your turn."), 0);
16482 DisplayError(_("No hint available"), 0);
16485 SendToProgram("hint\n", &first);
16486 hintRequested = TRUE;
16490 SaveSelected (FILE *g, int dummy, char *dummy2)
16492 ListGame * lg = (ListGame *) gameList.head;
16496 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16497 DisplayError(_("Game list not loaded or empty"), 0);
16501 creatingBook = TRUE; // suppresses stuff during load game
16503 /* Get list size */
16504 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16505 if(lg->position >= 0) { // selected?
16506 LoadGame(f, nItem, "", TRUE);
16507 SaveGamePGN2(g); // leaves g open
16510 lg = (ListGame *) lg->node.succ;
16514 creatingBook = FALSE;
16522 ListGame * lg = (ListGame *) gameList.head;
16525 static int secondTime = FALSE;
16527 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16528 DisplayError(_("Game list not loaded or empty"), 0);
16532 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16535 DisplayNote(_("Book file exists! Try again for overwrite."));
16539 creatingBook = TRUE;
16540 secondTime = FALSE;
16542 /* Get list size */
16543 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16544 if(lg->position >= 0) {
16545 LoadGame(f, nItem, "", TRUE);
16546 AddGameToBook(TRUE);
16549 lg = (ListGame *) lg->node.succ;
16552 creatingBook = FALSE;
16559 if (appData.noChessProgram) return;
16560 switch (gameMode) {
16561 case MachinePlaysWhite:
16562 if (WhiteOnMove(forwardMostMove)) {
16563 DisplayError(_("Wait until your turn."), 0);
16567 case BeginningOfGame:
16568 case MachinePlaysBlack:
16569 if (!WhiteOnMove(forwardMostMove)) {
16570 DisplayError(_("Wait until your turn."), 0);
16575 EditPositionDone(TRUE);
16577 case TwoMachinesPlay:
16582 SendToProgram("bk\n", &first);
16583 bookOutput[0] = NULLCHAR;
16584 bookRequested = TRUE;
16590 char *tags = PGNTags(&gameInfo);
16591 TagsPopUp(tags, CmailMsg());
16595 /* end button procedures */
16598 PrintPosition (FILE *fp, int move)
16602 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16603 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16604 char c = PieceToChar(boards[move][i][j]);
16605 fputc(c == '?' ? '.' : c, fp);
16606 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16609 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16610 fprintf(fp, "white to play\n");
16612 fprintf(fp, "black to play\n");
16616 PrintOpponents (FILE *fp)
16618 if (gameInfo.white != NULL) {
16619 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16625 /* Find last component of program's own name, using some heuristics */
16627 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16630 int local = (strcmp(host, "localhost") == 0);
16631 while (!local && (p = strchr(prog, ';')) != NULL) {
16633 while (*p == ' ') p++;
16636 if (*prog == '"' || *prog == '\'') {
16637 q = strchr(prog + 1, *prog);
16639 q = strchr(prog, ' ');
16641 if (q == NULL) q = prog + strlen(prog);
16643 while (p >= prog && *p != '/' && *p != '\\') p--;
16645 if(p == prog && *p == '"') p++;
16647 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16648 memcpy(buf, p, q - p);
16649 buf[q - p] = NULLCHAR;
16657 TimeControlTagValue ()
16660 if (!appData.clockMode) {
16661 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16662 } else if (movesPerSession > 0) {
16663 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16664 } else if (timeIncrement == 0) {
16665 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16667 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16669 return StrSave(buf);
16675 /* This routine is used only for certain modes */
16676 VariantClass v = gameInfo.variant;
16677 ChessMove r = GameUnfinished;
16680 if(keepInfo) return;
16682 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16683 r = gameInfo.result;
16684 p = gameInfo.resultDetails;
16685 gameInfo.resultDetails = NULL;
16687 ClearGameInfo(&gameInfo);
16688 gameInfo.variant = v;
16690 switch (gameMode) {
16691 case MachinePlaysWhite:
16692 gameInfo.event = StrSave( appData.pgnEventHeader );
16693 gameInfo.site = StrSave(HostName());
16694 gameInfo.date = PGNDate();
16695 gameInfo.round = StrSave("-");
16696 gameInfo.white = StrSave(first.tidy);
16697 gameInfo.black = StrSave(UserName());
16698 gameInfo.timeControl = TimeControlTagValue();
16701 case MachinePlaysBlack:
16702 gameInfo.event = StrSave( appData.pgnEventHeader );
16703 gameInfo.site = StrSave(HostName());
16704 gameInfo.date = PGNDate();
16705 gameInfo.round = StrSave("-");
16706 gameInfo.white = StrSave(UserName());
16707 gameInfo.black = StrSave(first.tidy);
16708 gameInfo.timeControl = TimeControlTagValue();
16711 case TwoMachinesPlay:
16712 gameInfo.event = StrSave( appData.pgnEventHeader );
16713 gameInfo.site = StrSave(HostName());
16714 gameInfo.date = PGNDate();
16717 snprintf(buf, MSG_SIZ, "%d", roundNr);
16718 gameInfo.round = StrSave(buf);
16720 gameInfo.round = StrSave("-");
16722 if (first.twoMachinesColor[0] == 'w') {
16723 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16724 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16726 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16727 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16729 gameInfo.timeControl = TimeControlTagValue();
16733 gameInfo.event = StrSave("Edited game");
16734 gameInfo.site = StrSave(HostName());
16735 gameInfo.date = PGNDate();
16736 gameInfo.round = StrSave("-");
16737 gameInfo.white = StrSave("-");
16738 gameInfo.black = StrSave("-");
16739 gameInfo.result = r;
16740 gameInfo.resultDetails = p;
16744 gameInfo.event = StrSave("Edited position");
16745 gameInfo.site = StrSave(HostName());
16746 gameInfo.date = PGNDate();
16747 gameInfo.round = StrSave("-");
16748 gameInfo.white = StrSave("-");
16749 gameInfo.black = StrSave("-");
16752 case IcsPlayingWhite:
16753 case IcsPlayingBlack:
16758 case PlayFromGameFile:
16759 gameInfo.event = StrSave("Game from non-PGN file");
16760 gameInfo.site = StrSave(HostName());
16761 gameInfo.date = PGNDate();
16762 gameInfo.round = StrSave("-");
16763 gameInfo.white = StrSave("?");
16764 gameInfo.black = StrSave("?");
16773 ReplaceComment (int index, char *text)
16779 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16780 pvInfoList[index-1].depth == len &&
16781 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16782 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16783 while (*text == '\n') text++;
16784 len = strlen(text);
16785 while (len > 0 && text[len - 1] == '\n') len--;
16787 if (commentList[index] != NULL)
16788 free(commentList[index]);
16791 commentList[index] = NULL;
16794 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16795 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16796 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16797 commentList[index] = (char *) malloc(len + 2);
16798 strncpy(commentList[index], text, len);
16799 commentList[index][len] = '\n';
16800 commentList[index][len + 1] = NULLCHAR;
16802 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16804 commentList[index] = (char *) malloc(len + 7);
16805 safeStrCpy(commentList[index], "{\n", 3);
16806 safeStrCpy(commentList[index]+2, text, len+1);
16807 commentList[index][len+2] = NULLCHAR;
16808 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16809 strcat(commentList[index], "\n}\n");
16814 CrushCRs (char *text)
16822 if (ch == '\r') continue;
16824 } while (ch != '\0');
16828 AppendComment (int index, char *text, Boolean addBraces)
16829 /* addBraces tells if we should add {} */
16834 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16835 if(addBraces == 3) addBraces = 0; else // force appending literally
16836 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16839 while (*text == '\n') text++;
16840 len = strlen(text);
16841 while (len > 0 && text[len - 1] == '\n') len--;
16842 text[len] = NULLCHAR;
16844 if (len == 0) return;
16846 if (commentList[index] != NULL) {
16847 Boolean addClosingBrace = addBraces;
16848 old = commentList[index];
16849 oldlen = strlen(old);
16850 while(commentList[index][oldlen-1] == '\n')
16851 commentList[index][--oldlen] = NULLCHAR;
16852 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16853 safeStrCpy(commentList[index], old, oldlen + len + 6);
16855 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16856 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16857 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16858 while (*text == '\n') { text++; len--; }
16859 commentList[index][--oldlen] = NULLCHAR;
16861 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16862 else strcat(commentList[index], "\n");
16863 strcat(commentList[index], text);
16864 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16865 else strcat(commentList[index], "\n");
16867 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16869 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16870 else commentList[index][0] = NULLCHAR;
16871 strcat(commentList[index], text);
16872 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16873 if(addBraces == TRUE) strcat(commentList[index], "}\n");
16878 FindStr (char * text, char * sub_text)
16880 char * result = strstr( text, sub_text );
16882 if( result != NULL ) {
16883 result += strlen( sub_text );
16889 /* [AS] Try to extract PV info from PGN comment */
16890 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16892 GetInfoFromComment (int index, char * text)
16894 char * sep = text, *p;
16896 if( text != NULL && index > 0 ) {
16899 int time = -1, sec = 0, deci;
16900 char * s_eval = FindStr( text, "[%eval " );
16901 char * s_emt = FindStr( text, "[%emt " );
16903 if( s_eval != NULL || s_emt != NULL ) {
16905 if(0) { // [HGM] this code is not finished, and could actually be detrimental
16910 if( s_eval != NULL ) {
16911 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16915 if( delim != ']' ) {
16920 if( s_emt != NULL ) {
16925 /* We expect something like: [+|-]nnn.nn/dd */
16928 if(*text != '{') return text; // [HGM] braces: must be normal comment
16930 sep = strchr( text, '/' );
16931 if( sep == NULL || sep < (text+4) ) {
16936 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16937 if(p[1] == '(') { // comment starts with PV
16938 p = strchr(p, ')'); // locate end of PV
16939 if(p == NULL || sep < p+5) return text;
16940 // at this point we have something like "{(.*) +0.23/6 ..."
16941 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16942 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16943 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16945 time = -1; sec = -1; deci = -1;
16946 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16947 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16948 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16949 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
16953 if( score_lo < 0 || score_lo >= 100 ) {
16957 if(sec >= 0) time = 600*time + 10*sec; else
16958 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16960 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16962 /* [HGM] PV time: now locate end of PV info */
16963 while( *++sep >= '0' && *sep <= '9'); // strip depth
16965 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16967 while( *++sep >= '0' && *sep <= '9'); // strip seconds
16969 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16970 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16981 pvInfoList[index-1].depth = depth;
16982 pvInfoList[index-1].score = score;
16983 pvInfoList[index-1].time = 10*time; // centi-sec
16984 if(*sep == '}') *sep = 0; else *--sep = '{';
16986 while(*p++ = *sep++)
16989 } // squeeze out space between PV and comment, and return both
16995 SendToProgram (char *message, ChessProgramState *cps)
16997 int count, outCount, error;
17000 if (cps->pr == NoProc) return;
17003 if (appData.debugMode) {
17006 fprintf(debugFP, "%ld >%-6s: %s",
17007 SubtractTimeMarks(&now, &programStartTime),
17008 cps->which, message);
17010 fprintf(serverFP, "%ld >%-6s: %s",
17011 SubtractTimeMarks(&now, &programStartTime),
17012 cps->which, message), fflush(serverFP);
17015 count = strlen(message);
17016 outCount = OutputToProcess(cps->pr, message, count, &error);
17017 if (outCount < count && !exiting
17018 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
17019 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
17020 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
17021 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
17022 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
17023 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
17024 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
17025 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
17027 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
17028 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
17029 gameInfo.result = res;
17031 gameInfo.resultDetails = StrSave(buf);
17033 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
17034 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
17039 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
17043 ChessProgramState *cps = (ChessProgramState *)closure;
17045 if (isr != cps->isr) return; /* Killed intentionally */
17048 RemoveInputSource(cps->isr);
17049 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
17050 _(cps->which), cps->program);
17051 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
17052 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
17053 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
17054 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
17055 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
17056 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
17058 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
17059 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
17060 gameInfo.result = res;
17062 gameInfo.resultDetails = StrSave(buf);
17064 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
17065 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
17067 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
17068 _(cps->which), cps->program);
17069 RemoveInputSource(cps->isr);
17071 /* [AS] Program is misbehaving badly... kill it */
17072 if( count == -2 ) {
17073 DestroyChildProcess( cps->pr, 9 );
17077 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
17082 if ((end_str = strchr(message, '\r')) != NULL)
17083 *end_str = NULLCHAR;
17084 if ((end_str = strchr(message, '\n')) != NULL)
17085 *end_str = NULLCHAR;
17087 if (appData.debugMode) {
17088 TimeMark now; int print = 1;
17089 char *quote = ""; char c; int i;
17091 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
17092 char start = message[0];
17093 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
17094 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
17095 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
17096 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
17097 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
17098 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
17099 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
17100 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
17101 sscanf(message, "hint: %c", &c)!=1 &&
17102 sscanf(message, "pong %c", &c)!=1 && start != '#') {
17103 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
17104 print = (appData.engineComments >= 2);
17106 message[0] = start; // restore original message
17110 fprintf(debugFP, "%ld <%-6s: %s%s\n",
17111 SubtractTimeMarks(&now, &programStartTime), cps->which,
17115 fprintf(serverFP, "%ld <%-6s: %s%s\n",
17116 SubtractTimeMarks(&now, &programStartTime), cps->which,
17118 message), fflush(serverFP);
17122 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
17123 if (appData.icsEngineAnalyze) {
17124 if (strstr(message, "whisper") != NULL ||
17125 strstr(message, "kibitz") != NULL ||
17126 strstr(message, "tellics") != NULL) return;
17129 HandleMachineMove(message, cps);
17134 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
17139 if( timeControl_2 > 0 ) {
17140 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
17141 tc = timeControl_2;
17144 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
17145 inc /= cps->timeOdds;
17146 st /= cps->timeOdds;
17148 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
17151 /* Set exact time per move, normally using st command */
17152 if (cps->stKludge) {
17153 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
17155 if (seconds == 0) {
17156 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
17158 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
17161 snprintf(buf, MSG_SIZ, "st %d\n", st);
17164 /* Set conventional or incremental time control, using level command */
17165 if (seconds == 0) {
17166 /* Note old gnuchess bug -- minutes:seconds used to not work.
17167 Fixed in later versions, but still avoid :seconds
17168 when seconds is 0. */
17169 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
17171 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
17172 seconds, inc/1000.);
17175 SendToProgram(buf, cps);
17177 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17178 /* Orthogonally, limit search to given depth */
17180 if (cps->sdKludge) {
17181 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17183 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17185 SendToProgram(buf, cps);
17188 if(cps->nps >= 0) { /* [HGM] nps */
17189 if(cps->supportsNPS == FALSE)
17190 cps->nps = -1; // don't use if engine explicitly says not supported!
17192 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17193 SendToProgram(buf, cps);
17198 ChessProgramState *
17200 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17202 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17203 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17209 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17211 char message[MSG_SIZ];
17214 /* Note: this routine must be called when the clocks are stopped
17215 or when they have *just* been set or switched; otherwise
17216 it will be off by the time since the current tick started.
17218 if (machineWhite) {
17219 time = whiteTimeRemaining / 10;
17220 otime = blackTimeRemaining / 10;
17222 time = blackTimeRemaining / 10;
17223 otime = whiteTimeRemaining / 10;
17225 /* [HGM] translate opponent's time by time-odds factor */
17226 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17228 if (time <= 0) time = 1;
17229 if (otime <= 0) otime = 1;
17231 snprintf(message, MSG_SIZ, "time %ld\n", time);
17232 SendToProgram(message, cps);
17234 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17235 SendToProgram(message, cps);
17239 EngineDefinedVariant (ChessProgramState *cps, int n)
17240 { // return name of n-th unknown variant that engine supports
17241 static char buf[MSG_SIZ];
17242 char *p, *s = cps->variants;
17243 if(!s) return NULL;
17244 do { // parse string from variants feature
17246 p = strchr(s, ',');
17247 if(p) *p = NULLCHAR;
17248 v = StringToVariant(s);
17249 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17250 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17251 if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17252 !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17253 !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17254 !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17255 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17258 if(n < 0) return buf;
17264 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17267 int len = strlen(name);
17270 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17272 sscanf(*p, "%d", &val);
17274 while (**p && **p != ' ')
17276 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17277 SendToProgram(buf, cps);
17284 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17287 int len = strlen(name);
17288 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17290 sscanf(*p, "%d", loc);
17291 while (**p && **p != ' ') (*p)++;
17292 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17293 SendToProgram(buf, cps);
17300 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17303 int len = strlen(name);
17304 if (strncmp((*p), name, len) == 0
17305 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17307 len = strlen(*p) + 1; if(len < MSG_SIZ && !strcmp(name, "option")) len = MSG_SIZ; // make sure string options have enough space to change their value
17308 FREE(*loc); *loc = malloc(len);
17309 strncpy(*loc, *p, len);
17310 sscanf(*p, "%[^\"]", *loc); // should always fit, because we allocated at least strlen(*p)
17311 while (**p && **p != '\"') (*p)++;
17312 if (**p == '\"') (*p)++;
17313 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17314 SendToProgram(buf, cps);
17321 ParseOption (Option *opt, ChessProgramState *cps)
17322 // [HGM] options: process the string that defines an engine option, and determine
17323 // name, type, default value, and allowed value range
17325 char *p, *q, buf[MSG_SIZ];
17326 int n, min = (-1)<<31, max = 1<<31, def;
17328 opt->target = &opt->value; // OK for spin/slider and checkbox
17329 if(p = strstr(opt->name, " -spin ")) {
17330 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17331 if(max < min) max = min; // enforce consistency
17332 if(def < min) def = min;
17333 if(def > max) def = max;
17338 } else if((p = strstr(opt->name, " -slider "))) {
17339 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17340 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17341 if(max < min) max = min; // enforce consistency
17342 if(def < min) def = min;
17343 if(def > max) def = max;
17347 opt->type = Spin; // Slider;
17348 } else if((p = strstr(opt->name, " -string "))) {
17349 opt->textValue = p+9;
17350 opt->type = TextBox;
17351 opt->target = &opt->textValue;
17352 } else if((p = strstr(opt->name, " -file "))) {
17353 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17354 opt->target = opt->textValue = p+7;
17355 opt->type = FileName; // FileName;
17356 opt->target = &opt->textValue;
17357 } else if((p = strstr(opt->name, " -path "))) {
17358 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17359 opt->target = opt->textValue = p+7;
17360 opt->type = PathName; // PathName;
17361 opt->target = &opt->textValue;
17362 } else if(p = strstr(opt->name, " -check ")) {
17363 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17364 opt->value = (def != 0);
17365 opt->type = CheckBox;
17366 } else if(p = strstr(opt->name, " -combo ")) {
17367 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17368 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17369 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17370 opt->value = n = 0;
17371 while(q = StrStr(q, " /// ")) {
17372 n++; *q = 0; // count choices, and null-terminate each of them
17374 if(*q == '*') { // remember default, which is marked with * prefix
17378 cps->comboList[cps->comboCnt++] = q;
17380 cps->comboList[cps->comboCnt++] = NULL;
17382 opt->type = ComboBox;
17383 } else if(p = strstr(opt->name, " -button")) {
17384 opt->type = Button;
17385 } else if(p = strstr(opt->name, " -save")) {
17386 opt->type = SaveButton;
17387 } else return FALSE;
17388 *p = 0; // terminate option name
17389 *(int*) (opt->name + MSG_SIZ - 104) = opt->value; // hide default values somewhere
17390 if(opt->target == &opt->textValue) strncpy(opt->name + MSG_SIZ - 100, opt->textValue, 99);
17391 // now look if the command-line options define a setting for this engine option.
17392 if(cps->optionSettings && cps->optionSettings[0])
17393 p = strstr(cps->optionSettings, opt->name); else p = NULL;
17394 if(p && (p == cps->optionSettings || p[-1] == ',')) {
17395 snprintf(buf, MSG_SIZ, "option %s", p);
17396 if(p = strstr(buf, ",")) *p = 0;
17397 if(q = strchr(buf, '=')) switch(opt->type) {
17399 for(n=0; n<opt->max; n++)
17400 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17405 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17409 opt->value = atoi(q+1);
17414 SendToProgram(buf, cps);
17420 FeatureDone (ChessProgramState *cps, int val)
17422 DelayedEventCallback cb = GetDelayedEvent();
17423 if ((cb == InitBackEnd3 && cps == &first) ||
17424 (cb == SettingsMenuIfReady && cps == &second) ||
17425 (cb == LoadEngine) || (cb == StartSecond) ||
17426 (cb == TwoMachinesEventIfReady)) {
17427 CancelDelayedEvent();
17428 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17429 } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17430 cps->initDone = val;
17431 if(val) cps->reload = FALSE, RefreshSettingsDialog(cps, val);
17434 /* Parse feature command from engine */
17436 ParseFeatures (char *args, ChessProgramState *cps)
17444 while (*p == ' ') p++;
17445 if (*p == NULLCHAR) return;
17447 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17448 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17449 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17450 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17451 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17452 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17453 if (BoolFeature(&p, "reuse", &val, cps)) {
17454 /* Engine can disable reuse, but can't enable it if user said no */
17455 if (!val) cps->reuse = FALSE;
17458 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17459 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17460 if (gameMode == TwoMachinesPlay) {
17461 DisplayTwoMachinesTitle();
17467 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17468 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17469 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17470 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17471 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17472 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17473 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17474 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17475 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17476 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17477 if (IntFeature(&p, "done", &val, cps)) {
17478 FeatureDone(cps, val);
17481 /* Added by Tord: */
17482 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17483 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17484 /* End of additions by Tord */
17486 /* [HGM] added features: */
17487 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17488 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17489 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17490 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17491 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17492 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17493 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17494 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17495 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17496 if(cps->nrOptions == 0) { ASSIGN(cps->option[0].name, _("Make Persistent -save")); ParseOption(&(cps->option[cps->nrOptions++]), cps); }
17497 FREE(cps->option[cps->nrOptions].name);
17498 cps->option[cps->nrOptions].name = q; q = NULL;
17499 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17500 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17501 SendToProgram(buf, cps);
17504 if(cps->nrOptions >= MAX_OPTIONS) {
17506 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17507 DisplayError(buf, 0);
17511 /* End of additions by HGM */
17513 /* unknown feature: complain and skip */
17515 while (*q && *q != '=') q++;
17516 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17517 SendToProgram(buf, cps);
17523 while (*p && *p != '\"') p++;
17524 if (*p == '\"') p++;
17526 while (*p && *p != ' ') p++;
17534 PeriodicUpdatesEvent (int newState)
17536 if (newState == appData.periodicUpdates)
17539 appData.periodicUpdates=newState;
17541 /* Display type changes, so update it now */
17542 // DisplayAnalysis();
17544 /* Get the ball rolling again... */
17546 AnalysisPeriodicEvent(1);
17547 StartAnalysisClock();
17552 PonderNextMoveEvent (int newState)
17554 if (newState == appData.ponderNextMove) return;
17555 if (gameMode == EditPosition) EditPositionDone(TRUE);
17557 SendToProgram("hard\n", &first);
17558 if (gameMode == TwoMachinesPlay) {
17559 SendToProgram("hard\n", &second);
17562 SendToProgram("easy\n", &first);
17563 thinkOutput[0] = NULLCHAR;
17564 if (gameMode == TwoMachinesPlay) {
17565 SendToProgram("easy\n", &second);
17568 appData.ponderNextMove = newState;
17572 NewSettingEvent (int option, int *feature, char *command, int value)
17576 if (gameMode == EditPosition) EditPositionDone(TRUE);
17577 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17578 if(feature == NULL || *feature) SendToProgram(buf, &first);
17579 if (gameMode == TwoMachinesPlay) {
17580 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17585 ShowThinkingEvent ()
17586 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17588 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17589 int newState = appData.showThinking
17590 // [HGM] thinking: other features now need thinking output as well
17591 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17593 if (oldState == newState) return;
17594 oldState = newState;
17595 if (gameMode == EditPosition) EditPositionDone(TRUE);
17597 SendToProgram("post\n", &first);
17598 if (gameMode == TwoMachinesPlay) {
17599 SendToProgram("post\n", &second);
17602 SendToProgram("nopost\n", &first);
17603 thinkOutput[0] = NULLCHAR;
17604 if (gameMode == TwoMachinesPlay) {
17605 SendToProgram("nopost\n", &second);
17608 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17612 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17614 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17615 if (pr == NoProc) return;
17616 AskQuestion(title, question, replyPrefix, pr);
17620 TypeInEvent (char firstChar)
17622 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17623 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17624 gameMode == AnalyzeMode || gameMode == EditGame ||
17625 gameMode == EditPosition || gameMode == IcsExamining ||
17626 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17627 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17628 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17629 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
17630 gameMode == Training) PopUpMoveDialog(firstChar);
17634 TypeInDoneEvent (char *move)
17637 int n, fromX, fromY, toX, toY;
17639 ChessMove moveType;
17642 if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17643 EditPositionPasteFEN(move);
17646 // [HGM] movenum: allow move number to be typed in any mode
17647 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17651 // undocumented kludge: allow command-line option to be typed in!
17652 // (potentially fatal, and does not implement the effect of the option.)
17653 // should only be used for options that are values on which future decisions will be made,
17654 // and definitely not on options that would be used during initialization.
17655 if(strstr(move, "!!! -") == move) {
17656 ParseArgsFromString(move+4);
17660 if (gameMode != EditGame && currentMove != forwardMostMove &&
17661 gameMode != Training) {
17662 DisplayMoveError(_("Displayed move is not current"));
17664 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17665 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17666 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17667 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17668 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17669 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17671 DisplayMoveError(_("Could not parse move"));
17677 DisplayMove (int moveNumber)
17679 char message[MSG_SIZ];
17681 char cpThinkOutput[MSG_SIZ];
17683 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17685 if (moveNumber == forwardMostMove - 1 ||
17686 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17688 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17690 if (strchr(cpThinkOutput, '\n')) {
17691 *strchr(cpThinkOutput, '\n') = NULLCHAR;
17694 *cpThinkOutput = NULLCHAR;
17697 /* [AS] Hide thinking from human user */
17698 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17699 *cpThinkOutput = NULLCHAR;
17700 if( thinkOutput[0] != NULLCHAR ) {
17703 for( i=0; i<=hiddenThinkOutputState; i++ ) {
17704 cpThinkOutput[i] = '.';
17706 cpThinkOutput[i] = NULLCHAR;
17707 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17711 if (moveNumber == forwardMostMove - 1 &&
17712 gameInfo.resultDetails != NULL) {
17713 if (gameInfo.resultDetails[0] == NULLCHAR) {
17714 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17716 snprintf(res, MSG_SIZ, " {%s} %s",
17717 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17723 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17724 DisplayMessage(res, cpThinkOutput);
17726 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17727 WhiteOnMove(moveNumber) ? " " : ".. ",
17728 parseList[moveNumber], res);
17729 DisplayMessage(message, cpThinkOutput);
17734 DisplayComment (int moveNumber, char *text)
17736 char title[MSG_SIZ];
17738 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17739 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17741 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17742 WhiteOnMove(moveNumber) ? " " : ".. ",
17743 parseList[moveNumber]);
17745 if (text != NULL && (appData.autoDisplayComment || commentUp))
17746 CommentPopUp(title, text);
17749 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17750 * might be busy thinking or pondering. It can be omitted if your
17751 * gnuchess is configured to stop thinking immediately on any user
17752 * input. However, that gnuchess feature depends on the FIONREAD
17753 * ioctl, which does not work properly on some flavors of Unix.
17756 Attention (ChessProgramState *cps)
17759 if (!cps->useSigint) return;
17760 if (appData.noChessProgram || (cps->pr == NoProc)) return;
17761 switch (gameMode) {
17762 case MachinePlaysWhite:
17763 case MachinePlaysBlack:
17764 case TwoMachinesPlay:
17765 case IcsPlayingWhite:
17766 case IcsPlayingBlack:
17769 /* Skip if we know it isn't thinking */
17770 if (!cps->maybeThinking) return;
17771 if (appData.debugMode)
17772 fprintf(debugFP, "Interrupting %s\n", cps->which);
17773 InterruptChildProcess(cps->pr);
17774 cps->maybeThinking = FALSE;
17779 #endif /*ATTENTION*/
17785 if (whiteTimeRemaining <= 0) {
17788 if (appData.icsActive) {
17789 if (appData.autoCallFlag &&
17790 gameMode == IcsPlayingBlack && !blackFlag) {
17791 SendToICS(ics_prefix);
17792 SendToICS("flag\n");
17796 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17798 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17799 if (appData.autoCallFlag) {
17800 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17807 if (blackTimeRemaining <= 0) {
17810 if (appData.icsActive) {
17811 if (appData.autoCallFlag &&
17812 gameMode == IcsPlayingWhite && !whiteFlag) {
17813 SendToICS(ics_prefix);
17814 SendToICS("flag\n");
17818 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17820 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17821 if (appData.autoCallFlag) {
17822 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17833 CheckTimeControl ()
17835 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17836 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17839 * add time to clocks when time control is achieved ([HGM] now also used for increment)
17841 if ( !WhiteOnMove(forwardMostMove) ) {
17842 /* White made time control */
17843 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17844 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17845 /* [HGM] time odds: correct new time quota for time odds! */
17846 / WhitePlayer()->timeOdds;
17847 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17849 lastBlack -= blackTimeRemaining;
17850 /* Black made time control */
17851 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17852 / WhitePlayer()->other->timeOdds;
17853 lastWhite = whiteTimeRemaining;
17858 DisplayBothClocks ()
17860 int wom = gameMode == EditPosition ?
17861 !blackPlaysFirst : WhiteOnMove(currentMove);
17862 DisplayWhiteClock(whiteTimeRemaining, wom);
17863 DisplayBlackClock(blackTimeRemaining, !wom);
17867 /* Timekeeping seems to be a portability nightmare. I think everyone
17868 has ftime(), but I'm really not sure, so I'm including some ifdefs
17869 to use other calls if you don't. Clocks will be less accurate if
17870 you have neither ftime nor gettimeofday.
17873 /* VS 2008 requires the #include outside of the function */
17874 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17875 #include <sys/timeb.h>
17878 /* Get the current time as a TimeMark */
17880 GetTimeMark (TimeMark *tm)
17882 #if HAVE_GETTIMEOFDAY
17884 struct timeval timeVal;
17885 struct timezone timeZone;
17887 gettimeofday(&timeVal, &timeZone);
17888 tm->sec = (long) timeVal.tv_sec;
17889 tm->ms = (int) (timeVal.tv_usec / 1000L);
17891 #else /*!HAVE_GETTIMEOFDAY*/
17894 // include <sys/timeb.h> / moved to just above start of function
17895 struct timeb timeB;
17898 tm->sec = (long) timeB.time;
17899 tm->ms = (int) timeB.millitm;
17901 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17902 tm->sec = (long) time(NULL);
17908 /* Return the difference in milliseconds between two
17909 time marks. We assume the difference will fit in a long!
17912 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17914 return 1000L*(tm2->sec - tm1->sec) +
17915 (long) (tm2->ms - tm1->ms);
17920 * Code to manage the game clocks.
17922 * In tournament play, black starts the clock and then white makes a move.
17923 * We give the human user a slight advantage if he is playing white---the
17924 * clocks don't run until he makes his first move, so it takes zero time.
17925 * Also, we don't account for network lag, so we could get out of sync
17926 * with GNU Chess's clock -- but then, referees are always right.
17929 static TimeMark tickStartTM;
17930 static long intendedTickLength;
17933 NextTickLength (long timeRemaining)
17935 long nominalTickLength, nextTickLength;
17937 if (timeRemaining > 0L && timeRemaining <= 10000L)
17938 nominalTickLength = 100L;
17940 nominalTickLength = 1000L;
17941 nextTickLength = timeRemaining % nominalTickLength;
17942 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17944 return nextTickLength;
17947 /* Adjust clock one minute up or down */
17949 AdjustClock (Boolean which, int dir)
17951 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17952 if(which) blackTimeRemaining += 60000*dir;
17953 else whiteTimeRemaining += 60000*dir;
17954 DisplayBothClocks();
17955 adjustedClock = TRUE;
17958 /* Stop clocks and reset to a fresh time control */
17962 (void) StopClockTimer();
17963 if (appData.icsActive) {
17964 whiteTimeRemaining = blackTimeRemaining = 0;
17965 } else if (searchTime) {
17966 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17967 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17968 } else { /* [HGM] correct new time quote for time odds */
17969 whiteTC = blackTC = fullTimeControlString;
17970 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17971 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17973 if (whiteFlag || blackFlag) {
17975 whiteFlag = blackFlag = FALSE;
17977 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17978 DisplayBothClocks();
17979 adjustedClock = FALSE;
17982 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17984 static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness...
17986 /* Decrement running clock by amount of time that has passed */
17991 long lastTickLength, fudge;
17994 if (!appData.clockMode) return;
17995 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17999 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18001 /* Fudge if we woke up a little too soon */
18002 fudge = intendedTickLength - lastTickLength;
18003 if (fudge < 0 || fudge > FUDGE) fudge = 0;
18005 if (WhiteOnMove(forwardMostMove)) {
18006 if(whiteNPS >= 0) lastTickLength = 0;
18007 tRemaining = whiteTimeRemaining -= lastTickLength;
18008 if( tRemaining < 0 && !appData.icsActive) {
18009 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
18010 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
18011 whiteStartMove = forwardMostMove; whiteTC = nextSession;
18012 lastWhite= tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
18015 if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining;
18016 DisplayWhiteClock(whiteTimeRemaining - fudge,
18017 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
18020 if(blackNPS >= 0) lastTickLength = 0;
18021 tRemaining = blackTimeRemaining -= lastTickLength;
18022 if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
18023 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
18025 blackStartMove = forwardMostMove;
18026 lastBlack = tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
18029 if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining;
18030 DisplayBlackClock(blackTimeRemaining - fudge,
18031 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
18034 if (CheckFlags()) return;
18036 if(twoBoards) { // count down secondary board's clocks as well
18037 activePartnerTime -= lastTickLength;
18039 if(activePartner == 'W')
18040 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
18042 DisplayBlackClock(activePartnerTime, TRUE);
18047 intendedTickLength = NextTickLength( tRemaining - fudge) + fudge;
18048 StartClockTimer(intendedTickLength);
18050 /* if the time remaining has fallen below the alarm threshold, sound the
18051 * alarm. if the alarm has sounded and (due to a takeback or time control
18052 * with increment) the time remaining has increased to a level above the
18053 * threshold, reset the alarm so it can sound again.
18056 if (appData.icsActive && appData.icsAlarm) {
18058 /* make sure we are dealing with the user's clock */
18059 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
18060 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
18063 if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) {
18064 alarmSounded = FALSE;
18065 } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) {
18067 alarmSounded = TRUE;
18073 /* A player has just moved, so stop the previously running
18074 clock and (if in clock mode) start the other one.
18075 We redisplay both clocks in case we're in ICS mode, because
18076 ICS gives us an update to both clocks after every move.
18077 Note that this routine is called *after* forwardMostMove
18078 is updated, so the last fractional tick must be subtracted
18079 from the color that is *not* on move now.
18082 SwitchClocks (int newMoveNr)
18084 long lastTickLength;
18086 int flagged = FALSE;
18090 if (StopClockTimer() && appData.clockMode) {
18091 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18092 if (!WhiteOnMove(forwardMostMove)) {
18093 if(blackNPS >= 0) lastTickLength = 0;
18094 blackTimeRemaining -= lastTickLength;
18095 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18096 // if(pvInfoList[forwardMostMove].time == -1)
18097 pvInfoList[forwardMostMove].time = // use GUI time
18098 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
18100 if(whiteNPS >= 0) lastTickLength = 0;
18101 whiteTimeRemaining -= lastTickLength;
18102 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18103 // if(pvInfoList[forwardMostMove].time == -1)
18104 pvInfoList[forwardMostMove].time =
18105 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
18107 flagged = CheckFlags();
18109 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
18110 CheckTimeControl();
18112 if (flagged || !appData.clockMode) return;
18114 switch (gameMode) {
18115 case MachinePlaysBlack:
18116 case MachinePlaysWhite:
18117 case BeginningOfGame:
18118 if (pausing) return;
18122 case PlayFromGameFile:
18130 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
18131 if(WhiteOnMove(forwardMostMove))
18132 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
18133 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
18137 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18138 whiteTimeRemaining : blackTimeRemaining);
18139 StartClockTimer(intendedTickLength);
18143 /* Stop both clocks */
18147 long lastTickLength;
18150 if (!StopClockTimer()) return;
18151 if (!appData.clockMode) return;
18155 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18156 if (WhiteOnMove(forwardMostMove)) {
18157 if(whiteNPS >= 0) lastTickLength = 0;
18158 whiteTimeRemaining -= lastTickLength;
18159 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
18161 if(blackNPS >= 0) lastTickLength = 0;
18162 blackTimeRemaining -= lastTickLength;
18163 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
18168 /* Start clock of player on move. Time may have been reset, so
18169 if clock is already running, stop and restart it. */
18173 (void) StopClockTimer(); /* in case it was running already */
18174 DisplayBothClocks();
18175 if (CheckFlags()) return;
18177 if (!appData.clockMode) return;
18178 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
18180 GetTimeMark(&tickStartTM);
18181 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18182 whiteTimeRemaining : blackTimeRemaining);
18184 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
18185 whiteNPS = blackNPS = -1;
18186 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
18187 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
18188 whiteNPS = first.nps;
18189 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18190 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18191 blackNPS = first.nps;
18192 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18193 whiteNPS = second.nps;
18194 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18195 blackNPS = second.nps;
18196 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18198 StartClockTimer(intendedTickLength);
18202 TimeString (long ms)
18204 long second, minute, hour, day;
18206 static char buf[40], moveTime[8];
18208 if (ms > 0 && ms <= 9900) {
18209 /* convert milliseconds to tenths, rounding up */
18210 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18212 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18216 /* convert milliseconds to seconds, rounding up */
18217 /* use floating point to avoid strangeness of integer division
18218 with negative dividends on many machines */
18219 second = (long) floor(((double) (ms + 999L)) / 1000.0);
18226 day = second / (60 * 60 * 24);
18227 second = second % (60 * 60 * 24);
18228 hour = second / (60 * 60);
18229 second = second % (60 * 60);
18230 minute = second / 60;
18231 second = second % 60;
18233 if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time
18234 else *moveTime = NULLCHAR;
18237 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ",
18238 sign, day, hour, minute, second, moveTime);
18240 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime);
18242 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime);
18249 * This is necessary because some C libraries aren't ANSI C compliant yet.
18252 StrStr (char *string, char *match)
18256 length = strlen(match);
18258 for (i = strlen(string) - length; i >= 0; i--, string++)
18259 if (!strncmp(match, string, length))
18266 StrCaseStr (char *string, char *match)
18270 length = strlen(match);
18272 for (i = strlen(string) - length; i >= 0; i--, string++) {
18273 for (j = 0; j < length; j++) {
18274 if (ToLower(match[j]) != ToLower(string[j]))
18277 if (j == length) return string;
18285 StrCaseCmp (char *s1, char *s2)
18290 c1 = ToLower(*s1++);
18291 c2 = ToLower(*s2++);
18292 if (c1 > c2) return 1;
18293 if (c1 < c2) return -1;
18294 if (c1 == NULLCHAR) return 0;
18302 return isupper(c) ? tolower(c) : c;
18309 return islower(c) ? toupper(c) : c;
18311 #endif /* !_amigados */
18318 if ((ret = (char *) malloc(strlen(s) + 1)))
18320 safeStrCpy(ret, s, strlen(s)+1);
18326 StrSavePtr (char *s, char **savePtr)
18331 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18332 safeStrCpy(*savePtr, s, strlen(s)+1);
18344 clock = time((time_t *)NULL);
18345 tm = localtime(&clock);
18346 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18347 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18348 return StrSave(buf);
18353 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18355 int i, j, fromX, fromY, toX, toY;
18356 int whiteToPlay, haveRights = nrCastlingRights;
18362 whiteToPlay = (gameMode == EditPosition) ?
18363 !blackPlaysFirst : (move % 2 == 0);
18366 /* Piece placement data */
18367 for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18368 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18370 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18371 if (boards[move][i][j] == EmptySquare) {
18373 } else { ChessSquare piece = boards[move][i][j];
18374 if (emptycount > 0) {
18375 if(emptycount<10) /* [HGM] can be >= 10 */
18376 *p++ = '0' + emptycount;
18377 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18380 if(PieceToChar(piece) == '+') {
18381 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18383 piece = (ChessSquare)(CHUDEMOTED(piece));
18385 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18386 if(*p = PieceSuffix(piece)) p++;
18388 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18389 p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18394 if (emptycount > 0) {
18395 if(emptycount<10) /* [HGM] can be >= 10 */
18396 *p++ = '0' + emptycount;
18397 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18404 /* [HGM] print Crazyhouse or Shogi holdings */
18405 if( gameInfo.holdingsWidth ) {
18406 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18408 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18409 piece = boards[move][i][BOARD_WIDTH-1];
18410 if( piece != EmptySquare )
18411 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18412 *p++ = PieceToChar(piece);
18414 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18415 piece = boards[move][handSize-i-1][0];
18416 if( piece != EmptySquare )
18417 for(j=0; j<(int) boards[move][handSize-i-1][1]; j++)
18418 *p++ = PieceToChar(piece);
18421 if( q == p ) *p++ = '-';
18427 *p++ = whiteToPlay ? 'w' : 'b';
18430 if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18431 haveRights = 0; q = p;
18432 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18433 piece = boards[move][0][i];
18434 if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18435 if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18438 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18439 piece = boards[move][BOARD_HEIGHT-1][i];
18440 if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18441 if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18444 if(p == q) *p++ = '-';
18448 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18451 if(q != overrideCastling+1) p[-1] = ' '; else --p;
18454 int handW=0, handB=0;
18455 if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18456 for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18457 for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18460 if(appData.fischerCastling) {
18461 if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18462 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18463 if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18465 /* [HGM] write directly from rights */
18466 if(boards[move][CASTLING][2] != NoRights &&
18467 boards[move][CASTLING][0] != NoRights )
18468 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18469 if(boards[move][CASTLING][2] != NoRights &&
18470 boards[move][CASTLING][1] != NoRights )
18471 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18474 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18475 if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18477 if(boards[move][CASTLING][5] != NoRights &&
18478 boards[move][CASTLING][3] != NoRights )
18479 *p++ = boards[move][CASTLING][3] + AAA;
18480 if(boards[move][CASTLING][5] != NoRights &&
18481 boards[move][CASTLING][4] != NoRights )
18482 *p++ = boards[move][CASTLING][4] + AAA;
18486 /* [HGM] write true castling rights */
18487 if( nrCastlingRights == 6 ) {
18489 if(boards[move][CASTLING][0] != NoRights &&
18490 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
18491 q = (boards[move][CASTLING][1] != NoRights &&
18492 boards[move][CASTLING][2] != NoRights );
18493 if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18494 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18495 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18496 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18500 if(boards[move][CASTLING][3] != NoRights &&
18501 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
18502 q = (boards[move][CASTLING][4] != NoRights &&
18503 boards[move][CASTLING][5] != NoRights );
18505 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18506 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18507 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18512 if (q == p) *p++ = '-'; /* No castling rights */
18516 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
18517 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18518 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18519 /* En passant target square */
18520 if (move > backwardMostMove) {
18521 fromX = moveList[move - 1][0] - AAA;
18522 fromY = moveList[move - 1][1] - ONE;
18523 toX = moveList[move - 1][2] - AAA;
18524 toY = moveList[move - 1][3] - ONE;
18525 if ((whiteToPlay ? toY < fromY - 1 : toY > fromY + 1) &&
18526 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) ) {
18527 /* 2-square pawn move just happened */
18528 *p++ = (3*toX + 5*fromX + 4)/8 + AAA;
18529 *p++ = (3*toY + 5*fromY + 4)/8 + ONE;
18530 if(gameInfo.variant == VariantBerolina) {
18537 } else if(move == backwardMostMove) {
18538 // [HGM] perhaps we should always do it like this, and forget the above?
18539 if((signed char)boards[move][EP_STATUS] >= 0) {
18540 *p++ = boards[move][EP_STATUS] + AAA;
18541 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18552 i = boards[move][CHECK_COUNT];
18554 sprintf(p, "%d+%d ", i&255, i>>8);
18559 { int i = 0, j=move;
18561 /* [HGM] find reversible plies */
18562 if (appData.debugMode) { int k;
18563 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18564 for(k=backwardMostMove; k<=forwardMostMove; k++)
18565 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18569 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18570 if( j == backwardMostMove ) i += initialRulePlies;
18571 sprintf(p, "%d ", i);
18572 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18574 /* Fullmove number */
18575 sprintf(p, "%d", (move / 2) + 1);
18576 } else *--p = NULLCHAR;
18578 return StrSave(buf);
18582 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18584 int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18586 int emptycount, virgin[BOARD_FILES];
18587 ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18591 for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) board[BOARD_HEIGHT-i][j] = DarkSquare;
18593 /* Piece placement data */
18594 for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18597 if (*p == '/' || *p == ' ' || *p == '[' ) {
18599 emptycount = gameInfo.boardWidth - j;
18600 while (emptycount--)
18601 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18602 if (*p == '/') p++;
18603 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18604 for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18605 for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18607 appData.NrRanks = gameInfo.boardHeight - i; i=0;
18610 #if(BOARD_FILES >= 10)*0
18611 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18612 p++; emptycount=10;
18613 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18614 while (emptycount--)
18615 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18617 } else if (*p == '*') {
18618 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18619 } else if (isdigit(*p)) {
18620 emptycount = *p++ - '0';
18621 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18622 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18623 while (emptycount--)
18624 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18625 } else if (*p == '<') {
18626 if(i == BOARD_HEIGHT-1) shuffle = 1;
18627 else if (i != 0 || !shuffle) return FALSE;
18629 } else if (shuffle && *p == '>') {
18630 p++; // for now ignore closing shuffle range, and assume rank-end
18631 } else if (*p == '?') {
18632 if (j >= gameInfo.boardWidth) return FALSE;
18633 if (i != 0 && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18634 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18635 } else if (*p == '+' || isalpha(*p)) {
18636 char *q, *s = SUFFIXES;
18637 if (j >= gameInfo.boardWidth) return FALSE;
18640 if(q = strchr(s, p[1])) p++;
18641 piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18642 if(piece == EmptySquare) return FALSE; /* unknown piece */
18643 piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18644 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18647 if(q = strchr(s, *p)) p++;
18648 piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18651 if(piece==EmptySquare) return FALSE; /* unknown piece */
18652 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18653 piece = (ChessSquare) (PROMOTED(piece));
18654 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18657 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18658 if(piece == king) wKingRank = i;
18659 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18665 while (*p == '/' || *p == ' ') p++;
18667 if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18669 /* [HGM] by default clear Crazyhouse holdings, if present */
18670 if(gameInfo.holdingsWidth) {
18671 for(i=0; i<handSize; i++) {
18672 board[i][0] = EmptySquare; /* black holdings */
18673 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18674 board[i][1] = (ChessSquare) 0; /* black counts */
18675 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18679 /* [HGM] look for Crazyhouse holdings here */
18680 while(*p==' ') p++;
18681 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18682 int swap=0, wcnt=0, bcnt=0;
18684 if(*p == '<') swap++, p++;
18685 if(*p == '-' ) p++; /* empty holdings */ else {
18686 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18687 /* if we would allow FEN reading to set board size, we would */
18688 /* have to add holdings and shift the board read so far here */
18689 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18691 if((int) piece >= (int) BlackPawn ) {
18692 i = (int)piece - (int)BlackPawn;
18693 i = PieceToNumber((ChessSquare)i);
18694 if( i >= gameInfo.holdingsSize ) return FALSE;
18695 board[handSize-1-i][0] = piece; /* black holdings */
18696 board[handSize-1-i][1]++; /* black counts */
18699 i = (int)piece - (int)WhitePawn;
18700 i = PieceToNumber((ChessSquare)i);
18701 if( i >= gameInfo.holdingsSize ) return FALSE;
18702 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
18703 board[i][BOARD_WIDTH-2]++; /* black holdings */
18707 if(subst) { // substitute back-rank question marks by holdings pieces
18708 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18709 int k, m, n = bcnt + 1;
18710 if(board[0][j] == ClearBoard) {
18711 if(!wcnt) return FALSE;
18713 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18714 board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18715 if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18719 if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18720 if(!bcnt) return FALSE;
18721 if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18722 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[handSize-1-k][1]) < 0) {
18723 board[BOARD_HEIGHT-1][j] = board[handSize-1-k][0]; bcnt--;
18724 if(--board[handSize-1-k][1] == 0) board[handSize-1-k][0] = EmptySquare;
18735 if(subst) return FALSE; // substitution requested, but no holdings
18737 while(*p == ' ') p++;
18741 if(appData.colorNickNames) {
18742 if( c == appData.colorNickNames[0] ) c = 'w'; else
18743 if( c == appData.colorNickNames[1] ) c = 'b';
18747 *blackPlaysFirst = FALSE;
18750 *blackPlaysFirst = TRUE;
18756 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18757 /* return the extra info in global variiables */
18759 while(*p==' ') p++;
18761 if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18762 if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18763 if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18766 /* set defaults in case FEN is incomplete */
18767 board[EP_STATUS] = EP_UNKNOWN;
18768 board[TOUCHED_W] = board[TOUCHED_B] = 0;
18769 for(i=0; i<nrCastlingRights; i++ ) {
18770 board[CASTLING][i] =
18771 appData.fischerCastling ? NoRights : initialRights[i];
18772 } /* assume possible unless obviously impossible */
18773 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18774 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18775 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18776 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18777 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18778 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18779 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18780 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18783 if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18786 while(isalpha(*p)) {
18787 if(isupper(*p)) w |= 1 << (*p++ - 'A');
18788 if(islower(*p)) b |= 1 << (*p++ - 'a');
18792 board[TOUCHED_W] = ~w;
18793 board[TOUCHED_B] = ~b;
18794 while(*p == ' ') p++;
18798 if(nrCastlingRights) {
18800 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18801 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18802 /* castling indicator present, so default becomes no castlings */
18803 for(i=0; i<nrCastlingRights; i++ ) {
18804 board[CASTLING][i] = NoRights;
18807 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18808 (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18809 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18810 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
18811 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18813 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18814 if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18815 if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18817 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18818 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18819 if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18820 && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18821 if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18822 && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18825 for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18826 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18827 board[CASTLING][2] = whiteKingFile;
18828 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18829 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18830 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18833 for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18834 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18835 board[CASTLING][2] = whiteKingFile;
18836 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18837 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18838 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18841 for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18842 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18843 board[CASTLING][5] = blackKingFile;
18844 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18845 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18846 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18849 for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18850 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18851 board[CASTLING][5] = blackKingFile;
18852 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18853 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18854 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18857 default: /* FRC castlings */
18858 if(c >= 'a') { /* black rights */
18859 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18860 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18861 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18862 if(i == BOARD_RGHT) break;
18863 board[CASTLING][5] = i;
18865 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
18866 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
18868 board[CASTLING][3] = c;
18870 board[CASTLING][4] = c;
18871 } else { /* white rights */
18872 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18873 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18874 if(board[0][i] == WhiteKing) break;
18875 if(i == BOARD_RGHT) break;
18876 board[CASTLING][2] = i;
18877 c -= AAA - 'a' + 'A';
18878 if(board[0][c] >= WhiteKing) break;
18880 board[CASTLING][0] = c;
18882 board[CASTLING][1] = c;
18886 for(i=0; i<nrCastlingRights; i++)
18887 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18888 if(gameInfo.variant == VariantSChess)
18889 for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18890 if(fischer && shuffle) appData.fischerCastling = TRUE;
18891 if (appData.debugMode) {
18892 fprintf(debugFP, "FEN castling rights:");
18893 for(i=0; i<nrCastlingRights; i++)
18894 fprintf(debugFP, " %d", board[CASTLING][i]);
18895 fprintf(debugFP, "\n");
18898 while(*p==' ') p++;
18901 if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18903 /* read e.p. field in games that know e.p. capture */
18904 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
18905 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18906 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18908 p++; board[EP_STATUS] = EP_NONE;
18910 int d, r, c = *p - AAA;
18912 if(c >= BOARD_LEFT && c < BOARD_RGHT) {
18914 board[EP_STATUS] = board[EP_FILE] = c; r = 0;
18915 if(*p >= '0' && *p <='9') r = board[EP_RANK] = *p++ - ONE;
18916 d = (r < BOARD_HEIGHT << 1 ? 1 : -1); // assume double-push (P next to e.p. square nearer center)
18917 if(board[r+d][c] == EmptySquare) d *= 2; // but if no Pawn there, triple push
18918 board[LAST_TO] = 256*(r + d) + c;
18920 if(c >= BOARD_LEFT && c < BOARD_RGHT) { // mover explicitly mentioned
18921 if(*p >= '0' && *p <='9') r = board[EP_RANK] = *p++ - ONE;
18922 board[LAST_TO] = 256*r + c;
18923 if(!(board[EP_RANK]-r & 1)) board[EP_RANK] |= 128;
18929 while(*p == ' ') p++;
18931 board[CHECK_COUNT] = 0; // [HGM] 3check: check-count field
18932 if(sscanf(p, "%d+%d", &i, &j) == 2) {
18933 board[CHECK_COUNT] = i + 256*j;
18934 while(*p && *p != ' ') p++;
18937 c = sscanf(p, "%d%*d +%d+%d", &i, &j, &k);
18939 FENrulePlies = i; /* 50-move ply counter */
18940 /* (The move number is still ignored) */
18941 if(c == 3 && !board[CHECK_COUNT]) board[CHECK_COUNT] = (3 - j) + 256*(3 - k); // SCIDB-style check count
18948 EditPositionPasteFEN (char *fen)
18951 Board initial_position;
18953 if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18954 DisplayError(_("Bad FEN position in clipboard"), 0);
18957 int savedBlackPlaysFirst = blackPlaysFirst;
18958 EditPositionEvent();
18959 blackPlaysFirst = savedBlackPlaysFirst;
18960 CopyBoard(boards[0], initial_position);
18961 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18962 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18963 DisplayBothClocks();
18964 DrawPosition(FALSE, boards[currentMove]);
18969 static char cseq[12] = "\\ ";
18972 set_cont_sequence (char *new_seq)
18977 // handle bad attempts to set the sequence
18979 return 0; // acceptable error - no debug
18981 len = strlen(new_seq);
18982 ret = (len > 0) && (len < sizeof(cseq));
18984 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18985 else if (appData.debugMode)
18986 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18991 reformat a source message so words don't cross the width boundary. internal
18992 newlines are not removed. returns the wrapped size (no null character unless
18993 included in source message). If dest is NULL, only calculate the size required
18994 for the dest buffer. lp argument indicats line position upon entry, and it's
18995 passed back upon exit.
18998 wrap (char *dest, char *src, int count, int width, int *lp)
19000 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
19002 cseq_len = strlen(cseq);
19003 old_line = line = *lp;
19004 ansi = len = clen = 0;
19006 for (i=0; i < count; i++)
19008 if (src[i] == '\033')
19011 // if we hit the width, back up
19012 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
19014 // store i & len in case the word is too long
19015 old_i = i, old_len = len;
19017 // find the end of the last word
19018 while (i && src[i] != ' ' && src[i] != '\n')
19024 // word too long? restore i & len before splitting it
19025 if ((old_i-i+clen) >= width)
19032 if (i && src[i-1] == ' ')
19035 if (src[i] != ' ' && src[i] != '\n')
19042 // now append the newline and continuation sequence
19047 strncpy(dest+len, cseq, cseq_len);
19055 dest[len] = src[i];
19059 if (src[i] == '\n')
19064 if (dest && appData.debugMode)
19066 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
19067 count, width, line, len, *lp);
19068 show_bytes(debugFP, src, count);
19069 fprintf(debugFP, "\ndest: ");
19070 show_bytes(debugFP, dest, len);
19071 fprintf(debugFP, "\n");
19073 *lp = dest ? line : old_line;
19078 // [HGM] vari: routines for shelving variations
19079 Boolean modeRestore = FALSE;
19082 PushInner (int firstMove, int lastMove)
19084 int i, j, nrMoves = lastMove - firstMove;
19086 // push current tail of game on stack
19087 savedResult[storedGames] = gameInfo.result;
19088 savedDetails[storedGames] = gameInfo.resultDetails;
19089 gameInfo.resultDetails = NULL;
19090 savedFirst[storedGames] = firstMove;
19091 savedLast [storedGames] = lastMove;
19092 savedFramePtr[storedGames] = framePtr;
19093 framePtr -= nrMoves; // reserve space for the boards
19094 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
19095 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
19096 for(j=0; j<MOVE_LEN; j++)
19097 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
19098 for(j=0; j<2*MOVE_LEN; j++)
19099 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
19100 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
19101 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
19102 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
19103 pvInfoList[firstMove+i-1].depth = 0;
19104 commentList[framePtr+i] = commentList[firstMove+i];
19105 commentList[firstMove+i] = NULL;
19109 forwardMostMove = firstMove; // truncate game so we can start variation
19113 PushTail (int firstMove, int lastMove)
19115 if(appData.icsActive) { // only in local mode
19116 forwardMostMove = currentMove; // mimic old ICS behavior
19119 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
19121 PushInner(firstMove, lastMove);
19122 if(storedGames == 1) GreyRevert(FALSE);
19123 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
19127 PopInner (Boolean annotate)
19130 char buf[8000], moveBuf[20];
19132 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
19133 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
19134 nrMoves = savedLast[storedGames] - currentMove;
19137 if(!WhiteOnMove(currentMove))
19138 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
19139 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
19140 for(i=currentMove; i<forwardMostMove; i++) {
19142 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
19143 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
19144 strcat(buf, moveBuf);
19145 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
19146 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
19150 for(i=1; i<=nrMoves; i++) { // copy last variation back
19151 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
19152 for(j=0; j<MOVE_LEN; j++)
19153 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
19154 for(j=0; j<2*MOVE_LEN; j++)
19155 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
19156 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
19157 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
19158 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
19159 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
19160 commentList[currentMove+i] = commentList[framePtr+i];
19161 commentList[framePtr+i] = NULL;
19163 if(annotate) AppendComment(currentMove+1, buf, FALSE);
19164 framePtr = savedFramePtr[storedGames];
19165 gameInfo.result = savedResult[storedGames];
19166 if(gameInfo.resultDetails != NULL) {
19167 free(gameInfo.resultDetails);
19169 gameInfo.resultDetails = savedDetails[storedGames];
19170 forwardMostMove = currentMove + nrMoves;
19174 PopTail (Boolean annotate)
19176 if(appData.icsActive) return FALSE; // only in local mode
19177 if(!storedGames) return FALSE; // sanity
19178 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
19180 PopInner(annotate);
19181 if(currentMove < forwardMostMove) ForwardEvent(); else
19182 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
19184 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
19190 { // remove all shelved variations
19192 for(i=0; i<storedGames; i++) {
19193 if(savedDetails[i])
19194 free(savedDetails[i]);
19195 savedDetails[i] = NULL;
19197 for(i=framePtr; i<MAX_MOVES; i++) {
19198 if(commentList[i]) free(commentList[i]);
19199 commentList[i] = NULL;
19201 framePtr = MAX_MOVES-1;
19206 LoadVariation (int index, char *text)
19207 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
19208 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
19209 int level = 0, move;
19211 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
19212 // first find outermost bracketing variation
19213 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
19214 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
19215 if(*p == '{') wait = '}'; else
19216 if(*p == '[') wait = ']'; else
19217 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
19218 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
19220 if(*p == wait) wait = NULLCHAR; // closing ]} found
19223 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19224 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19225 end[1] = NULLCHAR; // clip off comment beyond variation
19226 ToNrEvent(currentMove-1);
19227 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19228 // kludge: use ParsePV() to append variation to game
19229 move = currentMove;
19230 ParsePV(start, TRUE, TRUE);
19231 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19232 ClearPremoveHighlights();
19234 ToNrEvent(currentMove+1);
19237 int transparency[2];
19242 #define BUF_SIZ (2*MSG_SIZ)
19243 char *p, *q, buf[BUF_SIZ];
19244 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19245 snprintf(buf, BUF_SIZ, "-theme %s", engineLine);
19246 ParseArgsFromString(buf);
19247 ActivateTheme(TRUE); // also redo colors
19251 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19254 q = appData.themeNames;
19255 snprintf(buf, BUF_SIZ, "\"%s\"", nickName);
19256 if(appData.useBitmaps) {
19257 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt true -lbtf \"%s\"",
19258 Shorten(appData.liteBackTextureFile));
19259 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dbtf \"%s\" -lbtm %d -dbtm %d",
19260 Shorten(appData.darkBackTextureFile),
19261 appData.liteBackTextureMode,
19262 appData.darkBackTextureMode );
19264 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt false");
19266 if(!appData.useBitmaps || transparency[0]) {
19267 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -lsc %s", Col2Text(2) ); // lightSquareColor
19269 if(!appData.useBitmaps || transparency[1]) {
19270 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dsc %s", Col2Text(3) ); // darkSquareColor
19272 if(appData.useBorder) {
19273 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub true -border \"%s\"",
19276 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub false");
19278 if(appData.useFont) {
19279 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19280 appData.renderPiecesWithFont,
19281 appData.fontToPieceTable,
19282 Col2Text(9), // appData.fontBackColorWhite
19283 Col2Text(10) ); // appData.fontForeColorBlack
19285 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf false");
19286 if(appData.pieceDirectory[0]) {
19287 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -pid \"%s\"", Shorten(appData.pieceDirectory));
19288 if(appData.trueColors != 2) // 2 is a kludge to suppress this in WinBoard
19289 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -trueColors %s", appData.trueColors ? "true" : "false");
19291 if(!appData.pieceDirectory[0] || !appData.trueColors)
19292 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -wpc %s -bpc %s",
19293 Col2Text(0), // whitePieceColor
19294 Col2Text(1) ); // blackPieceColor
19296 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19297 Col2Text(4), // highlightSquareColor
19298 Col2Text(5) ); // premoveHighlightColor
19299 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19300 if(insert != q) insert[-1] = NULLCHAR;
19301 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19304 ActivateTheme(FALSE);