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 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 int flock(int f, int code);
63 # define EGBB_NAME "egbbdll64.dll"
65 # define EGBB_NAME "egbbdll.dll"
70 # include <sys/file.h>
75 # define EGBB_NAME "egbbso64.so"
77 # define EGBB_NAME "egbbso.so"
79 // kludge to allow Windows code in back-end by converting it to corresponding Linux code
81 # define HMODULE void *
82 # define LoadLibrary(x) dlopen(x, RTLD_LAZY)
83 # define GetProcAddress dlsym
93 #include <sys/types.h>
102 #else /* not STDC_HEADERS */
105 # else /* not HAVE_STRING_H */
106 # include <strings.h>
107 # endif /* not HAVE_STRING_H */
108 #endif /* not STDC_HEADERS */
111 # include <sys/fcntl.h>
112 #else /* not HAVE_SYS_FCNTL_H */
115 # endif /* HAVE_FCNTL_H */
116 #endif /* not HAVE_SYS_FCNTL_H */
118 #if TIME_WITH_SYS_TIME
119 # include <sys/time.h>
123 # include <sys/time.h>
129 #if defined(_amigados) && !defined(__GNUC__)
134 extern int gettimeofday(struct timeval *, struct timezone *);
142 #include "frontend.h"
149 #include "backendz.h"
150 #include "evalgraph.h"
151 #include "engineoutput.h"
155 # define _(s) gettext (s)
156 # define N_(s) gettext_noop (s)
157 # define T_(s) gettext(s)
170 int establish P((void));
171 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
172 char *buf, int count, int error));
173 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
174 char *buf, int count, int error));
175 void SendToICS P((char *s));
176 void SendToICSDelayed P((char *s, long msdelay));
177 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
178 void HandleMachineMove P((char *message, ChessProgramState *cps));
179 int AutoPlayOneMove P((void));
180 int LoadGameOneMove P((ChessMove readAhead));
181 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
182 int LoadPositionFromFile P((char *filename, int n, char *title));
183 int SavePositionToFile P((char *filename));
184 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
185 void ShowMove P((int fromX, int fromY, int toX, int toY));
186 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
187 /*char*/int promoChar));
188 void BackwardInner P((int target));
189 void ForwardInner P((int target));
190 int Adjudicate P((ChessProgramState *cps));
191 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
192 void EditPositionDone P((Boolean fakeRights));
193 void PrintOpponents P((FILE *fp));
194 void PrintPosition P((FILE *fp, int move));
195 void StartChessProgram P((ChessProgramState *cps));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199 char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201 int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
250 extern void ConsoleCreate();
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
272 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES]; /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
298 int border; /* [HGM] width of board rim, needed to size seek graph */
299 char bestMove[MSG_SIZ];
300 int solvingTime, totalTime;
302 /* States for ics_getting_history */
304 #define H_REQUESTED 1
305 #define H_GOT_REQ_HEADER 2
306 #define H_GOT_UNREQ_HEADER 3
307 #define H_GETTING_MOVES 4
308 #define H_GOT_UNWANTED_HEADER 5
310 /* whosays values for GameEnds */
319 /* Maximum number of games in a cmail message */
320 #define CMAIL_MAX_GAMES 20
322 /* Different types of move when calling RegisterMove */
324 #define CMAIL_RESIGN 1
326 #define CMAIL_ACCEPT 3
328 /* Different types of result to remember for each game */
329 #define CMAIL_NOT_RESULT 0
330 #define CMAIL_OLD_RESULT 1
331 #define CMAIL_NEW_RESULT 2
333 /* Telnet protocol constants */
344 safeStrCpy (char *dst, const char *src, size_t count)
347 assert( dst != NULL );
348 assert( src != NULL );
351 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
352 if( i == count && dst[count-1] != NULLCHAR)
354 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
355 if(appData.debugMode)
356 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
362 /* Some compiler can't cast u64 to double
363 * This function do the job for us:
365 * We use the highest bit for cast, this only
366 * works if the highest bit is not
367 * in use (This should not happen)
369 * We used this for all compiler
372 u64ToDouble (u64 value)
375 u64 tmp = value & u64Const(0x7fffffffffffffff);
376 r = (double)(s64)tmp;
377 if (value & u64Const(0x8000000000000000))
378 r += 9.2233720368547758080e18; /* 2^63 */
382 /* Fake up flags for now, as we aren't keeping track of castling
383 availability yet. [HGM] Change of logic: the flag now only
384 indicates the type of castlings allowed by the rule of the game.
385 The actual rights themselves are maintained in the array
386 castlingRights, as part of the game history, and are not probed
392 int flags = F_ALL_CASTLE_OK;
393 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
394 switch (gameInfo.variant) {
396 flags &= ~F_ALL_CASTLE_OK;
397 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
398 flags |= F_IGNORE_CHECK;
400 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
403 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
405 case VariantKriegspiel:
406 flags |= F_KRIEGSPIEL_CAPTURE;
408 case VariantCapaRandom:
409 case VariantFischeRandom:
410 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
411 case VariantNoCastle:
412 case VariantShatranj:
417 flags &= ~F_ALL_CASTLE_OK;
420 case VariantChuChess:
422 flags |= F_NULL_MOVE;
427 if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
431 FILE *gameFileFP, *debugFP, *serverFP;
432 char *currentDebugFile; // [HGM] debug split: to remember name
435 [AS] Note: sometimes, the sscanf() function is used to parse the input
436 into a fixed-size buffer. Because of this, we must be prepared to
437 receive strings as long as the size of the input buffer, which is currently
438 set to 4K for Windows and 8K for the rest.
439 So, we must either allocate sufficiently large buffers here, or
440 reduce the size of the input buffer in the input reading part.
443 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
444 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
445 char thinkOutput1[MSG_SIZ*10];
447 ChessProgramState first, second, pairing;
449 /* premove variables */
452 int premoveFromX = 0;
453 int premoveFromY = 0;
454 int premovePromoChar = 0;
456 Boolean alarmSounded;
457 /* end premove variables */
459 char *ics_prefix = "$";
460 enum ICS_TYPE ics_type = ICS_GENERIC;
462 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
463 int pauseExamForwardMostMove = 0;
464 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
465 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
466 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
467 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
468 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
469 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
470 int whiteFlag = FALSE, blackFlag = FALSE;
471 int userOfferedDraw = FALSE;
472 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
473 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
474 int cmailMoveType[CMAIL_MAX_GAMES];
475 long ics_clock_paused = 0;
476 ProcRef icsPR = NoProc, cmailPR = NoProc;
477 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
478 GameMode gameMode = BeginningOfGame;
479 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
480 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
481 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
482 int hiddenThinkOutputState = 0; /* [AS] */
483 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
484 int adjudicateLossPlies = 6;
485 char white_holding[64], black_holding[64];
486 TimeMark lastNodeCountTime;
487 long lastNodeCount=0;
488 int shiftKey, controlKey; // [HGM] set by mouse handler
490 int have_sent_ICS_logon = 0;
492 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
493 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
494 Boolean adjustedClock;
495 long timeControl_2; /* [AS] Allow separate time controls */
496 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
497 long timeRemaining[2][MAX_MOVES];
498 int matchGame = 0, nextGame = 0, roundNr = 0;
499 Boolean waitingForGame = FALSE, startingEngine = FALSE;
500 TimeMark programStartTime, pauseStart;
501 char ics_handle[MSG_SIZ];
502 int have_set_title = 0;
504 /* animateTraining preserves the state of appData.animate
505 * when Training mode is activated. This allows the
506 * response to be animated when appData.animate == TRUE and
507 * appData.animateDragging == TRUE.
509 Boolean animateTraining;
515 Board boards[MAX_MOVES];
516 /* [HGM] Following 7 needed for accurate legality tests: */
517 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
518 signed char initialRights[BOARD_FILES];
519 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
520 int initialRulePlies, FENrulePlies;
521 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
523 Boolean shuffleOpenings;
524 int mute; // mute all sounds
526 // [HGM] vari: next 12 to save and restore variations
527 #define MAX_VARIATIONS 10
528 int framePtr = MAX_MOVES-1; // points to free stack entry
530 int savedFirst[MAX_VARIATIONS];
531 int savedLast[MAX_VARIATIONS];
532 int savedFramePtr[MAX_VARIATIONS];
533 char *savedDetails[MAX_VARIATIONS];
534 ChessMove savedResult[MAX_VARIATIONS];
536 void PushTail P((int firstMove, int lastMove));
537 Boolean PopTail P((Boolean annotate));
538 void PushInner P((int firstMove, int lastMove));
539 void PopInner P((Boolean annotate));
540 void CleanupTail P((void));
542 ChessSquare FIDEArray[2][BOARD_FILES] = {
543 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
544 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
545 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
546 BlackKing, BlackBishop, BlackKnight, BlackRook }
549 ChessSquare twoKingsArray[2][BOARD_FILES] = {
550 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
551 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
552 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
553 BlackKing, BlackKing, BlackKnight, BlackRook }
556 ChessSquare KnightmateArray[2][BOARD_FILES] = {
557 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
558 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
559 { BlackRook, BlackMan, BlackBishop, BlackQueen,
560 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
563 ChessSquare SpartanArray[2][BOARD_FILES] = {
564 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
565 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
566 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
567 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
570 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
571 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
572 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
573 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
574 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
577 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
578 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
579 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
580 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
581 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
584 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
585 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
586 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
587 { BlackRook, BlackKnight, BlackMan, BlackFerz,
588 BlackKing, BlackMan, BlackKnight, BlackRook }
591 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
592 { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
593 WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
594 { BlackRook, BlackKnight, BlackMan, BlackFerz,
595 BlackKing, BlackMan, BlackKnight, BlackRook }
598 ChessSquare lionArray[2][BOARD_FILES] = {
599 { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
600 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
601 { BlackRook, BlackLion, BlackBishop, BlackQueen,
602 BlackKing, BlackBishop, BlackKnight, BlackRook }
606 #if (BOARD_FILES>=10)
607 ChessSquare ShogiArray[2][BOARD_FILES] = {
608 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
609 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
610 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
611 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
614 ChessSquare XiangqiArray[2][BOARD_FILES] = {
615 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
616 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
617 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
618 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
621 ChessSquare CapablancaArray[2][BOARD_FILES] = {
622 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
623 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
624 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
625 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
628 ChessSquare GreatArray[2][BOARD_FILES] = {
629 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
630 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
631 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
632 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
635 ChessSquare JanusArray[2][BOARD_FILES] = {
636 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
637 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
638 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
639 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
642 ChessSquare GrandArray[2][BOARD_FILES] = {
643 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
644 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
645 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
646 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
649 ChessSquare ChuChessArray[2][BOARD_FILES] = {
650 { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
651 WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
652 { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
653 BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
657 ChessSquare GothicArray[2][BOARD_FILES] = {
658 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
659 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
660 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
661 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
664 #define GothicArray CapablancaArray
668 ChessSquare FalconArray[2][BOARD_FILES] = {
669 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
670 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
671 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
672 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
675 #define FalconArray CapablancaArray
678 #else // !(BOARD_FILES>=10)
679 #define XiangqiPosition FIDEArray
680 #define CapablancaArray FIDEArray
681 #define GothicArray FIDEArray
682 #define GreatArray FIDEArray
683 #endif // !(BOARD_FILES>=10)
685 #if (BOARD_FILES>=12)
686 ChessSquare CourierArray[2][BOARD_FILES] = {
687 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
688 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
689 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
690 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
692 ChessSquare ChuArray[6][BOARD_FILES] = {
693 { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
694 WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
695 { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
696 BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
697 { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
698 WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
699 { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
700 BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
701 { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
702 WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
703 { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
704 BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
706 #else // !(BOARD_FILES>=12)
707 #define CourierArray CapablancaArray
708 #define ChuArray CapablancaArray
709 #endif // !(BOARD_FILES>=12)
712 Board initialPosition;
715 /* Convert str to a rating. Checks for special cases of "----",
717 "++++", etc. Also strips ()'s */
719 string_to_rating (char *str)
721 while(*str && !isdigit(*str)) ++str;
723 return 0; /* One of the special "no rating" cases */
731 /* Init programStats */
732 programStats.movelist[0] = 0;
733 programStats.depth = 0;
734 programStats.nr_moves = 0;
735 programStats.moves_left = 0;
736 programStats.nodes = 0;
737 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
738 programStats.score = 0;
739 programStats.got_only_move = 0;
740 programStats.got_fail = 0;
741 programStats.line_is_book = 0;
746 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
747 if (appData.firstPlaysBlack) {
748 first.twoMachinesColor = "black\n";
749 second.twoMachinesColor = "white\n";
751 first.twoMachinesColor = "white\n";
752 second.twoMachinesColor = "black\n";
755 first.other = &second;
756 second.other = &first;
759 if(appData.timeOddsMode) {
760 norm = appData.timeOdds[0];
761 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
763 first.timeOdds = appData.timeOdds[0]/norm;
764 second.timeOdds = appData.timeOdds[1]/norm;
767 if(programVersion) free(programVersion);
768 if (appData.noChessProgram) {
769 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
770 sprintf(programVersion, "%s", PACKAGE_STRING);
772 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
773 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
774 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
779 UnloadEngine (ChessProgramState *cps)
781 /* Kill off first chess program */
782 if (cps->isr != NULL)
783 RemoveInputSource(cps->isr);
786 if (cps->pr != NoProc) {
788 DoSleep( appData.delayBeforeQuit );
789 SendToProgram("quit\n", cps);
790 DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
793 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
797 ClearOptions (ChessProgramState *cps)
800 cps->nrOptions = cps->comboCnt = 0;
801 for(i=0; i<MAX_OPTIONS; i++) {
802 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
803 cps->option[i].textValue = 0;
807 char *engineNames[] = {
808 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
809 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
811 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
812 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
817 InitEngine (ChessProgramState *cps, int n)
818 { // [HGM] all engine initialiation put in a function that does one engine
822 cps->which = engineNames[n];
823 cps->maybeThinking = FALSE;
827 cps->sendDrawOffers = 1;
829 cps->program = appData.chessProgram[n];
830 cps->host = appData.host[n];
831 cps->dir = appData.directory[n];
832 cps->initString = appData.engInitString[n];
833 cps->computerString = appData.computerString[n];
834 cps->useSigint = TRUE;
835 cps->useSigterm = TRUE;
836 cps->reuse = appData.reuse[n];
837 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
838 cps->useSetboard = FALSE;
840 cps->usePing = FALSE;
843 cps->usePlayother = FALSE;
844 cps->useColors = TRUE;
845 cps->useUsermove = FALSE;
846 cps->sendICS = FALSE;
847 cps->sendName = appData.icsActive;
848 cps->sdKludge = FALSE;
849 cps->stKludge = FALSE;
850 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
851 TidyProgramName(cps->program, cps->host, cps->tidy);
853 ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
854 cps->analysisSupport = 2; /* detect */
855 cps->analyzing = FALSE;
856 cps->initDone = FALSE;
858 cps->pseudo = appData.pseudo[n];
860 /* New features added by Tord: */
861 cps->useFEN960 = FALSE;
862 cps->useOOCastle = TRUE;
863 /* End of new features added by Tord. */
864 cps->fenOverride = appData.fenOverride[n];
866 /* [HGM] time odds: set factor for each machine */
867 cps->timeOdds = appData.timeOdds[n];
869 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
870 cps->accumulateTC = appData.accumulateTC[n];
871 cps->maxNrOfSessions = 1;
876 cps->drawDepth = appData.drawDepth[n];
877 cps->supportsNPS = UNKNOWN;
878 cps->memSize = FALSE;
879 cps->maxCores = FALSE;
880 ASSIGN(cps->egtFormats, "");
883 cps->optionSettings = appData.engOptions[n];
885 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
886 cps->isUCI = appData.isUCI[n]; /* [AS] */
887 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
890 if (appData.protocolVersion[n] > PROTOVER
891 || appData.protocolVersion[n] < 1)
896 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
897 appData.protocolVersion[n]);
898 if( (len >= MSG_SIZ) && appData.debugMode )
899 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
901 DisplayFatalError(buf, 0, 2);
905 cps->protocolVersion = appData.protocolVersion[n];
908 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
909 ParseFeatures(appData.featureDefaults, cps);
912 ChessProgramState *savCps;
920 if(WaitForEngine(savCps, LoadEngine)) return;
921 CommonEngineInit(); // recalculate time odds
922 if(gameInfo.variant != StringToVariant(appData.variant)) {
923 // we changed variant when loading the engine; this forces us to reset
924 Reset(TRUE, savCps != &first);
925 oldMode = BeginningOfGame; // to prevent restoring old mode
927 InitChessProgram(savCps, FALSE);
928 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
929 DisplayMessage("", "");
930 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
931 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
934 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
938 ReplaceEngine (ChessProgramState *cps, int n)
940 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
942 if(oldMode != BeginningOfGame) EditGameEvent();
945 appData.noChessProgram = FALSE;
946 appData.clockMode = TRUE;
949 if(n) return; // only startup first engine immediately; second can wait
950 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
954 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
955 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
957 static char resetOptions[] =
958 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
959 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
960 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
961 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
964 FloatToFront(char **list, char *engineLine)
966 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
968 if(appData.recentEngines <= 0) return;
969 TidyProgramName(engineLine, "localhost", tidy+1);
970 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
971 strncpy(buf+1, *list, MSG_SIZ-50);
972 if(p = strstr(buf, tidy)) { // tidy name appears in list
973 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
974 while(*p++ = *++q); // squeeze out
976 strcat(tidy, buf+1); // put list behind tidy name
977 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
978 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
979 ASSIGN(*list, tidy+1);
982 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
985 Load (ChessProgramState *cps, int i)
987 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
988 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
989 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
990 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
991 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
992 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
993 appData.firstProtocolVersion = PROTOVER;
994 ParseArgsFromString(buf);
996 ReplaceEngine(cps, i);
997 FloatToFront(&appData.recentEngineList, engineLine);
1001 while(q = strchr(p, SLASH)) p = q+1;
1002 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1003 if(engineDir[0] != NULLCHAR) {
1004 ASSIGN(appData.directory[i], engineDir); p = engineName;
1005 } else if(p != engineName) { // derive directory from engine path, when not given
1007 ASSIGN(appData.directory[i], engineName);
1009 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1010 } else { ASSIGN(appData.directory[i], "."); }
1011 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1013 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1014 snprintf(command, MSG_SIZ, "%s %s", p, params);
1017 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1018 ASSIGN(appData.chessProgram[i], p);
1019 appData.isUCI[i] = isUCI;
1020 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1021 appData.hasOwnBookUCI[i] = hasBook;
1022 if(!nickName[0]) useNick = FALSE;
1023 if(useNick) ASSIGN(appData.pgnName[i], nickName);
1027 q = firstChessProgramNames;
1028 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1029 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1030 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1031 quote, p, quote, appData.directory[i],
1032 useNick ? " -fn \"" : "",
1033 useNick ? nickName : "",
1034 useNick ? "\"" : "",
1035 v1 ? " -firstProtocolVersion 1" : "",
1036 hasBook ? "" : " -fNoOwnBookUCI",
1037 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1038 storeVariant ? " -variant " : "",
1039 storeVariant ? VariantName(gameInfo.variant) : "");
1040 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1041 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1042 if(insert != q) insert[-1] = NULLCHAR;
1043 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1045 FloatToFront(&appData.recentEngineList, buf);
1047 ReplaceEngine(cps, i);
1053 int matched, min, sec;
1055 * Parse timeControl resource
1057 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1058 appData.movesPerSession)) {
1060 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1061 DisplayFatalError(buf, 0, 2);
1065 * Parse searchTime resource
1067 if (*appData.searchTime != NULLCHAR) {
1068 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1070 searchTime = min * 60;
1071 } else if (matched == 2) {
1072 searchTime = min * 60 + sec;
1075 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1076 DisplayFatalError(buf, 0, 2);
1085 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1086 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1088 GetTimeMark(&programStartTime);
1089 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1090 appData.seedBase = random() + (random()<<15);
1091 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1093 ClearProgramStats();
1094 programStats.ok_to_send = 1;
1095 programStats.seen_stat = 0;
1098 * Initialize game list
1104 * Internet chess server status
1106 if (appData.icsActive) {
1107 appData.matchMode = FALSE;
1108 appData.matchGames = 0;
1110 appData.noChessProgram = !appData.zippyPlay;
1112 appData.zippyPlay = FALSE;
1113 appData.zippyTalk = FALSE;
1114 appData.noChessProgram = TRUE;
1116 if (*appData.icsHelper != NULLCHAR) {
1117 appData.useTelnet = TRUE;
1118 appData.telnetProgram = appData.icsHelper;
1121 appData.zippyTalk = appData.zippyPlay = FALSE;
1124 /* [AS] Initialize pv info list [HGM] and game state */
1128 for( i=0; i<=framePtr; i++ ) {
1129 pvInfoList[i].depth = -1;
1130 boards[i][EP_STATUS] = EP_NONE;
1131 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1137 /* [AS] Adjudication threshold */
1138 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1140 InitEngine(&first, 0);
1141 InitEngine(&second, 1);
1144 pairing.which = "pairing"; // pairing engine
1145 pairing.pr = NoProc;
1147 pairing.program = appData.pairingEngine;
1148 pairing.host = "localhost";
1151 if (appData.icsActive) {
1152 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1153 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1154 appData.clockMode = FALSE;
1155 first.sendTime = second.sendTime = 0;
1159 /* Override some settings from environment variables, for backward
1160 compatibility. Unfortunately it's not feasible to have the env
1161 vars just set defaults, at least in xboard. Ugh.
1163 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1168 if (!appData.icsActive) {
1172 /* Check for variants that are supported only in ICS mode,
1173 or not at all. Some that are accepted here nevertheless
1174 have bugs; see comments below.
1176 VariantClass variant = StringToVariant(appData.variant);
1178 case VariantBughouse: /* need four players and two boards */
1179 case VariantKriegspiel: /* need to hide pieces and move details */
1180 /* case VariantFischeRandom: (Fabien: moved below) */
1181 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1182 if( (len >= MSG_SIZ) && appData.debugMode )
1183 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1185 DisplayFatalError(buf, 0, 2);
1188 case VariantUnknown:
1189 case VariantLoadable:
1199 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1200 if( (len >= MSG_SIZ) && appData.debugMode )
1201 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1203 DisplayFatalError(buf, 0, 2);
1206 case VariantNormal: /* definitely works! */
1207 if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1208 safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1211 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1212 case VariantFairy: /* [HGM] TestLegality definitely off! */
1213 case VariantGothic: /* [HGM] should work */
1214 case VariantCapablanca: /* [HGM] should work */
1215 case VariantCourier: /* [HGM] initial forced moves not implemented */
1216 case VariantShogi: /* [HGM] could still mate with pawn drop */
1217 case VariantChu: /* [HGM] experimental */
1218 case VariantKnightmate: /* [HGM] should work */
1219 case VariantCylinder: /* [HGM] untested */
1220 case VariantFalcon: /* [HGM] untested */
1221 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1222 offboard interposition not understood */
1223 case VariantWildCastle: /* pieces not automatically shuffled */
1224 case VariantNoCastle: /* pieces not automatically shuffled */
1225 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1226 case VariantLosers: /* should work except for win condition,
1227 and doesn't know captures are mandatory */
1228 case VariantSuicide: /* should work except for win condition,
1229 and doesn't know captures are mandatory */
1230 case VariantGiveaway: /* should work except for win condition,
1231 and doesn't know captures are mandatory */
1232 case VariantTwoKings: /* should work */
1233 case VariantAtomic: /* should work except for win condition */
1234 case Variant3Check: /* should work except for win condition */
1235 case VariantShatranj: /* should work except for all win conditions */
1236 case VariantMakruk: /* should work except for draw countdown */
1237 case VariantASEAN : /* should work except for draw countdown */
1238 case VariantBerolina: /* might work if TestLegality is off */
1239 case VariantCapaRandom: /* should work */
1240 case VariantJanus: /* should work */
1241 case VariantSuper: /* experimental */
1242 case VariantGreat: /* experimental, requires legality testing to be off */
1243 case VariantSChess: /* S-Chess, should work */
1244 case VariantGrand: /* should work */
1245 case VariantSpartan: /* should work */
1246 case VariantLion: /* should work */
1247 case VariantChuChess: /* should work */
1255 NextIntegerFromString (char ** str, long * value)
1260 while( *s == ' ' || *s == '\t' ) {
1266 if( *s >= '0' && *s <= '9' ) {
1267 while( *s >= '0' && *s <= '9' ) {
1268 *value = *value * 10 + (*s - '0');
1281 NextTimeControlFromString (char ** str, long * value)
1284 int result = NextIntegerFromString( str, &temp );
1287 *value = temp * 60; /* Minutes */
1288 if( **str == ':' ) {
1290 result = NextIntegerFromString( str, &temp );
1291 *value += temp; /* Seconds */
1299 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1300 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1301 int result = -1, type = 0; long temp, temp2;
1303 if(**str != ':') return -1; // old params remain in force!
1305 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1306 if( NextIntegerFromString( str, &temp ) ) return -1;
1307 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1310 /* time only: incremental or sudden-death time control */
1311 if(**str == '+') { /* increment follows; read it */
1313 if(**str == '!') type = *(*str)++; // Bronstein TC
1314 if(result = NextIntegerFromString( str, &temp2)) return -1;
1315 *inc = temp2 * 1000;
1316 if(**str == '.') { // read fraction of increment
1317 char *start = ++(*str);
1318 if(result = NextIntegerFromString( str, &temp2)) return -1;
1320 while(start++ < *str) temp2 /= 10;
1324 *moves = 0; *tc = temp * 1000; *incType = type;
1328 (*str)++; /* classical time control */
1329 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1341 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1342 { /* [HGM] get time to add from the multi-session time-control string */
1343 int incType, moves=1; /* kludge to force reading of first session */
1344 long time, increment;
1347 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1349 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1350 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1351 if(movenr == -1) return time; /* last move before new session */
1352 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1353 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1354 if(!moves) return increment; /* current session is incremental */
1355 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1356 } while(movenr >= -1); /* try again for next session */
1358 return 0; // no new time quota on this move
1362 ParseTimeControl (char *tc, float ti, int mps)
1366 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1369 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1370 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1371 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1375 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1377 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1380 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1382 snprintf(buf, MSG_SIZ, ":%s", mytc);
1384 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1386 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1391 /* Parse second time control */
1394 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1402 timeControl_2 = tc2 * 1000;
1412 timeControl = tc1 * 1000;
1415 timeIncrement = ti * 1000; /* convert to ms */
1416 movesPerSession = 0;
1419 movesPerSession = mps;
1427 if (appData.debugMode) {
1428 # ifdef __GIT_VERSION
1429 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1431 fprintf(debugFP, "Version: %s\n", programVersion);
1434 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1436 set_cont_sequence(appData.wrapContSeq);
1437 if (appData.matchGames > 0) {
1438 appData.matchMode = TRUE;
1439 } else if (appData.matchMode) {
1440 appData.matchGames = 1;
1442 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1443 appData.matchGames = appData.sameColorGames;
1444 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1445 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1446 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1449 if (appData.noChessProgram || first.protocolVersion == 1) {
1452 /* kludge: allow timeout for initial "feature" commands */
1454 DisplayMessage("", _("Starting chess program"));
1455 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1460 CalculateIndex (int index, int gameNr)
1461 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1463 if(index > 0) return index; // fixed nmber
1464 if(index == 0) return 1;
1465 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1466 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1471 LoadGameOrPosition (int gameNr)
1472 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1473 if (*appData.loadGameFile != NULLCHAR) {
1474 if (!LoadGameFromFile(appData.loadGameFile,
1475 CalculateIndex(appData.loadGameIndex, gameNr),
1476 appData.loadGameFile, FALSE)) {
1477 DisplayFatalError(_("Bad game file"), 0, 1);
1480 } else if (*appData.loadPositionFile != NULLCHAR) {
1481 if (!LoadPositionFromFile(appData.loadPositionFile,
1482 CalculateIndex(appData.loadPositionIndex, gameNr),
1483 appData.loadPositionFile)) {
1484 DisplayFatalError(_("Bad position file"), 0, 1);
1492 ReserveGame (int gameNr, char resChar)
1494 FILE *tf = fopen(appData.tourneyFile, "r+");
1495 char *p, *q, c, buf[MSG_SIZ];
1496 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1497 safeStrCpy(buf, lastMsg, MSG_SIZ);
1498 DisplayMessage(_("Pick new game"), "");
1499 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1500 ParseArgsFromFile(tf);
1501 p = q = appData.results;
1502 if(appData.debugMode) {
1503 char *r = appData.participants;
1504 fprintf(debugFP, "results = '%s'\n", p);
1505 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1506 fprintf(debugFP, "\n");
1508 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1510 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1511 safeStrCpy(q, p, strlen(p) + 2);
1512 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1513 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1514 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1515 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1518 fseek(tf, -(strlen(p)+4), SEEK_END);
1520 if(c != '"') // depending on DOS or Unix line endings we can be one off
1521 fseek(tf, -(strlen(p)+2), SEEK_END);
1522 else fseek(tf, -(strlen(p)+3), SEEK_END);
1523 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1524 DisplayMessage(buf, "");
1525 free(p); appData.results = q;
1526 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1527 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1528 int round = appData.defaultMatchGames * appData.tourneyType;
1529 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1530 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1531 UnloadEngine(&first); // next game belongs to other pairing;
1532 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1534 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1538 MatchEvent (int mode)
1539 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1541 if(matchMode) { // already in match mode: switch it off
1543 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1546 // if(gameMode != BeginningOfGame) {
1547 // DisplayError(_("You can only start a match from the initial position."), 0);
1551 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1552 /* Set up machine vs. machine match */
1554 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1555 if(appData.tourneyFile[0]) {
1557 if(nextGame > appData.matchGames) {
1559 if(strchr(appData.results, '*') == NULL) {
1561 appData.tourneyCycles++;
1562 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1564 NextTourneyGame(-1, &dummy);
1566 if(nextGame <= appData.matchGames) {
1567 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1569 ScheduleDelayedEvent(NextMatchGame, 10000);
1574 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1575 DisplayError(buf, 0);
1576 appData.tourneyFile[0] = 0;
1580 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1581 DisplayFatalError(_("Can't have a match with no chess programs"),
1586 matchGame = roundNr = 1;
1587 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1591 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1594 InitBackEnd3 P((void))
1596 GameMode initialMode;
1600 if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode && // mode involves only first engine
1601 !strcmp(appData.variant, "normal") && // no explicit variant request
1602 appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 && // no size overrides requested
1603 !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") && // but 'normal' won't work with engine
1604 !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1605 char c, *q = first.variants, *p = strchr(q, ',');
1606 if(p) *p = NULLCHAR;
1607 if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1609 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1610 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1611 ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1612 Reset(TRUE, FALSE); // and re-initialize
1617 InitChessProgram(&first, startedFromSetupPosition);
1619 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1620 free(programVersion);
1621 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1622 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1623 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1626 if (appData.icsActive) {
1628 /* [DM] Make a console window if needed [HGM] merged ifs */
1634 if (*appData.icsCommPort != NULLCHAR)
1635 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1636 appData.icsCommPort);
1638 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1639 appData.icsHost, appData.icsPort);
1641 if( (len >= MSG_SIZ) && appData.debugMode )
1642 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1644 DisplayFatalError(buf, err, 1);
1649 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1651 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1652 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1653 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1654 } else if (appData.noChessProgram) {
1660 if (*appData.cmailGameName != NULLCHAR) {
1662 OpenLoopback(&cmailPR);
1664 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1668 DisplayMessage("", "");
1669 if (StrCaseCmp(appData.initialMode, "") == 0) {
1670 initialMode = BeginningOfGame;
1671 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1672 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1673 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1674 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1677 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1678 initialMode = TwoMachinesPlay;
1679 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1680 initialMode = AnalyzeFile;
1681 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1682 initialMode = AnalyzeMode;
1683 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1684 initialMode = MachinePlaysWhite;
1685 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1686 initialMode = MachinePlaysBlack;
1687 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1688 initialMode = EditGame;
1689 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1690 initialMode = EditPosition;
1691 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1692 initialMode = Training;
1694 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1695 if( (len >= MSG_SIZ) && appData.debugMode )
1696 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1698 DisplayFatalError(buf, 0, 2);
1702 if (appData.matchMode) {
1703 if(appData.tourneyFile[0]) { // start tourney from command line
1705 if(f = fopen(appData.tourneyFile, "r")) {
1706 ParseArgsFromFile(f); // make sure tourney parmeters re known
1708 appData.clockMode = TRUE;
1710 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1713 } else if (*appData.cmailGameName != NULLCHAR) {
1714 /* Set up cmail mode */
1715 ReloadCmailMsgEvent(TRUE);
1717 /* Set up other modes */
1718 if (initialMode == AnalyzeFile) {
1719 if (*appData.loadGameFile == NULLCHAR) {
1720 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1724 if (*appData.loadGameFile != NULLCHAR) {
1725 (void) LoadGameFromFile(appData.loadGameFile,
1726 appData.loadGameIndex,
1727 appData.loadGameFile, TRUE);
1728 } else if (*appData.loadPositionFile != NULLCHAR) {
1729 (void) LoadPositionFromFile(appData.loadPositionFile,
1730 appData.loadPositionIndex,
1731 appData.loadPositionFile);
1732 /* [HGM] try to make self-starting even after FEN load */
1733 /* to allow automatic setup of fairy variants with wtm */
1734 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1735 gameMode = BeginningOfGame;
1736 setboardSpoiledMachineBlack = 1;
1738 /* [HGM] loadPos: make that every new game uses the setup */
1739 /* from file as long as we do not switch variant */
1740 if(!blackPlaysFirst) {
1741 startedFromPositionFile = TRUE;
1742 CopyBoard(filePosition, boards[0]);
1745 if (initialMode == AnalyzeMode) {
1746 if (appData.noChessProgram) {
1747 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1750 if (appData.icsActive) {
1751 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1755 } else if (initialMode == AnalyzeFile) {
1756 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1757 ShowThinkingEvent();
1759 AnalysisPeriodicEvent(1);
1760 } else if (initialMode == MachinePlaysWhite) {
1761 if (appData.noChessProgram) {
1762 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1766 if (appData.icsActive) {
1767 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1771 MachineWhiteEvent();
1772 } else if (initialMode == MachinePlaysBlack) {
1773 if (appData.noChessProgram) {
1774 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1778 if (appData.icsActive) {
1779 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1783 MachineBlackEvent();
1784 } else if (initialMode == TwoMachinesPlay) {
1785 if (appData.noChessProgram) {
1786 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1790 if (appData.icsActive) {
1791 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1796 } else if (initialMode == EditGame) {
1798 } else if (initialMode == EditPosition) {
1799 EditPositionEvent();
1800 } else if (initialMode == Training) {
1801 if (*appData.loadGameFile == NULLCHAR) {
1802 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1811 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1813 DisplayBook(current+1);
1815 MoveHistorySet( movelist, first, last, current, pvInfoList );
1817 EvalGraphSet( first, last, current, pvInfoList );
1819 MakeEngineOutputTitle();
1823 * Establish will establish a contact to a remote host.port.
1824 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1825 * used to talk to the host.
1826 * Returns 0 if okay, error code if not.
1833 if (*appData.icsCommPort != NULLCHAR) {
1834 /* Talk to the host through a serial comm port */
1835 return OpenCommPort(appData.icsCommPort, &icsPR);
1837 } else if (*appData.gateway != NULLCHAR) {
1838 if (*appData.remoteShell == NULLCHAR) {
1839 /* Use the rcmd protocol to run telnet program on a gateway host */
1840 snprintf(buf, sizeof(buf), "%s %s %s",
1841 appData.telnetProgram, appData.icsHost, appData.icsPort);
1842 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1845 /* Use the rsh program to run telnet program on a gateway host */
1846 if (*appData.remoteUser == NULLCHAR) {
1847 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1848 appData.gateway, appData.telnetProgram,
1849 appData.icsHost, appData.icsPort);
1851 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1852 appData.remoteShell, appData.gateway,
1853 appData.remoteUser, appData.telnetProgram,
1854 appData.icsHost, appData.icsPort);
1856 return StartChildProcess(buf, "", &icsPR);
1859 } else if (appData.useTelnet) {
1860 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1863 /* TCP socket interface differs somewhat between
1864 Unix and NT; handle details in the front end.
1866 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1871 EscapeExpand (char *p, char *q)
1872 { // [HGM] initstring: routine to shape up string arguments
1873 while(*p++ = *q++) if(p[-1] == '\\')
1875 case 'n': p[-1] = '\n'; break;
1876 case 'r': p[-1] = '\r'; break;
1877 case 't': p[-1] = '\t'; break;
1878 case '\\': p[-1] = '\\'; break;
1879 case 0: *p = 0; return;
1880 default: p[-1] = q[-1]; break;
1885 show_bytes (FILE *fp, char *buf, int count)
1888 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1889 fprintf(fp, "\\%03o", *buf & 0xff);
1898 /* Returns an errno value */
1900 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1902 char buf[8192], *p, *q, *buflim;
1903 int left, newcount, outcount;
1905 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1906 *appData.gateway != NULLCHAR) {
1907 if (appData.debugMode) {
1908 fprintf(debugFP, ">ICS: ");
1909 show_bytes(debugFP, message, count);
1910 fprintf(debugFP, "\n");
1912 return OutputToProcess(pr, message, count, outError);
1915 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1922 if (appData.debugMode) {
1923 fprintf(debugFP, ">ICS: ");
1924 show_bytes(debugFP, buf, newcount);
1925 fprintf(debugFP, "\n");
1927 outcount = OutputToProcess(pr, buf, newcount, outError);
1928 if (outcount < newcount) return -1; /* to be sure */
1935 } else if (((unsigned char) *p) == TN_IAC) {
1936 *q++ = (char) TN_IAC;
1943 if (appData.debugMode) {
1944 fprintf(debugFP, ">ICS: ");
1945 show_bytes(debugFP, buf, newcount);
1946 fprintf(debugFP, "\n");
1948 outcount = OutputToProcess(pr, buf, newcount, outError);
1949 if (outcount < newcount) return -1; /* to be sure */
1954 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1956 int outError, outCount;
1957 static int gotEof = 0;
1960 /* Pass data read from player on to ICS */
1963 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1964 if (outCount < count) {
1965 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1967 if(have_sent_ICS_logon == 2) {
1968 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1969 fprintf(ini, "%s", message);
1970 have_sent_ICS_logon = 3;
1972 have_sent_ICS_logon = 1;
1973 } else if(have_sent_ICS_logon == 3) {
1974 fprintf(ini, "%s", message);
1976 have_sent_ICS_logon = 1;
1978 } else if (count < 0) {
1979 RemoveInputSource(isr);
1980 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1981 } else if (gotEof++ > 0) {
1982 RemoveInputSource(isr);
1983 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1989 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1990 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1991 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1992 SendToICS("date\n");
1993 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1996 /* added routine for printf style output to ics */
1998 ics_printf (char *format, ...)
2000 char buffer[MSG_SIZ];
2003 va_start(args, format);
2004 vsnprintf(buffer, sizeof(buffer), format, args);
2005 buffer[sizeof(buffer)-1] = '\0';
2013 int count, outCount, outError;
2015 if (icsPR == NoProc) return;
2018 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2019 if (outCount < count) {
2020 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2024 /* This is used for sending logon scripts to the ICS. Sending
2025 without a delay causes problems when using timestamp on ICC
2026 (at least on my machine). */
2028 SendToICSDelayed (char *s, long msdelay)
2030 int count, outCount, outError;
2032 if (icsPR == NoProc) return;
2035 if (appData.debugMode) {
2036 fprintf(debugFP, ">ICS: ");
2037 show_bytes(debugFP, s, count);
2038 fprintf(debugFP, "\n");
2040 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2042 if (outCount < count) {
2043 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2048 /* Remove all highlighting escape sequences in s
2049 Also deletes any suffix starting with '('
2052 StripHighlightAndTitle (char *s)
2054 static char retbuf[MSG_SIZ];
2057 while (*s != NULLCHAR) {
2058 while (*s == '\033') {
2059 while (*s != NULLCHAR && !isalpha(*s)) s++;
2060 if (*s != NULLCHAR) s++;
2062 while (*s != NULLCHAR && *s != '\033') {
2063 if (*s == '(' || *s == '[') {
2074 /* Remove all highlighting escape sequences in s */
2076 StripHighlight (char *s)
2078 static char retbuf[MSG_SIZ];
2081 while (*s != NULLCHAR) {
2082 while (*s == '\033') {
2083 while (*s != NULLCHAR && !isalpha(*s)) s++;
2084 if (*s != NULLCHAR) s++;
2086 while (*s != NULLCHAR && *s != '\033') {
2094 char engineVariant[MSG_SIZ];
2095 char *variantNames[] = VARIANT_NAMES;
2097 VariantName (VariantClass v)
2099 if(v == VariantUnknown || *engineVariant) return engineVariant;
2100 return variantNames[v];
2104 /* Identify a variant from the strings the chess servers use or the
2105 PGN Variant tag names we use. */
2107 StringToVariant (char *e)
2111 VariantClass v = VariantNormal;
2112 int i, found = FALSE;
2113 char buf[MSG_SIZ], c;
2118 /* [HGM] skip over optional board-size prefixes */
2119 if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2120 sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2121 while( *e++ != '_');
2124 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2128 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2129 if (p = StrCaseStr(e, variantNames[i])) {
2130 if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2131 v = (VariantClass) i;
2138 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2139 || StrCaseStr(e, "wild/fr")
2140 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2141 v = VariantFischeRandom;
2142 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2143 (i = 1, p = StrCaseStr(e, "w"))) {
2145 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2152 case 0: /* FICS only, actually */
2154 /* Castling legal even if K starts on d-file */
2155 v = VariantWildCastle;
2160 /* Castling illegal even if K & R happen to start in
2161 normal positions. */
2162 v = VariantNoCastle;
2175 /* Castling legal iff K & R start in normal positions */
2181 /* Special wilds for position setup; unclear what to do here */
2182 v = VariantLoadable;
2185 /* Bizarre ICC game */
2186 v = VariantTwoKings;
2189 v = VariantKriegspiel;
2195 v = VariantFischeRandom;
2198 v = VariantCrazyhouse;
2201 v = VariantBughouse;
2207 /* Not quite the same as FICS suicide! */
2208 v = VariantGiveaway;
2214 v = VariantShatranj;
2217 /* Temporary names for future ICC types. The name *will* change in
2218 the next xboard/WinBoard release after ICC defines it. */
2256 v = VariantCapablanca;
2259 v = VariantKnightmate;
2265 v = VariantCylinder;
2271 v = VariantCapaRandom;
2274 v = VariantBerolina;
2286 /* Found "wild" or "w" in the string but no number;
2287 must assume it's normal chess. */
2291 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2292 if( (len >= MSG_SIZ) && appData.debugMode )
2293 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2295 DisplayError(buf, 0);
2301 if (appData.debugMode) {
2302 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2303 e, wnum, VariantName(v));
2308 static int leftover_start = 0, leftover_len = 0;
2309 char star_match[STAR_MATCH_N][MSG_SIZ];
2311 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2312 advance *index beyond it, and set leftover_start to the new value of
2313 *index; else return FALSE. If pattern contains the character '*', it
2314 matches any sequence of characters not containing '\r', '\n', or the
2315 character following the '*' (if any), and the matched sequence(s) are
2316 copied into star_match.
2319 looking_at ( char *buf, int *index, char *pattern)
2321 char *bufp = &buf[*index], *patternp = pattern;
2323 char *matchp = star_match[0];
2326 if (*patternp == NULLCHAR) {
2327 *index = leftover_start = bufp - buf;
2331 if (*bufp == NULLCHAR) return FALSE;
2332 if (*patternp == '*') {
2333 if (*bufp == *(patternp + 1)) {
2335 matchp = star_match[++star_count];
2339 } else if (*bufp == '\n' || *bufp == '\r') {
2341 if (*patternp == NULLCHAR)
2346 *matchp++ = *bufp++;
2350 if (*patternp != *bufp) return FALSE;
2357 SendToPlayer (char *data, int length)
2359 int error, outCount;
2360 outCount = OutputToProcess(NoProc, data, length, &error);
2361 if (outCount < length) {
2362 DisplayFatalError(_("Error writing to display"), error, 1);
2367 PackHolding (char packed[], char *holding)
2377 switch (runlength) {
2388 sprintf(q, "%d", runlength);
2400 /* Telnet protocol requests from the front end */
2402 TelnetRequest (unsigned char ddww, unsigned char option)
2404 unsigned char msg[3];
2405 int outCount, outError;
2407 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2409 if (appData.debugMode) {
2410 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2426 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2435 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2438 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2443 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2445 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2452 if (!appData.icsActive) return;
2453 TelnetRequest(TN_DO, TN_ECHO);
2459 if (!appData.icsActive) return;
2460 TelnetRequest(TN_DONT, TN_ECHO);
2464 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2466 /* put the holdings sent to us by the server on the board holdings area */
2467 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2471 if(gameInfo.holdingsWidth < 2) return;
2472 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2473 return; // prevent overwriting by pre-board holdings
2475 if( (int)lowestPiece >= BlackPawn ) {
2478 holdingsStartRow = BOARD_HEIGHT-1;
2481 holdingsColumn = BOARD_WIDTH-1;
2482 countsColumn = BOARD_WIDTH-2;
2483 holdingsStartRow = 0;
2487 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2488 board[i][holdingsColumn] = EmptySquare;
2489 board[i][countsColumn] = (ChessSquare) 0;
2491 while( (p=*holdings++) != NULLCHAR ) {
2492 piece = CharToPiece( ToUpper(p) );
2493 if(piece == EmptySquare) continue;
2494 /*j = (int) piece - (int) WhitePawn;*/
2495 j = PieceToNumber(piece);
2496 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2497 if(j < 0) continue; /* should not happen */
2498 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2499 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2500 board[holdingsStartRow+j*direction][countsColumn]++;
2506 VariantSwitch (Board board, VariantClass newVariant)
2508 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2509 static Board oldBoard;
2511 startedFromPositionFile = FALSE;
2512 if(gameInfo.variant == newVariant) return;
2514 /* [HGM] This routine is called each time an assignment is made to
2515 * gameInfo.variant during a game, to make sure the board sizes
2516 * are set to match the new variant. If that means adding or deleting
2517 * holdings, we shift the playing board accordingly
2518 * This kludge is needed because in ICS observe mode, we get boards
2519 * of an ongoing game without knowing the variant, and learn about the
2520 * latter only later. This can be because of the move list we requested,
2521 * in which case the game history is refilled from the beginning anyway,
2522 * but also when receiving holdings of a crazyhouse game. In the latter
2523 * case we want to add those holdings to the already received position.
2527 if (appData.debugMode) {
2528 fprintf(debugFP, "Switch board from %s to %s\n",
2529 VariantName(gameInfo.variant), VariantName(newVariant));
2530 setbuf(debugFP, NULL);
2532 shuffleOpenings = 0; /* [HGM] shuffle */
2533 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2537 newWidth = 9; newHeight = 9;
2538 gameInfo.holdingsSize = 7;
2539 case VariantBughouse:
2540 case VariantCrazyhouse:
2541 newHoldingsWidth = 2; break;
2545 newHoldingsWidth = 2;
2546 gameInfo.holdingsSize = 8;
2549 case VariantCapablanca:
2550 case VariantCapaRandom:
2553 newHoldingsWidth = gameInfo.holdingsSize = 0;
2556 if(newWidth != gameInfo.boardWidth ||
2557 newHeight != gameInfo.boardHeight ||
2558 newHoldingsWidth != gameInfo.holdingsWidth ) {
2560 /* shift position to new playing area, if needed */
2561 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2562 for(i=0; i<BOARD_HEIGHT; i++)
2563 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2564 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2566 for(i=0; i<newHeight; i++) {
2567 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2568 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2570 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2571 for(i=0; i<BOARD_HEIGHT; i++)
2572 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2573 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2576 board[HOLDINGS_SET] = 0;
2577 gameInfo.boardWidth = newWidth;
2578 gameInfo.boardHeight = newHeight;
2579 gameInfo.holdingsWidth = newHoldingsWidth;
2580 gameInfo.variant = newVariant;
2581 InitDrawingSizes(-2, 0);
2582 } else gameInfo.variant = newVariant;
2583 CopyBoard(oldBoard, board); // remember correctly formatted board
2584 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2585 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2588 static int loggedOn = FALSE;
2590 /*-- Game start info cache: --*/
2592 char gs_kind[MSG_SIZ];
2593 static char player1Name[128] = "";
2594 static char player2Name[128] = "";
2595 static char cont_seq[] = "\n\\ ";
2596 static int player1Rating = -1;
2597 static int player2Rating = -1;
2598 /*----------------------------*/
2600 ColorClass curColor = ColorNormal;
2601 int suppressKibitz = 0;
2604 Boolean soughtPending = FALSE;
2605 Boolean seekGraphUp;
2606 #define MAX_SEEK_ADS 200
2608 char *seekAdList[MAX_SEEK_ADS];
2609 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2610 float tcList[MAX_SEEK_ADS];
2611 char colorList[MAX_SEEK_ADS];
2612 int nrOfSeekAds = 0;
2613 int minRating = 1010, maxRating = 2800;
2614 int hMargin = 10, vMargin = 20, h, w;
2615 extern int squareSize, lineGap;
2620 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2621 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2622 if(r < minRating+100 && r >=0 ) r = minRating+100;
2623 if(r > maxRating) r = maxRating;
2624 if(tc < 1.f) tc = 1.f;
2625 if(tc > 95.f) tc = 95.f;
2626 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2627 y = ((double)r - minRating)/(maxRating - minRating)
2628 * (h-vMargin-squareSize/8-1) + vMargin;
2629 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2630 if(strstr(seekAdList[i], " u ")) color = 1;
2631 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2632 !strstr(seekAdList[i], "bullet") &&
2633 !strstr(seekAdList[i], "blitz") &&
2634 !strstr(seekAdList[i], "standard") ) color = 2;
2635 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2636 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2640 PlotSingleSeekAd (int i)
2646 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2648 char buf[MSG_SIZ], *ext = "";
2649 VariantClass v = StringToVariant(type);
2650 if(strstr(type, "wild")) {
2651 ext = type + 4; // append wild number
2652 if(v == VariantFischeRandom) type = "chess960"; else
2653 if(v == VariantLoadable) type = "setup"; else
2654 type = VariantName(v);
2656 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2657 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2658 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2659 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2660 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2661 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2662 seekNrList[nrOfSeekAds] = nr;
2663 zList[nrOfSeekAds] = 0;
2664 seekAdList[nrOfSeekAds++] = StrSave(buf);
2665 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2670 EraseSeekDot (int i)
2672 int x = xList[i], y = yList[i], d=squareSize/4, k;
2673 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2674 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2675 // now replot every dot that overlapped
2676 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2677 int xx = xList[k], yy = yList[k];
2678 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2679 DrawSeekDot(xx, yy, colorList[k]);
2684 RemoveSeekAd (int nr)
2687 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2689 if(seekAdList[i]) free(seekAdList[i]);
2690 seekAdList[i] = seekAdList[--nrOfSeekAds];
2691 seekNrList[i] = seekNrList[nrOfSeekAds];
2692 ratingList[i] = ratingList[nrOfSeekAds];
2693 colorList[i] = colorList[nrOfSeekAds];
2694 tcList[i] = tcList[nrOfSeekAds];
2695 xList[i] = xList[nrOfSeekAds];
2696 yList[i] = yList[nrOfSeekAds];
2697 zList[i] = zList[nrOfSeekAds];
2698 seekAdList[nrOfSeekAds] = NULL;
2704 MatchSoughtLine (char *line)
2706 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2707 int nr, base, inc, u=0; char dummy;
2709 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2710 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2712 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2713 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2714 // match: compact and save the line
2715 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2725 if(!seekGraphUp) return FALSE;
2726 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2727 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap + 2*border;
2729 DrawSeekBackground(0, 0, w, h);
2730 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2731 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2732 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2733 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2735 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2738 snprintf(buf, MSG_SIZ, "%d", i);
2739 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2742 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2743 for(i=1; i<100; i+=(i<10?1:5)) {
2744 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2745 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2746 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2748 snprintf(buf, MSG_SIZ, "%d", i);
2749 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2752 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2757 SeekGraphClick (ClickType click, int x, int y, int moving)
2759 static int lastDown = 0, displayed = 0, lastSecond;
2760 if(y < 0) return FALSE;
2761 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2762 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2763 if(!seekGraphUp) return FALSE;
2764 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2765 DrawPosition(TRUE, NULL);
2768 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2769 if(click == Release || moving) return FALSE;
2771 soughtPending = TRUE;
2772 SendToICS(ics_prefix);
2773 SendToICS("sought\n"); // should this be "sought all"?
2774 } else { // issue challenge based on clicked ad
2775 int dist = 10000; int i, closest = 0, second = 0;
2776 for(i=0; i<nrOfSeekAds; i++) {
2777 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2778 if(d < dist) { dist = d; closest = i; }
2779 second += (d - zList[i] < 120); // count in-range ads
2780 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2784 second = (second > 1);
2785 if(displayed != closest || second != lastSecond) {
2786 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2787 lastSecond = second; displayed = closest;
2789 if(click == Press) {
2790 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2793 } // on press 'hit', only show info
2794 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2795 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2796 SendToICS(ics_prefix);
2798 return TRUE; // let incoming board of started game pop down the graph
2799 } else if(click == Release) { // release 'miss' is ignored
2800 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2801 if(moving == 2) { // right up-click
2802 nrOfSeekAds = 0; // refresh graph
2803 soughtPending = TRUE;
2804 SendToICS(ics_prefix);
2805 SendToICS("sought\n"); // should this be "sought all"?
2808 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2809 // press miss or release hit 'pop down' seek graph
2810 seekGraphUp = FALSE;
2811 DrawPosition(TRUE, NULL);
2817 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2819 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2820 #define STARTED_NONE 0
2821 #define STARTED_MOVES 1
2822 #define STARTED_BOARD 2
2823 #define STARTED_OBSERVE 3
2824 #define STARTED_HOLDINGS 4
2825 #define STARTED_CHATTER 5
2826 #define STARTED_COMMENT 6
2827 #define STARTED_MOVES_NOHIDE 7
2829 static int started = STARTED_NONE;
2830 static char parse[20000];
2831 static int parse_pos = 0;
2832 static char buf[BUF_SIZE + 1];
2833 static int firstTime = TRUE, intfSet = FALSE;
2834 static ColorClass prevColor = ColorNormal;
2835 static int savingComment = FALSE;
2836 static int cmatch = 0; // continuation sequence match
2843 int backup; /* [DM] For zippy color lines */
2845 char talker[MSG_SIZ]; // [HGM] chat
2846 int channel, collective=0;
2848 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2850 if (appData.debugMode) {
2852 fprintf(debugFP, "<ICS: ");
2853 show_bytes(debugFP, data, count);
2854 fprintf(debugFP, "\n");
2858 if (appData.debugMode) { int f = forwardMostMove;
2859 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2860 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2861 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2864 /* If last read ended with a partial line that we couldn't parse,
2865 prepend it to the new read and try again. */
2866 if (leftover_len > 0) {
2867 for (i=0; i<leftover_len; i++)
2868 buf[i] = buf[leftover_start + i];
2871 /* copy new characters into the buffer */
2872 bp = buf + leftover_len;
2873 buf_len=leftover_len;
2874 for (i=0; i<count; i++)
2877 if (data[i] == '\r')
2880 // join lines split by ICS?
2881 if (!appData.noJoin)
2884 Joining just consists of finding matches against the
2885 continuation sequence, and discarding that sequence
2886 if found instead of copying it. So, until a match
2887 fails, there's nothing to do since it might be the
2888 complete sequence, and thus, something we don't want
2891 if (data[i] == cont_seq[cmatch])
2894 if (cmatch == strlen(cont_seq))
2896 cmatch = 0; // complete match. just reset the counter
2899 it's possible for the ICS to not include the space
2900 at the end of the last word, making our [correct]
2901 join operation fuse two separate words. the server
2902 does this when the space occurs at the width setting.
2904 if (!buf_len || buf[buf_len-1] != ' ')
2915 match failed, so we have to copy what matched before
2916 falling through and copying this character. In reality,
2917 this will only ever be just the newline character, but
2918 it doesn't hurt to be precise.
2920 strncpy(bp, cont_seq, cmatch);
2932 buf[buf_len] = NULLCHAR;
2933 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2938 while (i < buf_len) {
2939 /* Deal with part of the TELNET option negotiation
2940 protocol. We refuse to do anything beyond the
2941 defaults, except that we allow the WILL ECHO option,
2942 which ICS uses to turn off password echoing when we are
2943 directly connected to it. We reject this option
2944 if localLineEditing mode is on (always on in xboard)
2945 and we are talking to port 23, which might be a real
2946 telnet server that will try to keep WILL ECHO on permanently.
2948 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2949 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2950 unsigned char option;
2952 switch ((unsigned char) buf[++i]) {
2954 if (appData.debugMode)
2955 fprintf(debugFP, "\n<WILL ");
2956 switch (option = (unsigned char) buf[++i]) {
2958 if (appData.debugMode)
2959 fprintf(debugFP, "ECHO ");
2960 /* Reply only if this is a change, according
2961 to the protocol rules. */
2962 if (remoteEchoOption) break;
2963 if (appData.localLineEditing &&
2964 atoi(appData.icsPort) == TN_PORT) {
2965 TelnetRequest(TN_DONT, TN_ECHO);
2968 TelnetRequest(TN_DO, TN_ECHO);
2969 remoteEchoOption = TRUE;
2973 if (appData.debugMode)
2974 fprintf(debugFP, "%d ", option);
2975 /* Whatever this is, we don't want it. */
2976 TelnetRequest(TN_DONT, option);
2981 if (appData.debugMode)
2982 fprintf(debugFP, "\n<WONT ");
2983 switch (option = (unsigned char) buf[++i]) {
2985 if (appData.debugMode)
2986 fprintf(debugFP, "ECHO ");
2987 /* Reply only if this is a change, according
2988 to the protocol rules. */
2989 if (!remoteEchoOption) break;
2991 TelnetRequest(TN_DONT, TN_ECHO);
2992 remoteEchoOption = FALSE;
2995 if (appData.debugMode)
2996 fprintf(debugFP, "%d ", (unsigned char) option);
2997 /* Whatever this is, it must already be turned
2998 off, because we never agree to turn on
2999 anything non-default, so according to the
3000 protocol rules, we don't reply. */
3005 if (appData.debugMode)
3006 fprintf(debugFP, "\n<DO ");
3007 switch (option = (unsigned char) buf[++i]) {
3009 /* Whatever this is, we refuse to do it. */
3010 if (appData.debugMode)
3011 fprintf(debugFP, "%d ", option);
3012 TelnetRequest(TN_WONT, option);
3017 if (appData.debugMode)
3018 fprintf(debugFP, "\n<DONT ");
3019 switch (option = (unsigned char) buf[++i]) {
3021 if (appData.debugMode)
3022 fprintf(debugFP, "%d ", option);
3023 /* Whatever this is, we are already not doing
3024 it, because we never agree to do anything
3025 non-default, so according to the protocol
3026 rules, we don't reply. */
3031 if (appData.debugMode)
3032 fprintf(debugFP, "\n<IAC ");
3033 /* Doubled IAC; pass it through */
3037 if (appData.debugMode)
3038 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3039 /* Drop all other telnet commands on the floor */
3042 if (oldi > next_out)
3043 SendToPlayer(&buf[next_out], oldi - next_out);
3049 /* OK, this at least will *usually* work */
3050 if (!loggedOn && looking_at(buf, &i, "ics%")) {
3054 if (loggedOn && !intfSet) {
3055 if (ics_type == ICS_ICC) {
3056 snprintf(str, MSG_SIZ,
3057 "/set-quietly interface %s\n/set-quietly style 12\n",
3059 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3060 strcat(str, "/set-2 51 1\n/set seek 1\n");
3061 } else if (ics_type == ICS_CHESSNET) {
3062 snprintf(str, MSG_SIZ, "/style 12\n");
3064 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3065 strcat(str, programVersion);
3066 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3067 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3068 strcat(str, "$iset seekremove 1\n$set seek 1\n");
3070 strcat(str, "$iset nohighlight 1\n");
3072 strcat(str, "$iset lock 1\n$style 12\n");
3075 NotifyFrontendLogin();
3079 if (started == STARTED_COMMENT) {
3080 /* Accumulate characters in comment */
3081 parse[parse_pos++] = buf[i];
3082 if (buf[i] == '\n') {
3083 parse[parse_pos] = NULLCHAR;
3084 if(chattingPartner>=0) {
3086 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3087 OutputChatMessage(chattingPartner, mess);
3088 if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3090 talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3091 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3092 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3093 OutputChatMessage(p, mess);
3097 chattingPartner = -1;
3098 if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3101 if(!suppressKibitz) // [HGM] kibitz
3102 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3103 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3104 int nrDigit = 0, nrAlph = 0, j;
3105 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3106 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3107 parse[parse_pos] = NULLCHAR;
3108 // try to be smart: if it does not look like search info, it should go to
3109 // ICS interaction window after all, not to engine-output window.
3110 for(j=0; j<parse_pos; j++) { // count letters and digits
3111 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3112 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3113 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3115 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3116 int depth=0; float score;
3117 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3118 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3119 pvInfoList[forwardMostMove-1].depth = depth;
3120 pvInfoList[forwardMostMove-1].score = 100*score;
3122 OutputKibitz(suppressKibitz, parse);
3125 if(gameMode == IcsObserving) // restore original ICS messages
3126 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3127 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3129 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3130 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3131 SendToPlayer(tmp, strlen(tmp));
3133 next_out = i+1; // [HGM] suppress printing in ICS window
3135 started = STARTED_NONE;
3137 /* Don't match patterns against characters in comment */
3142 if (started == STARTED_CHATTER) {
3143 if (buf[i] != '\n') {
3144 /* Don't match patterns against characters in chatter */
3148 started = STARTED_NONE;
3149 if(suppressKibitz) next_out = i+1;
3152 /* Kludge to deal with rcmd protocol */
3153 if (firstTime && looking_at(buf, &i, "\001*")) {
3154 DisplayFatalError(&buf[1], 0, 1);
3160 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3163 if (appData.debugMode)
3164 fprintf(debugFP, "ics_type %d\n", ics_type);
3167 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3168 ics_type = ICS_FICS;
3170 if (appData.debugMode)
3171 fprintf(debugFP, "ics_type %d\n", ics_type);
3174 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3175 ics_type = ICS_CHESSNET;
3177 if (appData.debugMode)
3178 fprintf(debugFP, "ics_type %d\n", ics_type);
3183 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3184 looking_at(buf, &i, "Logging you in as \"*\"") ||
3185 looking_at(buf, &i, "will be \"*\""))) {
3186 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3190 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3192 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3193 DisplayIcsInteractionTitle(buf);
3194 have_set_title = TRUE;
3197 /* skip finger notes */
3198 if (started == STARTED_NONE &&
3199 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3200 (buf[i] == '1' && buf[i+1] == '0')) &&
3201 buf[i+2] == ':' && buf[i+3] == ' ') {
3202 started = STARTED_CHATTER;
3208 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3209 if(appData.seekGraph) {
3210 if(soughtPending && MatchSoughtLine(buf+i)) {
3211 i = strstr(buf+i, "rated") - buf;
3212 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3213 next_out = leftover_start = i;
3214 started = STARTED_CHATTER;
3215 suppressKibitz = TRUE;
3218 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3219 && looking_at(buf, &i, "* ads displayed")) {
3220 soughtPending = FALSE;
3225 if(appData.autoRefresh) {
3226 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3227 int s = (ics_type == ICS_ICC); // ICC format differs
3229 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3230 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3231 looking_at(buf, &i, "*% "); // eat prompt
3232 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3233 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3234 next_out = i; // suppress
3237 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3238 char *p = star_match[0];
3240 if(seekGraphUp) RemoveSeekAd(atoi(p));
3241 while(*p && *p++ != ' '); // next
3243 looking_at(buf, &i, "*% "); // eat prompt
3244 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3251 /* skip formula vars */
3252 if (started == STARTED_NONE &&
3253 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3254 started = STARTED_CHATTER;
3259 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3260 if (appData.autoKibitz && started == STARTED_NONE &&
3261 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3262 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3263 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3264 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3265 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3266 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3267 suppressKibitz = TRUE;
3268 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3270 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3271 && (gameMode == IcsPlayingWhite)) ||
3272 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3273 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3274 started = STARTED_CHATTER; // own kibitz we simply discard
3276 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3277 parse_pos = 0; parse[0] = NULLCHAR;
3278 savingComment = TRUE;
3279 suppressKibitz = gameMode != IcsObserving ? 2 :
3280 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3284 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3285 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3286 && atoi(star_match[0])) {
3287 // suppress the acknowledgements of our own autoKibitz
3289 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3290 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3291 SendToPlayer(star_match[0], strlen(star_match[0]));
3292 if(looking_at(buf, &i, "*% ")) // eat prompt
3293 suppressKibitz = FALSE;
3297 } // [HGM] kibitz: end of patch
3299 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3301 // [HGM] chat: intercept tells by users for which we have an open chat window
3303 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3304 looking_at(buf, &i, "* whispers:") ||
3305 looking_at(buf, &i, "* kibitzes:") ||
3306 looking_at(buf, &i, "* shouts:") ||
3307 looking_at(buf, &i, "* c-shouts:") ||
3308 looking_at(buf, &i, "--> * ") ||
3309 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3310 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3311 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3312 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3314 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3315 chattingPartner = -1; collective = 0;
3317 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3318 for(p=0; p<MAX_CHAT; p++) {
3320 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3321 talker[0] = '['; strcat(talker, "] ");
3322 Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3323 chattingPartner = p; break;
3326 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3327 for(p=0; p<MAX_CHAT; p++) {
3329 if(!strcmp("kibitzes", chatPartner[p])) {
3330 talker[0] = '['; strcat(talker, "] ");
3331 chattingPartner = p; break;
3334 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3335 for(p=0; p<MAX_CHAT; p++) {
3337 if(!strcmp("whispers", chatPartner[p])) {
3338 talker[0] = '['; strcat(talker, "] ");
3339 chattingPartner = p; break;
3342 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3343 if(buf[i-8] == '-' && buf[i-3] == 't')
3344 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3346 if(!strcmp("c-shouts", chatPartner[p])) {
3347 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3348 chattingPartner = p; break;
3351 if(chattingPartner < 0)
3352 for(p=0; p<MAX_CHAT; p++) {
3354 if(!strcmp("shouts", chatPartner[p])) {
3355 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3356 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3357 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3358 chattingPartner = p; break;
3362 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3363 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3365 Colorize(ColorTell, FALSE);
3366 if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3368 chattingPartner = p; break;
3370 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3371 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3372 started = STARTED_COMMENT;
3373 parse_pos = 0; parse[0] = NULLCHAR;
3374 savingComment = 3 + chattingPartner; // counts as TRUE
3375 if(collective == 3) i = oldi; else {
3376 suppressKibitz = TRUE;
3377 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3378 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3382 } // [HGM] chat: end of patch
3385 if (appData.zippyTalk || appData.zippyPlay) {
3386 /* [DM] Backup address for color zippy lines */
3388 if (loggedOn == TRUE)
3389 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3390 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3392 } // [DM] 'else { ' deleted
3394 /* Regular tells and says */
3395 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3396 looking_at(buf, &i, "* (your partner) tells you: ") ||
3397 looking_at(buf, &i, "* says: ") ||
3398 /* Don't color "message" or "messages" output */
3399 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3400 looking_at(buf, &i, "*. * at *:*: ") ||
3401 looking_at(buf, &i, "--* (*:*): ") ||
3402 /* Message notifications (same color as tells) */
3403 looking_at(buf, &i, "* has left a message ") ||
3404 looking_at(buf, &i, "* just sent you a message:\n") ||
3405 /* Whispers and kibitzes */
3406 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3407 looking_at(buf, &i, "* kibitzes: ") ||
3409 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3411 if (tkind == 1 && strchr(star_match[0], ':')) {
3412 /* Avoid "tells you:" spoofs in channels */
3415 if (star_match[0][0] == NULLCHAR ||
3416 strchr(star_match[0], ' ') ||
3417 (tkind == 3 && strchr(star_match[1], ' '))) {
3418 /* Reject bogus matches */
3421 if (appData.colorize) {
3422 if (oldi > next_out) {
3423 SendToPlayer(&buf[next_out], oldi - next_out);
3428 Colorize(ColorTell, FALSE);
3429 curColor = ColorTell;
3432 Colorize(ColorKibitz, FALSE);
3433 curColor = ColorKibitz;
3436 p = strrchr(star_match[1], '(');
3443 Colorize(ColorChannel1, FALSE);
3444 curColor = ColorChannel1;
3446 Colorize(ColorChannel, FALSE);
3447 curColor = ColorChannel;
3451 curColor = ColorNormal;
3455 if (started == STARTED_NONE && appData.autoComment &&
3456 (gameMode == IcsObserving ||
3457 gameMode == IcsPlayingWhite ||
3458 gameMode == IcsPlayingBlack)) {
3459 parse_pos = i - oldi;
3460 memcpy(parse, &buf[oldi], parse_pos);
3461 parse[parse_pos] = NULLCHAR;
3462 started = STARTED_COMMENT;
3463 savingComment = TRUE;
3464 } else if(collective != 3) {
3465 started = STARTED_CHATTER;
3466 savingComment = FALSE;
3473 if (looking_at(buf, &i, "* s-shouts: ") ||
3474 looking_at(buf, &i, "* c-shouts: ")) {
3475 if (appData.colorize) {
3476 if (oldi > next_out) {
3477 SendToPlayer(&buf[next_out], oldi - next_out);
3480 Colorize(ColorSShout, FALSE);
3481 curColor = ColorSShout;
3484 started = STARTED_CHATTER;
3488 if (looking_at(buf, &i, "--->")) {
3493 if (looking_at(buf, &i, "* shouts: ") ||
3494 looking_at(buf, &i, "--> ")) {
3495 if (appData.colorize) {
3496 if (oldi > next_out) {
3497 SendToPlayer(&buf[next_out], oldi - next_out);
3500 Colorize(ColorShout, FALSE);
3501 curColor = ColorShout;
3504 started = STARTED_CHATTER;
3508 if (looking_at( buf, &i, "Challenge:")) {
3509 if (appData.colorize) {
3510 if (oldi > next_out) {
3511 SendToPlayer(&buf[next_out], oldi - next_out);
3514 Colorize(ColorChallenge, FALSE);
3515 curColor = ColorChallenge;
3521 if (looking_at(buf, &i, "* offers you") ||
3522 looking_at(buf, &i, "* offers to be") ||
3523 looking_at(buf, &i, "* would like to") ||
3524 looking_at(buf, &i, "* requests to") ||
3525 looking_at(buf, &i, "Your opponent offers") ||
3526 looking_at(buf, &i, "Your opponent requests")) {
3528 if (appData.colorize) {
3529 if (oldi > next_out) {
3530 SendToPlayer(&buf[next_out], oldi - next_out);
3533 Colorize(ColorRequest, FALSE);
3534 curColor = ColorRequest;
3539 if (looking_at(buf, &i, "* (*) seeking")) {
3540 if (appData.colorize) {
3541 if (oldi > next_out) {
3542 SendToPlayer(&buf[next_out], oldi - next_out);
3545 Colorize(ColorSeek, FALSE);
3546 curColor = ColorSeek;
3551 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3553 if (looking_at(buf, &i, "\\ ")) {
3554 if (prevColor != ColorNormal) {
3555 if (oldi > next_out) {
3556 SendToPlayer(&buf[next_out], oldi - next_out);
3559 Colorize(prevColor, TRUE);
3560 curColor = prevColor;
3562 if (savingComment) {
3563 parse_pos = i - oldi;
3564 memcpy(parse, &buf[oldi], parse_pos);
3565 parse[parse_pos] = NULLCHAR;
3566 started = STARTED_COMMENT;
3567 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3568 chattingPartner = savingComment - 3; // kludge to remember the box
3570 started = STARTED_CHATTER;
3575 if (looking_at(buf, &i, "Black Strength :") ||
3576 looking_at(buf, &i, "<<< style 10 board >>>") ||
3577 looking_at(buf, &i, "<10>") ||
3578 looking_at(buf, &i, "#@#")) {
3579 /* Wrong board style */
3581 SendToICS(ics_prefix);
3582 SendToICS("set style 12\n");
3583 SendToICS(ics_prefix);
3584 SendToICS("refresh\n");
3588 if (looking_at(buf, &i, "login:")) {
3589 if (!have_sent_ICS_logon) {
3591 have_sent_ICS_logon = 1;
3592 else // no init script was found
3593 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3594 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3595 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3600 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3601 (looking_at(buf, &i, "\n<12> ") ||
3602 looking_at(buf, &i, "<12> "))) {
3604 if (oldi > next_out) {
3605 SendToPlayer(&buf[next_out], oldi - next_out);
3608 started = STARTED_BOARD;
3613 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3614 looking_at(buf, &i, "<b1> ")) {
3615 if (oldi > next_out) {
3616 SendToPlayer(&buf[next_out], oldi - next_out);
3619 started = STARTED_HOLDINGS;
3624 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3626 /* Header for a move list -- first line */
3628 switch (ics_getting_history) {
3632 case BeginningOfGame:
3633 /* User typed "moves" or "oldmoves" while we
3634 were idle. Pretend we asked for these
3635 moves and soak them up so user can step
3636 through them and/or save them.
3639 gameMode = IcsObserving;
3642 ics_getting_history = H_GOT_UNREQ_HEADER;
3644 case EditGame: /*?*/
3645 case EditPosition: /*?*/
3646 /* Should above feature work in these modes too? */
3647 /* For now it doesn't */
3648 ics_getting_history = H_GOT_UNWANTED_HEADER;
3651 ics_getting_history = H_GOT_UNWANTED_HEADER;
3656 /* Is this the right one? */
3657 if (gameInfo.white && gameInfo.black &&
3658 strcmp(gameInfo.white, star_match[0]) == 0 &&
3659 strcmp(gameInfo.black, star_match[2]) == 0) {
3661 ics_getting_history = H_GOT_REQ_HEADER;
3664 case H_GOT_REQ_HEADER:
3665 case H_GOT_UNREQ_HEADER:
3666 case H_GOT_UNWANTED_HEADER:
3667 case H_GETTING_MOVES:
3668 /* Should not happen */
3669 DisplayError(_("Error gathering move list: two headers"), 0);
3670 ics_getting_history = H_FALSE;
3674 /* Save player ratings into gameInfo if needed */
3675 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3676 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3677 (gameInfo.whiteRating == -1 ||
3678 gameInfo.blackRating == -1)) {
3680 gameInfo.whiteRating = string_to_rating(star_match[1]);
3681 gameInfo.blackRating = string_to_rating(star_match[3]);
3682 if (appData.debugMode)
3683 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3684 gameInfo.whiteRating, gameInfo.blackRating);
3689 if (looking_at(buf, &i,
3690 "* * match, initial time: * minute*, increment: * second")) {
3691 /* Header for a move list -- second line */
3692 /* Initial board will follow if this is a wild game */
3693 if (gameInfo.event != NULL) free(gameInfo.event);
3694 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3695 gameInfo.event = StrSave(str);
3696 /* [HGM] we switched variant. Translate boards if needed. */
3697 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3701 if (looking_at(buf, &i, "Move ")) {
3702 /* Beginning of a move list */
3703 switch (ics_getting_history) {
3705 /* Normally should not happen */
3706 /* Maybe user hit reset while we were parsing */
3709 /* Happens if we are ignoring a move list that is not
3710 * the one we just requested. Common if the user
3711 * tries to observe two games without turning off
3714 case H_GETTING_MOVES:
3715 /* Should not happen */
3716 DisplayError(_("Error gathering move list: nested"), 0);
3717 ics_getting_history = H_FALSE;
3719 case H_GOT_REQ_HEADER:
3720 ics_getting_history = H_GETTING_MOVES;
3721 started = STARTED_MOVES;
3723 if (oldi > next_out) {
3724 SendToPlayer(&buf[next_out], oldi - next_out);
3727 case H_GOT_UNREQ_HEADER:
3728 ics_getting_history = H_GETTING_MOVES;
3729 started = STARTED_MOVES_NOHIDE;
3732 case H_GOT_UNWANTED_HEADER:
3733 ics_getting_history = H_FALSE;
3739 if (looking_at(buf, &i, "% ") ||
3740 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3741 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3742 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3743 soughtPending = FALSE;
3747 if(suppressKibitz) next_out = i;
3748 savingComment = FALSE;
3752 case STARTED_MOVES_NOHIDE:
3753 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3754 parse[parse_pos + i - oldi] = NULLCHAR;
3755 ParseGameHistory(parse);
3757 if (appData.zippyPlay && first.initDone) {
3758 FeedMovesToProgram(&first, forwardMostMove);
3759 if (gameMode == IcsPlayingWhite) {
3760 if (WhiteOnMove(forwardMostMove)) {
3761 if (first.sendTime) {
3762 if (first.useColors) {
3763 SendToProgram("black\n", &first);
3765 SendTimeRemaining(&first, TRUE);
3767 if (first.useColors) {
3768 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3770 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3771 first.maybeThinking = TRUE;
3773 if (first.usePlayother) {
3774 if (first.sendTime) {
3775 SendTimeRemaining(&first, TRUE);
3777 SendToProgram("playother\n", &first);
3783 } else if (gameMode == IcsPlayingBlack) {
3784 if (!WhiteOnMove(forwardMostMove)) {
3785 if (first.sendTime) {
3786 if (first.useColors) {
3787 SendToProgram("white\n", &first);
3789 SendTimeRemaining(&first, FALSE);
3791 if (first.useColors) {
3792 SendToProgram("black\n", &first);
3794 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3795 first.maybeThinking = TRUE;
3797 if (first.usePlayother) {
3798 if (first.sendTime) {
3799 SendTimeRemaining(&first, FALSE);
3801 SendToProgram("playother\n", &first);
3810 if (gameMode == IcsObserving && ics_gamenum == -1) {
3811 /* Moves came from oldmoves or moves command
3812 while we weren't doing anything else.
3814 currentMove = forwardMostMove;
3815 ClearHighlights();/*!!could figure this out*/
3816 flipView = appData.flipView;
3817 DrawPosition(TRUE, boards[currentMove]);
3818 DisplayBothClocks();
3819 snprintf(str, MSG_SIZ, "%s %s %s",
3820 gameInfo.white, _("vs."), gameInfo.black);
3824 /* Moves were history of an active game */
3825 if (gameInfo.resultDetails != NULL) {
3826 free(gameInfo.resultDetails);
3827 gameInfo.resultDetails = NULL;
3830 HistorySet(parseList, backwardMostMove,
3831 forwardMostMove, currentMove-1);
3832 DisplayMove(currentMove - 1);
3833 if (started == STARTED_MOVES) next_out = i;
3834 started = STARTED_NONE;
3835 ics_getting_history = H_FALSE;
3838 case STARTED_OBSERVE:
3839 started = STARTED_NONE;
3840 SendToICS(ics_prefix);
3841 SendToICS("refresh\n");
3847 if(bookHit) { // [HGM] book: simulate book reply
3848 static char bookMove[MSG_SIZ]; // a bit generous?
3850 programStats.nodes = programStats.depth = programStats.time =
3851 programStats.score = programStats.got_only_move = 0;
3852 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3854 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3855 strcat(bookMove, bookHit);
3856 HandleMachineMove(bookMove, &first);
3861 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3862 started == STARTED_HOLDINGS ||
3863 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3864 /* Accumulate characters in move list or board */
3865 parse[parse_pos++] = buf[i];
3868 /* Start of game messages. Mostly we detect start of game
3869 when the first board image arrives. On some versions
3870 of the ICS, though, we need to do a "refresh" after starting
3871 to observe in order to get the current board right away. */
3872 if (looking_at(buf, &i, "Adding game * to observation list")) {
3873 started = STARTED_OBSERVE;
3877 /* Handle auto-observe */
3878 if (appData.autoObserve &&
3879 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3880 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3882 /* Choose the player that was highlighted, if any. */
3883 if (star_match[0][0] == '\033' ||
3884 star_match[1][0] != '\033') {
3885 player = star_match[0];
3887 player = star_match[2];
3889 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3890 ics_prefix, StripHighlightAndTitle(player));
3893 /* Save ratings from notify string */
3894 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3895 player1Rating = string_to_rating(star_match[1]);
3896 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3897 player2Rating = string_to_rating(star_match[3]);
3899 if (appData.debugMode)
3901 "Ratings from 'Game notification:' %s %d, %s %d\n",
3902 player1Name, player1Rating,
3903 player2Name, player2Rating);
3908 /* Deal with automatic examine mode after a game,
3909 and with IcsObserving -> IcsExamining transition */
3910 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3911 looking_at(buf, &i, "has made you an examiner of game *")) {
3913 int gamenum = atoi(star_match[0]);
3914 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3915 gamenum == ics_gamenum) {
3916 /* We were already playing or observing this game;
3917 no need to refetch history */
3918 gameMode = IcsExamining;
3920 pauseExamForwardMostMove = forwardMostMove;
3921 } else if (currentMove < forwardMostMove) {
3922 ForwardInner(forwardMostMove);
3925 /* I don't think this case really can happen */
3926 SendToICS(ics_prefix);
3927 SendToICS("refresh\n");
3932 /* Error messages */
3933 // if (ics_user_moved) {
3934 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3935 if (looking_at(buf, &i, "Illegal move") ||
3936 looking_at(buf, &i, "Not a legal move") ||
3937 looking_at(buf, &i, "Your king is in check") ||
3938 looking_at(buf, &i, "It isn't your turn") ||
3939 looking_at(buf, &i, "It is not your move")) {
3941 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3942 currentMove = forwardMostMove-1;
3943 DisplayMove(currentMove - 1); /* before DMError */
3944 DrawPosition(FALSE, boards[currentMove]);
3945 SwitchClocks(forwardMostMove-1); // [HGM] race
3946 DisplayBothClocks();
3948 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3954 if (looking_at(buf, &i, "still have time") ||
3955 looking_at(buf, &i, "not out of time") ||
3956 looking_at(buf, &i, "either player is out of time") ||
3957 looking_at(buf, &i, "has timeseal; checking")) {
3958 /* We must have called his flag a little too soon */
3959 whiteFlag = blackFlag = FALSE;
3963 if (looking_at(buf, &i, "added * seconds to") ||
3964 looking_at(buf, &i, "seconds were added to")) {
3965 /* Update the clocks */
3966 SendToICS(ics_prefix);
3967 SendToICS("refresh\n");
3971 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3972 ics_clock_paused = TRUE;
3977 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3978 ics_clock_paused = FALSE;
3983 /* Grab player ratings from the Creating: message.
3984 Note we have to check for the special case when
3985 the ICS inserts things like [white] or [black]. */
3986 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3987 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3989 0 player 1 name (not necessarily white)
3991 2 empty, white, or black (IGNORED)
3992 3 player 2 name (not necessarily black)
3995 The names/ratings are sorted out when the game
3996 actually starts (below).
3998 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3999 player1Rating = string_to_rating(star_match[1]);
4000 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4001 player2Rating = string_to_rating(star_match[4]);
4003 if (appData.debugMode)
4005 "Ratings from 'Creating:' %s %d, %s %d\n",
4006 player1Name, player1Rating,
4007 player2Name, player2Rating);
4012 /* Improved generic start/end-of-game messages */
4013 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4014 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4015 /* If tkind == 0: */
4016 /* star_match[0] is the game number */
4017 /* [1] is the white player's name */
4018 /* [2] is the black player's name */
4019 /* For end-of-game: */
4020 /* [3] is the reason for the game end */
4021 /* [4] is a PGN end game-token, preceded by " " */
4022 /* For start-of-game: */
4023 /* [3] begins with "Creating" or "Continuing" */
4024 /* [4] is " *" or empty (don't care). */
4025 int gamenum = atoi(star_match[0]);
4026 char *whitename, *blackname, *why, *endtoken;
4027 ChessMove endtype = EndOfFile;
4030 whitename = star_match[1];
4031 blackname = star_match[2];
4032 why = star_match[3];
4033 endtoken = star_match[4];
4035 whitename = star_match[1];
4036 blackname = star_match[3];
4037 why = star_match[5];
4038 endtoken = star_match[6];
4041 /* Game start messages */
4042 if (strncmp(why, "Creating ", 9) == 0 ||
4043 strncmp(why, "Continuing ", 11) == 0) {
4044 gs_gamenum = gamenum;
4045 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4046 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4047 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4049 if (appData.zippyPlay) {
4050 ZippyGameStart(whitename, blackname);
4053 partnerBoardValid = FALSE; // [HGM] bughouse
4057 /* Game end messages */
4058 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4059 ics_gamenum != gamenum) {
4062 while (endtoken[0] == ' ') endtoken++;
4063 switch (endtoken[0]) {
4066 endtype = GameUnfinished;
4069 endtype = BlackWins;
4072 if (endtoken[1] == '/')
4073 endtype = GameIsDrawn;
4075 endtype = WhiteWins;
4078 GameEnds(endtype, why, GE_ICS);
4080 if (appData.zippyPlay && first.initDone) {
4081 ZippyGameEnd(endtype, why);
4082 if (first.pr == NoProc) {
4083 /* Start the next process early so that we'll
4084 be ready for the next challenge */
4085 StartChessProgram(&first);
4087 /* Send "new" early, in case this command takes
4088 a long time to finish, so that we'll be ready
4089 for the next challenge. */
4090 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4094 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4098 if (looking_at(buf, &i, "Removing game * from observation") ||
4099 looking_at(buf, &i, "no longer observing game *") ||
4100 looking_at(buf, &i, "Game * (*) has no examiners")) {
4101 if (gameMode == IcsObserving &&
4102 atoi(star_match[0]) == ics_gamenum)
4104 /* icsEngineAnalyze */
4105 if (appData.icsEngineAnalyze) {
4112 ics_user_moved = FALSE;
4117 if (looking_at(buf, &i, "no longer examining game *")) {
4118 if (gameMode == IcsExamining &&
4119 atoi(star_match[0]) == ics_gamenum)
4123 ics_user_moved = FALSE;
4128 /* Advance leftover_start past any newlines we find,
4129 so only partial lines can get reparsed */
4130 if (looking_at(buf, &i, "\n")) {
4131 prevColor = curColor;
4132 if (curColor != ColorNormal) {
4133 if (oldi > next_out) {
4134 SendToPlayer(&buf[next_out], oldi - next_out);
4137 Colorize(ColorNormal, FALSE);
4138 curColor = ColorNormal;
4140 if (started == STARTED_BOARD) {
4141 started = STARTED_NONE;
4142 parse[parse_pos] = NULLCHAR;
4143 ParseBoard12(parse);
4146 /* Send premove here */
4147 if (appData.premove) {
4149 if (currentMove == 0 &&
4150 gameMode == IcsPlayingWhite &&
4151 appData.premoveWhite) {
4152 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4153 if (appData.debugMode)
4154 fprintf(debugFP, "Sending premove:\n");
4156 } else if (currentMove == 1 &&
4157 gameMode == IcsPlayingBlack &&
4158 appData.premoveBlack) {
4159 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4160 if (appData.debugMode)
4161 fprintf(debugFP, "Sending premove:\n");
4163 } else if (gotPremove) {
4165 ClearPremoveHighlights();
4166 if (appData.debugMode)
4167 fprintf(debugFP, "Sending premove:\n");
4168 UserMoveEvent(premoveFromX, premoveFromY,
4169 premoveToX, premoveToY,
4174 /* Usually suppress following prompt */
4175 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4176 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4177 if (looking_at(buf, &i, "*% ")) {
4178 savingComment = FALSE;
4183 } else if (started == STARTED_HOLDINGS) {
4185 char new_piece[MSG_SIZ];
4186 started = STARTED_NONE;
4187 parse[parse_pos] = NULLCHAR;
4188 if (appData.debugMode)
4189 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4190 parse, currentMove);
4191 if (sscanf(parse, " game %d", &gamenum) == 1) {
4192 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4193 if (gameInfo.variant == VariantNormal) {
4194 /* [HGM] We seem to switch variant during a game!
4195 * Presumably no holdings were displayed, so we have
4196 * to move the position two files to the right to
4197 * create room for them!
4199 VariantClass newVariant;
4200 switch(gameInfo.boardWidth) { // base guess on board width
4201 case 9: newVariant = VariantShogi; break;
4202 case 10: newVariant = VariantGreat; break;
4203 default: newVariant = VariantCrazyhouse; break;
4205 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4206 /* Get a move list just to see the header, which
4207 will tell us whether this is really bug or zh */
4208 if (ics_getting_history == H_FALSE) {
4209 ics_getting_history = H_REQUESTED;
4210 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4214 new_piece[0] = NULLCHAR;
4215 sscanf(parse, "game %d white [%s black [%s <- %s",
4216 &gamenum, white_holding, black_holding,
4218 white_holding[strlen(white_holding)-1] = NULLCHAR;
4219 black_holding[strlen(black_holding)-1] = NULLCHAR;
4220 /* [HGM] copy holdings to board holdings area */
4221 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4222 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4223 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4225 if (appData.zippyPlay && first.initDone) {
4226 ZippyHoldings(white_holding, black_holding,
4230 if (tinyLayout || smallLayout) {
4231 char wh[16], bh[16];
4232 PackHolding(wh, white_holding);
4233 PackHolding(bh, black_holding);
4234 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4235 gameInfo.white, gameInfo.black);
4237 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4238 gameInfo.white, white_holding, _("vs."),
4239 gameInfo.black, black_holding);
4241 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4242 DrawPosition(FALSE, boards[currentMove]);
4244 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4245 sscanf(parse, "game %d white [%s black [%s <- %s",
4246 &gamenum, white_holding, black_holding,
4248 white_holding[strlen(white_holding)-1] = NULLCHAR;
4249 black_holding[strlen(black_holding)-1] = NULLCHAR;
4250 /* [HGM] copy holdings to partner-board holdings area */
4251 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4252 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4253 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4254 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4255 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4258 /* Suppress following prompt */
4259 if (looking_at(buf, &i, "*% ")) {
4260 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4261 savingComment = FALSE;
4269 i++; /* skip unparsed character and loop back */
4272 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4273 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4274 // SendToPlayer(&buf[next_out], i - next_out);
4275 started != STARTED_HOLDINGS && leftover_start > next_out) {
4276 SendToPlayer(&buf[next_out], leftover_start - next_out);
4280 leftover_len = buf_len - leftover_start;
4281 /* if buffer ends with something we couldn't parse,
4282 reparse it after appending the next read */
4284 } else if (count == 0) {
4285 RemoveInputSource(isr);
4286 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4288 DisplayFatalError(_("Error reading from ICS"), error, 1);
4293 /* Board style 12 looks like this:
4295 <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
4297 * The "<12> " is stripped before it gets to this routine. The two
4298 * trailing 0's (flip state and clock ticking) are later addition, and
4299 * some chess servers may not have them, or may have only the first.
4300 * Additional trailing fields may be added in the future.
4303 #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"
4305 #define RELATION_OBSERVING_PLAYED 0
4306 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4307 #define RELATION_PLAYING_MYMOVE 1
4308 #define RELATION_PLAYING_NOTMYMOVE -1
4309 #define RELATION_EXAMINING 2
4310 #define RELATION_ISOLATED_BOARD -3
4311 #define RELATION_STARTING_POSITION -4 /* FICS only */
4314 ParseBoard12 (char *string)
4318 char *bookHit = NULL; // [HGM] book
4320 GameMode newGameMode;
4321 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4322 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4323 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4324 char to_play, board_chars[200];
4325 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4326 char black[32], white[32];
4328 int prevMove = currentMove;
4331 int fromX, fromY, toX, toY;
4333 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4334 Boolean weird = FALSE, reqFlag = FALSE;
4336 fromX = fromY = toX = toY = -1;
4340 if (appData.debugMode)
4341 fprintf(debugFP, "Parsing board: %s\n", string);
4343 move_str[0] = NULLCHAR;
4344 elapsed_time[0] = NULLCHAR;
4345 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4347 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4348 if(string[i] == ' ') { ranks++; files = 0; }
4350 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4353 for(j = 0; j <i; j++) board_chars[j] = string[j];
4354 board_chars[i] = '\0';
4357 n = sscanf(string, PATTERN, &to_play, &double_push,
4358 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4359 &gamenum, white, black, &relation, &basetime, &increment,
4360 &white_stren, &black_stren, &white_time, &black_time,
4361 &moveNum, str, elapsed_time, move_str, &ics_flip,
4365 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4366 DisplayError(str, 0);
4370 /* Convert the move number to internal form */
4371 moveNum = (moveNum - 1) * 2;
4372 if (to_play == 'B') moveNum++;
4373 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4374 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4380 case RELATION_OBSERVING_PLAYED:
4381 case RELATION_OBSERVING_STATIC:
4382 if (gamenum == -1) {
4383 /* Old ICC buglet */
4384 relation = RELATION_OBSERVING_STATIC;
4386 newGameMode = IcsObserving;
4388 case RELATION_PLAYING_MYMOVE:
4389 case RELATION_PLAYING_NOTMYMOVE:
4391 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4392 IcsPlayingWhite : IcsPlayingBlack;
4393 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4395 case RELATION_EXAMINING:
4396 newGameMode = IcsExamining;
4398 case RELATION_ISOLATED_BOARD:
4400 /* Just display this board. If user was doing something else,
4401 we will forget about it until the next board comes. */
4402 newGameMode = IcsIdle;
4404 case RELATION_STARTING_POSITION:
4405 newGameMode = gameMode;
4409 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4410 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4411 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4412 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4413 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4414 static int lastBgGame = -1;
4416 for (k = 0; k < ranks; k++) {
4417 for (j = 0; j < files; j++)
4418 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4419 if(gameInfo.holdingsWidth > 1) {
4420 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4421 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4424 CopyBoard(partnerBoard, board);
4425 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4426 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4427 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4428 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4429 if(toSqr = strchr(str, '-')) {
4430 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4431 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4432 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4433 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4434 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4435 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4437 DisplayWhiteClock(white_time*fac, to_play == 'W');
4438 DisplayBlackClock(black_time*fac, to_play != 'W');
4439 activePartner = to_play;
4440 if(gamenum != lastBgGame) {
4442 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4445 lastBgGame = gamenum;
4446 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4447 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4448 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4449 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4450 if(!twoBoards) DisplayMessage(partnerStatus, "");
4451 partnerBoardValid = TRUE;
4455 if(appData.dualBoard && appData.bgObserve) {
4456 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4457 SendToICS(ics_prefix), SendToICS("pobserve\n");
4458 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4460 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4465 /* Modify behavior for initial board display on move listing
4468 switch (ics_getting_history) {
4472 case H_GOT_REQ_HEADER:
4473 case H_GOT_UNREQ_HEADER:
4474 /* This is the initial position of the current game */
4475 gamenum = ics_gamenum;
4476 moveNum = 0; /* old ICS bug workaround */
4477 if (to_play == 'B') {
4478 startedFromSetupPosition = TRUE;
4479 blackPlaysFirst = TRUE;
4481 if (forwardMostMove == 0) forwardMostMove = 1;
4482 if (backwardMostMove == 0) backwardMostMove = 1;
4483 if (currentMove == 0) currentMove = 1;
4485 newGameMode = gameMode;
4486 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4488 case H_GOT_UNWANTED_HEADER:
4489 /* This is an initial board that we don't want */
4491 case H_GETTING_MOVES:
4492 /* Should not happen */
4493 DisplayError(_("Error gathering move list: extra board"), 0);
4494 ics_getting_history = H_FALSE;
4498 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4499 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4500 weird && (int)gameInfo.variant < (int)VariantShogi) {
4501 /* [HGM] We seem to have switched variant unexpectedly
4502 * Try to guess new variant from board size
4504 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4505 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4506 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4507 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4508 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4509 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4510 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4511 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4512 /* Get a move list just to see the header, which
4513 will tell us whether this is really bug or zh */
4514 if (ics_getting_history == H_FALSE) {
4515 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4516 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4521 /* Take action if this is the first board of a new game, or of a
4522 different game than is currently being displayed. */
4523 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4524 relation == RELATION_ISOLATED_BOARD) {
4526 /* Forget the old game and get the history (if any) of the new one */
4527 if (gameMode != BeginningOfGame) {
4531 if (appData.autoRaiseBoard) BoardToTop();
4533 if (gamenum == -1) {
4534 newGameMode = IcsIdle;
4535 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4536 appData.getMoveList && !reqFlag) {
4537 /* Need to get game history */
4538 ics_getting_history = H_REQUESTED;
4539 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4543 /* Initially flip the board to have black on the bottom if playing
4544 black or if the ICS flip flag is set, but let the user change
4545 it with the Flip View button. */
4546 flipView = appData.autoFlipView ?
4547 (newGameMode == IcsPlayingBlack) || ics_flip :
4550 /* Done with values from previous mode; copy in new ones */
4551 gameMode = newGameMode;
4553 ics_gamenum = gamenum;
4554 if (gamenum == gs_gamenum) {
4555 int klen = strlen(gs_kind);
4556 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4557 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4558 gameInfo.event = StrSave(str);
4560 gameInfo.event = StrSave("ICS game");
4562 gameInfo.site = StrSave(appData.icsHost);
4563 gameInfo.date = PGNDate();
4564 gameInfo.round = StrSave("-");
4565 gameInfo.white = StrSave(white);
4566 gameInfo.black = StrSave(black);
4567 timeControl = basetime * 60 * 1000;
4569 timeIncrement = increment * 1000;
4570 movesPerSession = 0;
4571 gameInfo.timeControl = TimeControlTagValue();
4572 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4573 if (appData.debugMode) {
4574 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4575 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4576 setbuf(debugFP, NULL);
4579 gameInfo.outOfBook = NULL;
4581 /* Do we have the ratings? */
4582 if (strcmp(player1Name, white) == 0 &&
4583 strcmp(player2Name, black) == 0) {
4584 if (appData.debugMode)
4585 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4586 player1Rating, player2Rating);
4587 gameInfo.whiteRating = player1Rating;
4588 gameInfo.blackRating = player2Rating;
4589 } else if (strcmp(player2Name, white) == 0 &&
4590 strcmp(player1Name, black) == 0) {
4591 if (appData.debugMode)
4592 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4593 player2Rating, player1Rating);
4594 gameInfo.whiteRating = player2Rating;
4595 gameInfo.blackRating = player1Rating;
4597 player1Name[0] = player2Name[0] = NULLCHAR;
4599 /* Silence shouts if requested */
4600 if (appData.quietPlay &&
4601 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4602 SendToICS(ics_prefix);
4603 SendToICS("set shout 0\n");
4607 /* Deal with midgame name changes */
4609 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4610 if (gameInfo.white) free(gameInfo.white);
4611 gameInfo.white = StrSave(white);
4613 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4614 if (gameInfo.black) free(gameInfo.black);
4615 gameInfo.black = StrSave(black);
4619 /* Throw away game result if anything actually changes in examine mode */
4620 if (gameMode == IcsExamining && !newGame) {
4621 gameInfo.result = GameUnfinished;
4622 if (gameInfo.resultDetails != NULL) {
4623 free(gameInfo.resultDetails);
4624 gameInfo.resultDetails = NULL;
4628 /* In pausing && IcsExamining mode, we ignore boards coming
4629 in if they are in a different variation than we are. */
4630 if (pauseExamInvalid) return;
4631 if (pausing && gameMode == IcsExamining) {
4632 if (moveNum <= pauseExamForwardMostMove) {
4633 pauseExamInvalid = TRUE;
4634 forwardMostMove = pauseExamForwardMostMove;
4639 if (appData.debugMode) {
4640 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4642 /* Parse the board */
4643 for (k = 0; k < ranks; k++) {
4644 for (j = 0; j < files; j++)
4645 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4646 if(gameInfo.holdingsWidth > 1) {
4647 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4648 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4651 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4652 board[5][BOARD_RGHT+1] = WhiteAngel;
4653 board[6][BOARD_RGHT+1] = WhiteMarshall;
4654 board[1][0] = BlackMarshall;
4655 board[2][0] = BlackAngel;
4656 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4658 CopyBoard(boards[moveNum], board);
4659 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4661 startedFromSetupPosition =
4662 !CompareBoards(board, initialPosition);
4663 if(startedFromSetupPosition)
4664 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4667 /* [HGM] Set castling rights. Take the outermost Rooks,
4668 to make it also work for FRC opening positions. Note that board12
4669 is really defective for later FRC positions, as it has no way to
4670 indicate which Rook can castle if they are on the same side of King.
4671 For the initial position we grant rights to the outermost Rooks,
4672 and remember thos rights, and we then copy them on positions
4673 later in an FRC game. This means WB might not recognize castlings with
4674 Rooks that have moved back to their original position as illegal,
4675 but in ICS mode that is not its job anyway.
4677 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4678 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4680 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4681 if(board[0][i] == WhiteRook) j = i;
4682 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4683 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4684 if(board[0][i] == WhiteRook) j = i;
4685 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4686 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4687 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4688 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4689 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4690 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4691 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4693 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4694 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4695 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4696 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4697 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4698 if(board[BOARD_HEIGHT-1][k] == bKing)
4699 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4700 if(gameInfo.variant == VariantTwoKings) {
4701 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4702 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4703 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4706 r = boards[moveNum][CASTLING][0] = initialRights[0];
4707 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4708 r = boards[moveNum][CASTLING][1] = initialRights[1];
4709 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4710 r = boards[moveNum][CASTLING][3] = initialRights[3];
4711 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4712 r = boards[moveNum][CASTLING][4] = initialRights[4];
4713 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4714 /* wildcastle kludge: always assume King has rights */
4715 r = boards[moveNum][CASTLING][2] = initialRights[2];
4716 r = boards[moveNum][CASTLING][5] = initialRights[5];
4718 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4719 boards[moveNum][EP_STATUS] = EP_NONE;
4720 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4721 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4722 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4725 if (ics_getting_history == H_GOT_REQ_HEADER ||
4726 ics_getting_history == H_GOT_UNREQ_HEADER) {
4727 /* This was an initial position from a move list, not
4728 the current position */
4732 /* Update currentMove and known move number limits */
4733 newMove = newGame || moveNum > forwardMostMove;
4736 forwardMostMove = backwardMostMove = currentMove = moveNum;
4737 if (gameMode == IcsExamining && moveNum == 0) {
4738 /* Workaround for ICS limitation: we are not told the wild
4739 type when starting to examine a game. But if we ask for
4740 the move list, the move list header will tell us */
4741 ics_getting_history = H_REQUESTED;
4742 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4745 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4746 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4748 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4749 /* [HGM] applied this also to an engine that is silently watching */
4750 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4751 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4752 gameInfo.variant == currentlyInitializedVariant) {
4753 takeback = forwardMostMove - moveNum;
4754 for (i = 0; i < takeback; i++) {
4755 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4756 SendToProgram("undo\n", &first);
4761 forwardMostMove = moveNum;
4762 if (!pausing || currentMove > forwardMostMove)
4763 currentMove = forwardMostMove;
4765 /* New part of history that is not contiguous with old part */
4766 if (pausing && gameMode == IcsExamining) {
4767 pauseExamInvalid = TRUE;
4768 forwardMostMove = pauseExamForwardMostMove;
4771 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4773 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4774 // [HGM] when we will receive the move list we now request, it will be
4775 // fed to the engine from the first move on. So if the engine is not
4776 // in the initial position now, bring it there.
4777 InitChessProgram(&first, 0);
4780 ics_getting_history = H_REQUESTED;
4781 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4784 forwardMostMove = backwardMostMove = currentMove = moveNum;
4787 /* Update the clocks */
4788 if (strchr(elapsed_time, '.')) {
4790 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4791 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4793 /* Time is in seconds */
4794 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4795 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4800 if (appData.zippyPlay && newGame &&
4801 gameMode != IcsObserving && gameMode != IcsIdle &&
4802 gameMode != IcsExamining)
4803 ZippyFirstBoard(moveNum, basetime, increment);
4806 /* Put the move on the move list, first converting
4807 to canonical algebraic form. */
4809 if (appData.debugMode) {
4810 int f = forwardMostMove;
4811 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4812 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4813 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4814 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4815 fprintf(debugFP, "moveNum = %d\n", moveNum);
4816 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4817 setbuf(debugFP, NULL);
4819 if (moveNum <= backwardMostMove) {
4820 /* We don't know what the board looked like before
4822 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4823 strcat(parseList[moveNum - 1], " ");
4824 strcat(parseList[moveNum - 1], elapsed_time);
4825 moveList[moveNum - 1][0] = NULLCHAR;
4826 } else if (strcmp(move_str, "none") == 0) {
4827 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4828 /* Again, we don't know what the board looked like;
4829 this is really the start of the game. */
4830 parseList[moveNum - 1][0] = NULLCHAR;
4831 moveList[moveNum - 1][0] = NULLCHAR;
4832 backwardMostMove = moveNum;
4833 startedFromSetupPosition = TRUE;
4834 fromX = fromY = toX = toY = -1;
4836 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4837 // So we parse the long-algebraic move string in stead of the SAN move
4838 int valid; char buf[MSG_SIZ], *prom;
4840 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4841 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4842 // str looks something like "Q/a1-a2"; kill the slash
4844 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4845 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4846 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4847 strcat(buf, prom); // long move lacks promo specification!
4848 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4849 if(appData.debugMode)
4850 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4851 safeStrCpy(move_str, buf, MSG_SIZ);
4853 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4854 &fromX, &fromY, &toX, &toY, &promoChar)
4855 || ParseOneMove(buf, moveNum - 1, &moveType,
4856 &fromX, &fromY, &toX, &toY, &promoChar);
4857 // end of long SAN patch
4859 (void) CoordsToAlgebraic(boards[moveNum - 1],
4860 PosFlags(moveNum - 1),
4861 fromY, fromX, toY, toX, promoChar,
4862 parseList[moveNum-1]);
4863 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4869 if(!IS_SHOGI(gameInfo.variant))
4870 strcat(parseList[moveNum - 1], "+");
4873 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4874 strcat(parseList[moveNum - 1], "#");
4877 strcat(parseList[moveNum - 1], " ");
4878 strcat(parseList[moveNum - 1], elapsed_time);
4879 /* currentMoveString is set as a side-effect of ParseOneMove */
4880 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4881 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4882 strcat(moveList[moveNum - 1], "\n");
4884 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4885 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4886 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4887 ChessSquare old, new = boards[moveNum][k][j];
4888 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4889 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4890 if(old == new) continue;
4891 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4892 else if(new == WhiteWazir || new == BlackWazir) {
4893 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4894 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4895 else boards[moveNum][k][j] = old; // preserve type of Gold
4896 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4897 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4900 /* Move from ICS was illegal!? Punt. */
4901 if (appData.debugMode) {
4902 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4903 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4905 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4906 strcat(parseList[moveNum - 1], " ");
4907 strcat(parseList[moveNum - 1], elapsed_time);
4908 moveList[moveNum - 1][0] = NULLCHAR;
4909 fromX = fromY = toX = toY = -1;
4912 if (appData.debugMode) {
4913 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4914 setbuf(debugFP, NULL);
4918 /* Send move to chess program (BEFORE animating it). */
4919 if (appData.zippyPlay && !newGame && newMove &&
4920 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4922 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4923 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4924 if (moveList[moveNum - 1][0] == NULLCHAR) {
4925 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4927 DisplayError(str, 0);
4929 if (first.sendTime) {
4930 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4932 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4933 if (firstMove && !bookHit) {
4935 if (first.useColors) {
4936 SendToProgram(gameMode == IcsPlayingWhite ?
4938 "black\ngo\n", &first);
4940 SendToProgram("go\n", &first);
4942 first.maybeThinking = TRUE;
4945 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4946 if (moveList[moveNum - 1][0] == NULLCHAR) {
4947 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4948 DisplayError(str, 0);
4950 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4951 SendMoveToProgram(moveNum - 1, &first);
4958 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4959 /* If move comes from a remote source, animate it. If it
4960 isn't remote, it will have already been animated. */
4961 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4962 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4964 if (!pausing && appData.highlightLastMove) {
4965 SetHighlights(fromX, fromY, toX, toY);
4969 /* Start the clocks */
4970 whiteFlag = blackFlag = FALSE;
4971 appData.clockMode = !(basetime == 0 && increment == 0);
4973 ics_clock_paused = TRUE;
4975 } else if (ticking == 1) {
4976 ics_clock_paused = FALSE;
4978 if (gameMode == IcsIdle ||
4979 relation == RELATION_OBSERVING_STATIC ||
4980 relation == RELATION_EXAMINING ||
4982 DisplayBothClocks();
4986 /* Display opponents and material strengths */
4987 if (gameInfo.variant != VariantBughouse &&
4988 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4989 if (tinyLayout || smallLayout) {
4990 if(gameInfo.variant == VariantNormal)
4991 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4992 gameInfo.white, white_stren, gameInfo.black, black_stren,
4993 basetime, increment);
4995 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4996 gameInfo.white, white_stren, gameInfo.black, black_stren,
4997 basetime, increment, (int) gameInfo.variant);
4999 if(gameInfo.variant == VariantNormal)
5000 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5001 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5002 basetime, increment);
5004 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5005 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5006 basetime, increment, VariantName(gameInfo.variant));
5009 if (appData.debugMode) {
5010 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5015 /* Display the board */
5016 if (!pausing && !appData.noGUI) {
5018 if (appData.premove)
5020 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5021 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5022 ClearPremoveHighlights();
5024 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5025 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5026 DrawPosition(j, boards[currentMove]);
5028 DisplayMove(moveNum - 1);
5029 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5030 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5031 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
5032 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5036 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5038 if(bookHit) { // [HGM] book: simulate book reply
5039 static char bookMove[MSG_SIZ]; // a bit generous?
5041 programStats.nodes = programStats.depth = programStats.time =
5042 programStats.score = programStats.got_only_move = 0;
5043 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5045 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5046 strcat(bookMove, bookHit);
5047 HandleMachineMove(bookMove, &first);
5056 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5057 ics_getting_history = H_REQUESTED;
5058 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5064 SendToBoth (char *msg)
5065 { // to make it easy to keep two engines in step in dual analysis
5066 SendToProgram(msg, &first);
5067 if(second.analyzing) SendToProgram(msg, &second);
5071 AnalysisPeriodicEvent (int force)
5073 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5074 && !force) || !appData.periodicUpdates)
5077 /* Send . command to Crafty to collect stats */
5080 /* Don't send another until we get a response (this makes
5081 us stop sending to old Crafty's which don't understand
5082 the "." command (sending illegal cmds resets node count & time,
5083 which looks bad)) */
5084 programStats.ok_to_send = 0;
5088 ics_update_width (int new_width)
5090 ics_printf("set width %d\n", new_width);
5094 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5098 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5099 if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5100 sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5101 SendToProgram(buf, cps);
5104 // null move in variant where engine does not understand it (for analysis purposes)
5105 SendBoard(cps, moveNum + 1); // send position after move in stead.
5108 if (cps->useUsermove) {
5109 SendToProgram("usermove ", cps);
5113 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5114 int len = space - parseList[moveNum];
5115 memcpy(buf, parseList[moveNum], len);
5117 buf[len] = NULLCHAR;
5119 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5121 SendToProgram(buf, cps);
5123 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5124 AlphaRank(moveList[moveNum], 4);
5125 SendToProgram(moveList[moveNum], cps);
5126 AlphaRank(moveList[moveNum], 4); // and back
5128 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5129 * the engine. It would be nice to have a better way to identify castle
5131 if(appData.fischerCastling && cps->useOOCastle) {
5132 int fromX = moveList[moveNum][0] - AAA;
5133 int fromY = moveList[moveNum][1] - ONE;
5134 int toX = moveList[moveNum][2] - AAA;
5135 int toY = moveList[moveNum][3] - ONE;
5136 if((boards[moveNum][fromY][fromX] == WhiteKing
5137 && boards[moveNum][toY][toX] == WhiteRook)
5138 || (boards[moveNum][fromY][fromX] == BlackKing
5139 && boards[moveNum][toY][toX] == BlackRook)) {
5140 if(toX > fromX) SendToProgram("O-O\n", cps);
5141 else SendToProgram("O-O-O\n", cps);
5143 else SendToProgram(moveList[moveNum], cps);
5145 if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5146 char *m = moveList[moveNum];
5147 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
5148 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5151 m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5153 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5157 SendToProgram(buf, cps);
5159 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5160 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5161 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5162 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5163 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5165 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5166 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5167 SendToProgram(buf, cps);
5169 else SendToProgram(moveList[moveNum], cps);
5170 /* End of additions by Tord */
5173 /* [HGM] setting up the opening has brought engine in force mode! */
5174 /* Send 'go' if we are in a mode where machine should play. */
5175 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5176 (gameMode == TwoMachinesPlay ||
5178 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5180 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5181 SendToProgram("go\n", cps);
5182 if (appData.debugMode) {
5183 fprintf(debugFP, "(extra)\n");
5186 setboardSpoiledMachineBlack = 0;
5190 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5192 char user_move[MSG_SIZ];
5195 if(gameInfo.variant == VariantSChess && promoChar) {
5196 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5197 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5198 } else suffix[0] = NULLCHAR;
5202 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5203 (int)moveType, fromX, fromY, toX, toY);
5204 DisplayError(user_move + strlen("say "), 0);
5206 case WhiteKingSideCastle:
5207 case BlackKingSideCastle:
5208 case WhiteQueenSideCastleWild:
5209 case BlackQueenSideCastleWild:
5211 case WhiteHSideCastleFR:
5212 case BlackHSideCastleFR:
5214 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5216 case WhiteQueenSideCastle:
5217 case BlackQueenSideCastle:
5218 case WhiteKingSideCastleWild:
5219 case BlackKingSideCastleWild:
5221 case WhiteASideCastleFR:
5222 case BlackASideCastleFR:
5224 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5226 case WhiteNonPromotion:
5227 case BlackNonPromotion:
5228 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5230 case WhitePromotion:
5231 case BlackPromotion:
5232 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5233 gameInfo.variant == VariantMakruk)
5234 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5235 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5236 PieceToChar(WhiteFerz));
5237 else if(gameInfo.variant == VariantGreat)
5238 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5239 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5240 PieceToChar(WhiteMan));
5242 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5243 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5249 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5250 ToUpper(PieceToChar((ChessSquare) fromX)),
5251 AAA + toX, ONE + toY);
5253 case IllegalMove: /* could be a variant we don't quite understand */
5254 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5256 case WhiteCapturesEnPassant:
5257 case BlackCapturesEnPassant:
5258 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5259 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5262 SendToICS(user_move);
5263 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5264 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5269 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5270 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5271 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5272 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5273 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5276 if(gameMode != IcsExamining) { // is this ever not the case?
5277 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5279 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5280 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5281 } else { // on FICS we must first go to general examine mode
5282 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5284 if(gameInfo.variant != VariantNormal) {
5285 // try figure out wild number, as xboard names are not always valid on ICS
5286 for(i=1; i<=36; i++) {
5287 snprintf(buf, MSG_SIZ, "wild/%d", i);
5288 if(StringToVariant(buf) == gameInfo.variant) break;
5290 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5291 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5292 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5293 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5294 SendToICS(ics_prefix);
5296 if(startedFromSetupPosition || backwardMostMove != 0) {
5297 fen = PositionToFEN(backwardMostMove, NULL, 1);
5298 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5299 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5301 } else { // FICS: everything has to set by separate bsetup commands
5302 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5303 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5305 if(!WhiteOnMove(backwardMostMove)) {
5306 SendToICS("bsetup tomove black\n");
5308 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5309 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5311 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5312 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5314 i = boards[backwardMostMove][EP_STATUS];
5315 if(i >= 0) { // set e.p.
5316 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5322 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5323 SendToICS("bsetup done\n"); // switch to normal examining.
5325 for(i = backwardMostMove; i<last; i++) {
5327 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5328 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5329 int len = strlen(moveList[i]);
5330 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5331 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5335 SendToICS(ics_prefix);
5336 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5339 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5343 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5345 if (rf == DROP_RANK) {
5346 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5347 sprintf(move, "%c@%c%c\n",
5348 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5350 if (promoChar == 'x' || promoChar == NULLCHAR) {
5351 sprintf(move, "%c%c%c%c\n",
5352 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5353 if(killX >= 0 && killY >= 0) {
5354 sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5355 if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + killX, ONE + killY);
5358 sprintf(move, "%c%c%c%c%c\n",
5359 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5365 ProcessICSInitScript (FILE *f)
5369 while (fgets(buf, MSG_SIZ, f)) {
5370 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5377 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5379 static ClickType lastClickType;
5382 Partner (ChessSquare *p)
5383 { // change piece into promotion partner if one shogi-promotes to the other
5384 int stride = gameInfo.variant == VariantChu ? 22 : 11;
5385 ChessSquare partner;
5386 partner = (*p/stride & 1 ? *p - stride : *p + stride);
5387 if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5395 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5396 static int toggleFlag;
5397 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5398 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5399 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5400 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5401 if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5402 if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5404 if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5405 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5406 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5407 else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5408 else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5409 if(!step) step = -1;
5410 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5411 !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5412 appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5413 (promoSweep == WhiteLion || promoSweep == BlackLion)));
5415 int victim = boards[currentMove][toY][toX];
5416 boards[currentMove][toY][toX] = promoSweep;
5417 DrawPosition(FALSE, boards[currentMove]);
5418 boards[currentMove][toY][toX] = victim;
5420 ChangeDragPiece(promoSweep);
5424 PromoScroll (int x, int y)
5428 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5429 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5430 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5431 if(!step) return FALSE;
5432 lastX = x; lastY = y;
5433 if((promoSweep < BlackPawn) == flipView) step = -step;
5434 if(step > 0) selectFlag = 1;
5435 if(!selectFlag) Sweep(step);
5440 NextPiece (int step)
5442 ChessSquare piece = boards[currentMove][toY][toX];
5445 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5446 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5447 if(!step) step = -1;
5448 } while(PieceToChar(pieceSweep) == '.');
5449 boards[currentMove][toY][toX] = pieceSweep;
5450 DrawPosition(FALSE, boards[currentMove]);
5451 boards[currentMove][toY][toX] = piece;
5453 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5455 AlphaRank (char *move, int n)
5457 // char *p = move, c; int x, y;
5459 if (appData.debugMode) {
5460 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5464 move[2]>='0' && move[2]<='9' &&
5465 move[3]>='a' && move[3]<='x' ) {
5467 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5468 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5470 if(move[0]>='0' && move[0]<='9' &&
5471 move[1]>='a' && move[1]<='x' &&
5472 move[2]>='0' && move[2]<='9' &&
5473 move[3]>='a' && move[3]<='x' ) {
5474 /* input move, Shogi -> normal */
5475 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5476 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5477 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5478 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5481 move[3]>='0' && move[3]<='9' &&
5482 move[2]>='a' && move[2]<='x' ) {
5484 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5485 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5488 move[0]>='a' && move[0]<='x' &&
5489 move[3]>='0' && move[3]<='9' &&
5490 move[2]>='a' && move[2]<='x' ) {
5491 /* output move, normal -> Shogi */
5492 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5493 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5494 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5495 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5496 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5498 if (appData.debugMode) {
5499 fprintf(debugFP, " out = '%s'\n", move);
5503 char yy_textstr[8000];
5505 /* Parser for moves from gnuchess, ICS, or user typein box */
5507 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5509 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5511 switch (*moveType) {
5512 case WhitePromotion:
5513 case BlackPromotion:
5514 case WhiteNonPromotion:
5515 case BlackNonPromotion:
5518 case WhiteCapturesEnPassant:
5519 case BlackCapturesEnPassant:
5520 case WhiteKingSideCastle:
5521 case WhiteQueenSideCastle:
5522 case BlackKingSideCastle:
5523 case BlackQueenSideCastle:
5524 case WhiteKingSideCastleWild:
5525 case WhiteQueenSideCastleWild:
5526 case BlackKingSideCastleWild:
5527 case BlackQueenSideCastleWild:
5528 /* Code added by Tord: */
5529 case WhiteHSideCastleFR:
5530 case WhiteASideCastleFR:
5531 case BlackHSideCastleFR:
5532 case BlackASideCastleFR:
5533 /* End of code added by Tord */
5534 case IllegalMove: /* bug or odd chess variant */
5535 if(currentMoveString[1] == '@') { // illegal drop
5536 *fromX = WhiteOnMove(moveNum) ?
5537 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5538 (int) CharToPiece(ToLower(currentMoveString[0]));
5541 *fromX = currentMoveString[0] - AAA;
5542 *fromY = currentMoveString[1] - ONE;
5543 *toX = currentMoveString[2] - AAA;
5544 *toY = currentMoveString[3] - ONE;
5545 *promoChar = currentMoveString[4];
5546 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5547 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5548 if (appData.debugMode) {
5549 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5551 *fromX = *fromY = *toX = *toY = 0;
5554 if (appData.testLegality) {
5555 return (*moveType != IllegalMove);
5557 return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5558 // [HGM] lion: if this is a double move we are less critical
5559 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5564 *fromX = *moveType == WhiteDrop ?
5565 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5566 (int) CharToPiece(ToLower(currentMoveString[0]));
5569 *toX = currentMoveString[2] - AAA;
5570 *toY = currentMoveString[3] - ONE;
5571 *promoChar = NULLCHAR;
5575 case ImpossibleMove:
5585 if (appData.debugMode) {
5586 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5589 *fromX = *fromY = *toX = *toY = 0;
5590 *promoChar = NULLCHAR;
5595 Boolean pushed = FALSE;
5596 char *lastParseAttempt;
5599 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5600 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5601 int fromX, fromY, toX, toY; char promoChar;
5606 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5607 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5608 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5611 endPV = forwardMostMove;
5613 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5614 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5615 lastParseAttempt = pv;
5616 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5617 if(!valid && nr == 0 &&
5618 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5619 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5620 // Hande case where played move is different from leading PV move
5621 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5622 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5623 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5624 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5625 endPV += 2; // if position different, keep this
5626 moveList[endPV-1][0] = fromX + AAA;
5627 moveList[endPV-1][1] = fromY + ONE;
5628 moveList[endPV-1][2] = toX + AAA;
5629 moveList[endPV-1][3] = toY + ONE;
5630 parseList[endPV-1][0] = NULLCHAR;
5631 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5634 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5635 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5636 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5637 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5638 valid++; // allow comments in PV
5642 if(endPV+1 > framePtr) break; // no space, truncate
5645 CopyBoard(boards[endPV], boards[endPV-1]);
5646 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5647 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5648 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5649 CoordsToAlgebraic(boards[endPV - 1],
5650 PosFlags(endPV - 1),
5651 fromY, fromX, toY, toX, promoChar,
5652 parseList[endPV - 1]);
5654 if(atEnd == 2) return; // used hidden, for PV conversion
5655 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5656 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5657 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5658 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5659 DrawPosition(TRUE, boards[currentMove]);
5663 MultiPV (ChessProgramState *cps)
5664 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5666 for(i=0; i<cps->nrOptions; i++)
5667 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5672 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5675 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5677 int startPV, multi, lineStart, origIndex = index;
5678 char *p, buf2[MSG_SIZ];
5679 ChessProgramState *cps = (pane ? &second : &first);
5681 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5682 lastX = x; lastY = y;
5683 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5684 lineStart = startPV = index;
5685 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5686 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5688 do{ while(buf[index] && buf[index] != '\n') index++;
5689 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5691 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5692 int n = cps->option[multi].value;
5693 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5694 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5695 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5696 cps->option[multi].value = n;
5699 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5700 ExcludeClick(origIndex - lineStart);
5702 } else if(!strncmp(buf+lineStart, "dep\t", 4)) { // column headers clicked
5703 Collapse(origIndex - lineStart);
5706 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5707 *start = startPV; *end = index-1;
5708 extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5715 static char buf[10*MSG_SIZ];
5716 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5718 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5719 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5720 for(i = forwardMostMove; i<endPV; i++){
5721 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5722 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5725 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5726 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5727 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5733 LoadPV (int x, int y)
5734 { // called on right mouse click to load PV
5735 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5736 lastX = x; lastY = y;
5737 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5745 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5746 if(endPV < 0) return;
5747 if(appData.autoCopyPV) CopyFENToClipboard();
5749 if(extendGame && currentMove > forwardMostMove) {
5750 Boolean saveAnimate = appData.animate;
5752 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5753 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5754 } else storedGames--; // abandon shelved tail of original game
5757 forwardMostMove = currentMove;
5758 currentMove = oldFMM;
5759 appData.animate = FALSE;
5760 ToNrEvent(forwardMostMove);
5761 appData.animate = saveAnimate;
5763 currentMove = forwardMostMove;
5764 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5765 ClearPremoveHighlights();
5766 DrawPosition(TRUE, boards[currentMove]);
5770 MovePV (int x, int y, int h)
5771 { // step through PV based on mouse coordinates (called on mouse move)
5772 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5774 // we must somehow check if right button is still down (might be released off board!)
5775 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5776 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5777 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5779 lastX = x; lastY = y;
5781 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5782 if(endPV < 0) return;
5783 if(y < margin) step = 1; else
5784 if(y > h - margin) step = -1;
5785 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5786 currentMove += step;
5787 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5788 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5789 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5790 DrawPosition(FALSE, boards[currentMove]);
5794 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5795 // All positions will have equal probability, but the current method will not provide a unique
5796 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5802 int piecesLeft[(int)BlackPawn];
5803 int seed, nrOfShuffles;
5806 GetPositionNumber ()
5807 { // sets global variable seed
5810 seed = appData.defaultFrcPosition;
5811 if(seed < 0) { // randomize based on time for negative FRC position numbers
5812 for(i=0; i<50; i++) seed += random();
5813 seed = random() ^ random() >> 8 ^ random() << 8;
5814 if(seed<0) seed = -seed;
5819 put (Board board, int pieceType, int rank, int n, int shade)
5820 // put the piece on the (n-1)-th empty squares of the given shade
5824 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5825 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5826 board[rank][i] = (ChessSquare) pieceType;
5827 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5829 piecesLeft[pieceType]--;
5838 AddOnePiece (Board board, int pieceType, int rank, int shade)
5839 // calculate where the next piece goes, (any empty square), and put it there
5843 i = seed % squaresLeft[shade];
5844 nrOfShuffles *= squaresLeft[shade];
5845 seed /= squaresLeft[shade];
5846 put(board, pieceType, rank, i, shade);
5850 AddTwoPieces (Board board, int pieceType, int rank)
5851 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5853 int i, n=squaresLeft[ANY], j=n-1, k;
5855 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5856 i = seed % k; // pick one
5859 while(i >= j) i -= j--;
5860 j = n - 1 - j; i += j;
5861 put(board, pieceType, rank, j, ANY);
5862 put(board, pieceType, rank, i, ANY);
5866 SetUpShuffle (Board board, int number)
5870 GetPositionNumber(); nrOfShuffles = 1;
5872 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5873 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5874 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5876 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5878 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5879 p = (int) board[0][i];
5880 if(p < (int) BlackPawn) piecesLeft[p] ++;
5881 board[0][i] = EmptySquare;
5884 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5885 // shuffles restricted to allow normal castling put KRR first
5886 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5887 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5888 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5889 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5890 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5891 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5892 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5893 put(board, WhiteRook, 0, 0, ANY);
5894 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5897 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5898 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5899 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5900 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5901 while(piecesLeft[p] >= 2) {
5902 AddOnePiece(board, p, 0, LITE);
5903 AddOnePiece(board, p, 0, DARK);
5905 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5908 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5909 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5910 // but we leave King and Rooks for last, to possibly obey FRC restriction
5911 if(p == (int)WhiteRook) continue;
5912 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5913 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5916 // now everything is placed, except perhaps King (Unicorn) and Rooks
5918 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5919 // Last King gets castling rights
5920 while(piecesLeft[(int)WhiteUnicorn]) {
5921 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5922 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5925 while(piecesLeft[(int)WhiteKing]) {
5926 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5927 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5932 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5933 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5936 // Only Rooks can be left; simply place them all
5937 while(piecesLeft[(int)WhiteRook]) {
5938 i = put(board, WhiteRook, 0, 0, ANY);
5939 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5942 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5944 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5947 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5948 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5951 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5955 ptclen (const char *s, char *escapes)
5958 if(!*escapes) return strlen(s);
5959 while(*s) n += (*s != '/' && !strchr(escapes, *s)), s++;
5964 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
5965 /* [HGM] moved here from winboard.c because of its general usefulness */
5966 /* Basically a safe strcpy that uses the last character as King */
5968 int result = FALSE; int NrPieces, offs;
5970 if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
5971 && NrPieces >= 12 && !(NrPieces&1)) {
5972 int i, j = 0; /* [HGM] Accept even length from 12 to 88 */
5974 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5975 for( i=offs=0; i<NrPieces/2-1; i++ ) {
5977 if(map[j] == '/' && *escapes) offs = WhiteTokin - i, j++;
5978 table[i + offs] = map[j++];
5979 if(p = strchr(escapes, map[j])) j++, table[i + offs] += 64*(p - escapes + 1);
5981 table[(int) WhiteKing] = map[j++];
5982 for( i=offs=0; i<NrPieces/2-1; i++ ) {
5984 if(map[j] == '/' && *escapes) offs = WhiteTokin - i, j++;
5985 table[WHITE_TO_BLACK i + offs] = map[j++];
5986 if(p = strchr(escapes, map[j])) j++, table[WHITE_TO_BLACK i + offs] += 64*(p - escapes + 1);
5988 table[(int) BlackKing] = map[j++];
5997 SetCharTable (unsigned char *table, const char * map)
5999 return SetCharTableEsc(table, map, "");
6003 Prelude (Board board)
6004 { // [HGM] superchess: random selection of exo-pieces
6005 int i, j, k; ChessSquare p;
6006 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6008 GetPositionNumber(); // use FRC position number
6010 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6011 SetCharTable(pieceToChar, appData.pieceToCharTable);
6012 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6013 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6016 j = seed%4; seed /= 4;
6017 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6018 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6019 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
6020 j = seed%3 + (seed%3 >= j); seed /= 3;
6021 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6022 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6023 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
6024 j = seed%3; seed /= 3;
6025 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6026 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6027 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
6028 j = seed%2 + (seed%2 >= j); seed /= 2;
6029 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6030 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6031 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
6032 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
6033 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
6034 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6035 put(board, exoPieces[0], 0, 0, ANY);
6036 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6040 InitPosition (int redraw)
6042 ChessSquare (* pieces)[BOARD_FILES];
6043 int i, j, pawnRow=1, pieceRows=1, overrule,
6044 oldx = gameInfo.boardWidth,
6045 oldy = gameInfo.boardHeight,
6046 oldh = gameInfo.holdingsWidth;
6049 if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6051 /* [AS] Initialize pv info list [HGM] and game status */
6053 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6054 pvInfoList[i].depth = 0;
6055 boards[i][EP_STATUS] = EP_NONE;
6056 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6059 initialRulePlies = 0; /* 50-move counter start */
6061 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6062 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6066 /* [HGM] logic here is completely changed. In stead of full positions */
6067 /* the initialized data only consist of the two backranks. The switch */
6068 /* selects which one we will use, which is than copied to the Board */
6069 /* initialPosition, which for the rest is initialized by Pawns and */
6070 /* empty squares. This initial position is then copied to boards[0], */
6071 /* possibly after shuffling, so that it remains available. */
6073 gameInfo.holdingsWidth = 0; /* default board sizes */
6074 gameInfo.boardWidth = 8;
6075 gameInfo.boardHeight = 8;
6076 gameInfo.holdingsSize = 0;
6077 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6078 for(i=0; i<BOARD_FILES-6; i++)
6079 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6080 initialPosition[EP_STATUS] = EP_NONE;
6081 initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6082 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6083 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6084 SetCharTable(pieceNickName, appData.pieceNickNames);
6085 else SetCharTable(pieceNickName, "............");
6088 switch (gameInfo.variant) {
6089 case VariantFischeRandom:
6090 shuffleOpenings = TRUE;
6091 appData.fischerCastling = TRUE;
6094 case VariantShatranj:
6095 pieces = ShatranjArray;
6096 nrCastlingRights = 0;
6097 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6100 pieces = makrukArray;
6101 nrCastlingRights = 0;
6102 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6105 pieces = aseanArray;
6106 nrCastlingRights = 0;
6107 SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6109 case VariantTwoKings:
6110 pieces = twoKingsArray;
6113 pieces = GrandArray;
6114 nrCastlingRights = 0;
6115 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6116 gameInfo.boardWidth = 10;
6117 gameInfo.boardHeight = 10;
6118 gameInfo.holdingsSize = 7;
6120 case VariantCapaRandom:
6121 shuffleOpenings = TRUE;
6122 appData.fischerCastling = TRUE;
6123 case VariantCapablanca:
6124 pieces = CapablancaArray;
6125 gameInfo.boardWidth = 10;
6126 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6129 pieces = GothicArray;
6130 gameInfo.boardWidth = 10;
6131 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6134 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6135 gameInfo.holdingsSize = 7;
6136 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6139 pieces = JanusArray;
6140 gameInfo.boardWidth = 10;
6141 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6142 nrCastlingRights = 6;
6143 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6144 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6145 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6146 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6147 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6148 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6151 pieces = FalconArray;
6152 gameInfo.boardWidth = 10;
6153 SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6155 case VariantXiangqi:
6156 pieces = XiangqiArray;
6157 gameInfo.boardWidth = 9;
6158 gameInfo.boardHeight = 10;
6159 nrCastlingRights = 0;
6160 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6163 pieces = ShogiArray;
6164 gameInfo.boardWidth = 9;
6165 gameInfo.boardHeight = 9;
6166 gameInfo.holdingsSize = 7;
6167 nrCastlingRights = 0;
6168 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6171 pieces = ChuArray; pieceRows = 3;
6172 gameInfo.boardWidth = 12;
6173 gameInfo.boardHeight = 12;
6174 nrCastlingRights = 0;
6175 SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN/+.++.++++++++++.+++++K"
6176 "p.brqsexogcathd.vmlifn/+.++.++++++++++.+++++k", SUFFIXES);
6178 case VariantCourier:
6179 pieces = CourierArray;
6180 gameInfo.boardWidth = 12;
6181 nrCastlingRights = 0;
6182 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6184 case VariantKnightmate:
6185 pieces = KnightmateArray;
6186 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6188 case VariantSpartan:
6189 pieces = SpartanArray;
6190 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6194 SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6196 case VariantChuChess:
6197 pieces = ChuChessArray;
6198 gameInfo.boardWidth = 10;
6199 gameInfo.boardHeight = 10;
6200 SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6203 pieces = fairyArray;
6204 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6207 pieces = GreatArray;
6208 gameInfo.boardWidth = 10;
6209 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6210 gameInfo.holdingsSize = 8;
6214 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6215 gameInfo.holdingsSize = 8;
6216 startedFromSetupPosition = TRUE;
6218 case VariantCrazyhouse:
6219 case VariantBughouse:
6221 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6222 gameInfo.holdingsSize = 5;
6224 case VariantWildCastle:
6226 /* !!?shuffle with kings guaranteed to be on d or e file */
6227 shuffleOpenings = 1;
6229 case VariantNoCastle:
6231 nrCastlingRights = 0;
6232 /* !!?unconstrained back-rank shuffle */
6233 shuffleOpenings = 1;
6238 if(appData.NrFiles >= 0) {
6239 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6240 gameInfo.boardWidth = appData.NrFiles;
6242 if(appData.NrRanks >= 0) {
6243 gameInfo.boardHeight = appData.NrRanks;
6245 if(appData.holdingsSize >= 0) {
6246 i = appData.holdingsSize;
6247 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6248 gameInfo.holdingsSize = i;
6250 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6251 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6252 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6254 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6255 if(pawnRow < 1) pawnRow = 1;
6256 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6257 gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6258 if(gameInfo.variant == VariantChu) pawnRow = 3;
6260 /* User pieceToChar list overrules defaults */
6261 if(appData.pieceToCharTable != NULL)
6262 SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6264 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6266 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6267 s = (ChessSquare) 0; /* account holding counts in guard band */
6268 for( i=0; i<BOARD_HEIGHT; i++ )
6269 initialPosition[i][j] = s;
6271 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6272 initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6273 initialPosition[pawnRow][j] = WhitePawn;
6274 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6275 if(gameInfo.variant == VariantXiangqi) {
6277 initialPosition[pawnRow][j] =
6278 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6279 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6280 initialPosition[2][j] = WhiteCannon;
6281 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6285 if(gameInfo.variant == VariantChu) {
6286 if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6287 initialPosition[pawnRow+1][j] = WhiteCobra,
6288 initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6289 for(i=1; i<pieceRows; i++) {
6290 initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6291 initialPosition[BOARD_HEIGHT-1-i][j] = pieces[2*i+1][j-gameInfo.holdingsWidth];
6294 if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6295 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6296 initialPosition[0][j] = WhiteRook;
6297 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6300 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] = pieces[1][j-gameInfo.holdingsWidth];
6302 if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6303 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6306 initialPosition[1][j] = WhiteBishop;
6307 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6309 initialPosition[1][j] = WhiteRook;
6310 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6313 if( nrCastlingRights == -1) {
6314 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6315 /* This sets default castling rights from none to normal corners */
6316 /* Variants with other castling rights must set them themselves above */
6317 nrCastlingRights = 6;
6319 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6320 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6321 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6322 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6323 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6324 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6327 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6328 if(gameInfo.variant == VariantGreat) { // promotion commoners
6329 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6330 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6331 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6332 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6334 if( gameInfo.variant == VariantSChess ) {
6335 initialPosition[1][0] = BlackMarshall;
6336 initialPosition[2][0] = BlackAngel;
6337 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6338 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6339 initialPosition[1][1] = initialPosition[2][1] =
6340 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6342 if (appData.debugMode) {
6343 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6345 if(shuffleOpenings) {
6346 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6347 startedFromSetupPosition = TRUE;
6349 if(startedFromPositionFile) {
6350 /* [HGM] loadPos: use PositionFile for every new game */
6351 CopyBoard(initialPosition, filePosition);
6352 for(i=0; i<nrCastlingRights; i++)
6353 initialRights[i] = filePosition[CASTLING][i];
6354 startedFromSetupPosition = TRUE;
6357 CopyBoard(boards[0], initialPosition);
6359 if(oldx != gameInfo.boardWidth ||
6360 oldy != gameInfo.boardHeight ||
6361 oldv != gameInfo.variant ||
6362 oldh != gameInfo.holdingsWidth
6364 InitDrawingSizes(-2 ,0);
6366 oldv = gameInfo.variant;
6368 DrawPosition(TRUE, boards[currentMove]);
6372 SendBoard (ChessProgramState *cps, int moveNum)
6374 char message[MSG_SIZ];
6376 if (cps->useSetboard) {
6377 char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6378 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6379 SendToProgram(message, cps);
6384 int i, j, left=0, right=BOARD_WIDTH;
6385 /* Kludge to set black to move, avoiding the troublesome and now
6386 * deprecated "black" command.
6388 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6389 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6391 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6393 SendToProgram("edit\n", cps);
6394 SendToProgram("#\n", cps);
6395 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6396 bp = &boards[moveNum][i][left];
6397 for (j = left; j < right; j++, bp++) {
6398 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6399 if ((int) *bp < (int) BlackPawn) {
6400 if(j == BOARD_RGHT+1)
6401 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6402 else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6403 if(message[0] == '+' || message[0] == '~') {
6404 snprintf(message, MSG_SIZ,"%c%c%d+\n",
6405 PieceToChar((ChessSquare)(DEMOTED *bp)),
6406 AAA + j, ONE + i - '0');
6408 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6409 message[1] = BOARD_RGHT - 1 - j + '1';
6410 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6412 SendToProgram(message, cps);
6417 SendToProgram("c\n", cps);
6418 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6419 bp = &boards[moveNum][i][left];
6420 for (j = left; j < right; j++, bp++) {
6421 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6422 if (((int) *bp != (int) EmptySquare)
6423 && ((int) *bp >= (int) BlackPawn)) {
6424 if(j == BOARD_LEFT-2)
6425 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6426 else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6427 AAA + j, ONE + i - '0');
6428 if(message[0] == '+' || message[0] == '~') {
6429 snprintf(message, MSG_SIZ,"%c%c%d+\n",
6430 PieceToChar((ChessSquare)(DEMOTED *bp)),
6431 AAA + j, ONE + i - '0');
6433 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6434 message[1] = BOARD_RGHT - 1 - j + '1';
6435 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6437 SendToProgram(message, cps);
6442 SendToProgram(".\n", cps);
6444 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6447 char exclusionHeader[MSG_SIZ];
6448 int exCnt, excludePtr;
6449 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6450 static Exclusion excluTab[200];
6451 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6457 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6458 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6464 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6465 excludePtr = 24; exCnt = 0;
6470 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6471 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6472 char buf[2*MOVE_LEN], *p;
6473 Exclusion *e = excluTab;
6475 for(i=0; i<exCnt; i++)
6476 if(e[i].ff == fromX && e[i].fr == fromY &&
6477 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6478 if(i == exCnt) { // was not in exclude list; add it
6479 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6480 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6481 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6484 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6485 excludePtr++; e[i].mark = excludePtr++;
6486 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6489 exclusionHeader[e[i].mark] = state;
6493 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6494 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6498 if((signed char)promoChar == -1) { // kludge to indicate best move
6499 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6500 return 1; // if unparsable, abort
6502 // update exclusion map (resolving toggle by consulting existing state)
6503 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6505 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6506 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6507 excludeMap[k] |= 1<<j;
6508 else excludeMap[k] &= ~(1<<j);
6510 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6512 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6513 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6515 return (state == '+');
6519 ExcludeClick (int index)
6522 Exclusion *e = excluTab;
6523 if(index < 25) { // none, best or tail clicked
6524 if(index < 13) { // none: include all
6525 WriteMap(0); // clear map
6526 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6527 SendToBoth("include all\n"); // and inform engine
6528 } else if(index > 18) { // tail
6529 if(exclusionHeader[19] == '-') { // tail was excluded
6530 SendToBoth("include all\n");
6531 WriteMap(0); // clear map completely
6532 // now re-exclude selected moves
6533 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6534 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6535 } else { // tail was included or in mixed state
6536 SendToBoth("exclude all\n");
6537 WriteMap(0xFF); // fill map completely
6538 // now re-include selected moves
6539 j = 0; // count them
6540 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6541 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6542 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6545 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6548 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6549 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6550 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6557 DefaultPromoChoice (int white)
6560 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6561 gameInfo.variant == VariantMakruk)
6562 result = WhiteFerz; // no choice
6563 else if(gameInfo.variant == VariantASEAN)
6564 result = WhiteRook; // no choice
6565 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6566 result= WhiteKing; // in Suicide Q is the last thing we want
6567 else if(gameInfo.variant == VariantSpartan)
6568 result = white ? WhiteQueen : WhiteAngel;
6569 else result = WhiteQueen;
6570 if(!white) result = WHITE_TO_BLACK result;
6574 static int autoQueen; // [HGM] oneclick
6577 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6579 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6580 /* [HGM] add Shogi promotions */
6581 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6582 ChessSquare piece, partner;
6586 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6587 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6589 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6590 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6593 piece = boards[currentMove][fromY][fromX];
6594 if(gameInfo.variant == VariantChu) {
6595 int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6596 promotionZoneSize = BOARD_HEIGHT/3;
6597 highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6598 } else if(gameInfo.variant == VariantShogi) {
6599 promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6600 highestPromotingPiece = (int)WhiteAlfil;
6601 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6602 promotionZoneSize = 3;
6605 // Treat Lance as Pawn when it is not representing Amazon or Lance
6606 if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6607 if(piece == WhiteLance) piece = WhitePawn; else
6608 if(piece == BlackLance) piece = BlackPawn;
6611 // next weed out all moves that do not touch the promotion zone at all
6612 if((int)piece >= BlackPawn) {
6613 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6615 if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6616 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6618 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6619 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6620 if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6624 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6626 // weed out mandatory Shogi promotions
6627 if(gameInfo.variant == VariantShogi) {
6628 if(piece >= BlackPawn) {
6629 if(toY == 0 && piece == BlackPawn ||
6630 toY == 0 && piece == BlackQueen ||
6631 toY <= 1 && piece == BlackKnight) {
6636 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6637 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6638 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6645 // weed out obviously illegal Pawn moves
6646 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6647 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6648 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6649 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6650 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6651 // note we are not allowed to test for valid (non-)capture, due to premove
6654 // we either have a choice what to promote to, or (in Shogi) whether to promote
6655 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6656 gameInfo.variant == VariantMakruk) {
6657 ChessSquare p=BlackFerz; // no choice
6658 while(p < EmptySquare) { //but make sure we use piece that exists
6659 *promoChoice = PieceToChar(p++);
6660 if(*promoChoice != '.') break;
6664 // no sense asking what we must promote to if it is going to explode...
6665 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6666 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6669 // give caller the default choice even if we will not make it
6670 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6671 partner = piece; // pieces can promote if the pieceToCharTable says so
6672 if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6673 else if(Partner(&partner)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6674 if( sweepSelect && gameInfo.variant != VariantGreat
6675 && gameInfo.variant != VariantGrand
6676 && gameInfo.variant != VariantSuper) return FALSE;
6677 if(autoQueen) return FALSE; // predetermined
6679 // suppress promotion popup on illegal moves that are not premoves
6680 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6681 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6682 if(appData.testLegality && !premove) {
6683 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6684 fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6685 if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6686 if(moveType != WhitePromotion && moveType != BlackPromotion)
6694 InPalace (int row, int column)
6695 { /* [HGM] for Xiangqi */
6696 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6697 column < (BOARD_WIDTH + 4)/2 &&
6698 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6703 PieceForSquare (int x, int y)
6705 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6708 return boards[currentMove][y][x];
6712 OKToStartUserMove (int x, int y)
6714 ChessSquare from_piece;
6717 if (matchMode) return FALSE;
6718 if (gameMode == EditPosition) return TRUE;
6720 if (x >= 0 && y >= 0)
6721 from_piece = boards[currentMove][y][x];
6723 from_piece = EmptySquare;
6725 if (from_piece == EmptySquare) return FALSE;
6727 white_piece = (int)from_piece >= (int)WhitePawn &&
6728 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6732 case TwoMachinesPlay:
6740 case MachinePlaysWhite:
6741 case IcsPlayingBlack:
6742 if (appData.zippyPlay) return FALSE;
6744 DisplayMoveError(_("You are playing Black"));
6749 case MachinePlaysBlack:
6750 case IcsPlayingWhite:
6751 if (appData.zippyPlay) return FALSE;
6753 DisplayMoveError(_("You are playing White"));
6758 case PlayFromGameFile:
6759 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6761 if (!white_piece && WhiteOnMove(currentMove)) {
6762 DisplayMoveError(_("It is White's turn"));
6765 if (white_piece && !WhiteOnMove(currentMove)) {
6766 DisplayMoveError(_("It is Black's turn"));
6769 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6770 /* Editing correspondence game history */
6771 /* Could disallow this or prompt for confirmation */
6776 case BeginningOfGame:
6777 if (appData.icsActive) return FALSE;
6778 if (!appData.noChessProgram) {
6780 DisplayMoveError(_("You are playing White"));
6787 if (!white_piece && WhiteOnMove(currentMove)) {
6788 DisplayMoveError(_("It is White's turn"));
6791 if (white_piece && !WhiteOnMove(currentMove)) {
6792 DisplayMoveError(_("It is Black's turn"));
6801 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6802 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6803 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6804 && gameMode != AnalyzeFile && gameMode != Training) {
6805 DisplayMoveError(_("Displayed position is not current"));
6812 OnlyMove (int *x, int *y, Boolean captures)
6814 DisambiguateClosure cl;
6815 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6817 case MachinePlaysBlack:
6818 case IcsPlayingWhite:
6819 case BeginningOfGame:
6820 if(!WhiteOnMove(currentMove)) return FALSE;
6822 case MachinePlaysWhite:
6823 case IcsPlayingBlack:
6824 if(WhiteOnMove(currentMove)) return FALSE;
6831 cl.pieceIn = EmptySquare;
6836 cl.promoCharIn = NULLCHAR;
6837 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6838 if( cl.kind == NormalMove ||
6839 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6840 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6841 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6848 if(cl.kind != ImpossibleMove) return FALSE;
6849 cl.pieceIn = EmptySquare;
6854 cl.promoCharIn = NULLCHAR;
6855 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6856 if( cl.kind == NormalMove ||
6857 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6858 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6859 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6864 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6870 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6871 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6872 int lastLoadGameUseList = FALSE;
6873 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6874 ChessMove lastLoadGameStart = EndOfFile;
6876 Boolean addToBookFlag;
6879 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6883 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6885 /* Check if the user is playing in turn. This is complicated because we
6886 let the user "pick up" a piece before it is his turn. So the piece he
6887 tried to pick up may have been captured by the time he puts it down!
6888 Therefore we use the color the user is supposed to be playing in this
6889 test, not the color of the piece that is currently on the starting
6890 square---except in EditGame mode, where the user is playing both
6891 sides; fortunately there the capture race can't happen. (It can
6892 now happen in IcsExamining mode, but that's just too bad. The user
6893 will get a somewhat confusing message in that case.)
6898 case TwoMachinesPlay:
6902 /* We switched into a game mode where moves are not accepted,
6903 perhaps while the mouse button was down. */
6906 case MachinePlaysWhite:
6907 /* User is moving for Black */
6908 if (WhiteOnMove(currentMove)) {
6909 DisplayMoveError(_("It is White's turn"));
6914 case MachinePlaysBlack:
6915 /* User is moving for White */
6916 if (!WhiteOnMove(currentMove)) {
6917 DisplayMoveError(_("It is Black's turn"));
6922 case PlayFromGameFile:
6923 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6926 case BeginningOfGame:
6929 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6930 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6931 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6932 /* User is moving for Black */
6933 if (WhiteOnMove(currentMove)) {
6934 DisplayMoveError(_("It is White's turn"));
6938 /* User is moving for White */
6939 if (!WhiteOnMove(currentMove)) {
6940 DisplayMoveError(_("It is Black's turn"));
6946 case IcsPlayingBlack:
6947 /* User is moving for Black */
6948 if (WhiteOnMove(currentMove)) {
6949 if (!appData.premove) {
6950 DisplayMoveError(_("It is White's turn"));
6951 } else if (toX >= 0 && toY >= 0) {
6954 premoveFromX = fromX;
6955 premoveFromY = fromY;
6956 premovePromoChar = promoChar;
6958 if (appData.debugMode)
6959 fprintf(debugFP, "Got premove: fromX %d,"
6960 "fromY %d, toX %d, toY %d\n",
6961 fromX, fromY, toX, toY);
6967 case IcsPlayingWhite:
6968 /* User is moving for White */
6969 if (!WhiteOnMove(currentMove)) {
6970 if (!appData.premove) {
6971 DisplayMoveError(_("It is Black's turn"));
6972 } else if (toX >= 0 && toY >= 0) {
6975 premoveFromX = fromX;
6976 premoveFromY = fromY;
6977 premovePromoChar = promoChar;
6979 if (appData.debugMode)
6980 fprintf(debugFP, "Got premove: fromX %d,"
6981 "fromY %d, toX %d, toY %d\n",
6982 fromX, fromY, toX, toY);
6992 /* EditPosition, empty square, or different color piece;
6993 click-click move is possible */
6994 if (toX == -2 || toY == -2) {
6995 boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
6996 DrawPosition(FALSE, boards[currentMove]);
6998 } else if (toX >= 0 && toY >= 0) {
6999 if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7000 ChessSquare q, p = boards[0][rf][ff];
7001 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
7002 if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
7003 else p = CHUDEMOTED (q = boards[0][rf][ff]);
7004 if(PieceToChar(q) == '+') gatingPiece = p;
7006 boards[0][toY][toX] = boards[0][fromY][fromX];
7007 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7008 if(boards[0][fromY][0] != EmptySquare) {
7009 if(boards[0][fromY][1]) boards[0][fromY][1]--;
7010 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
7013 if(fromX == BOARD_RGHT+1) {
7014 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7015 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7016 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7019 boards[0][fromY][fromX] = gatingPiece;
7020 DrawPosition(FALSE, boards[currentMove]);
7026 if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7027 pup = boards[currentMove][toY][toX];
7029 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7030 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7031 if( pup != EmptySquare ) return;
7032 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7033 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7034 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7035 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7036 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7037 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7038 while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7042 /* [HGM] always test for legality, to get promotion info */
7043 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7044 fromY, fromX, toY, toX, promoChar);
7046 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7048 /* [HGM] but possibly ignore an IllegalMove result */
7049 if (appData.testLegality) {
7050 if (moveType == IllegalMove || moveType == ImpossibleMove) {
7051 DisplayMoveError(_("Illegal move"));
7056 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7057 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7058 ClearPremoveHighlights(); // was included
7059 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
7063 if(addToBookFlag) { // adding moves to book
7064 char buf[MSG_SIZ], move[MSG_SIZ];
7065 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7066 if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d", fromX + AAA, fromY + ONE - '0', killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0');
7067 snprintf(buf, MSG_SIZ, " 0.0%% 1 %s\n", move);
7069 addToBookFlag = FALSE;
7074 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7077 /* Common tail of UserMoveEvent and DropMenuEvent */
7079 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7083 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7084 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7085 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7086 if(WhiteOnMove(currentMove)) {
7087 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7089 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7093 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7094 move type in caller when we know the move is a legal promotion */
7095 if(moveType == NormalMove && promoChar)
7096 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7098 /* [HGM] <popupFix> The following if has been moved here from
7099 UserMoveEvent(). Because it seemed to belong here (why not allow
7100 piece drops in training games?), and because it can only be
7101 performed after it is known to what we promote. */
7102 if (gameMode == Training) {
7103 /* compare the move played on the board to the next move in the
7104 * game. If they match, display the move and the opponent's response.
7105 * If they don't match, display an error message.
7109 CopyBoard(testBoard, boards[currentMove]);
7110 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7112 if (CompareBoards(testBoard, boards[currentMove+1])) {
7113 ForwardInner(currentMove+1);
7115 /* Autoplay the opponent's response.
7116 * if appData.animate was TRUE when Training mode was entered,
7117 * the response will be animated.
7119 saveAnimate = appData.animate;
7120 appData.animate = animateTraining;
7121 ForwardInner(currentMove+1);
7122 appData.animate = saveAnimate;
7124 /* check for the end of the game */
7125 if (currentMove >= forwardMostMove) {
7126 gameMode = PlayFromGameFile;
7128 SetTrainingModeOff();
7129 DisplayInformation(_("End of game"));
7132 DisplayError(_("Incorrect move"), 0);
7137 /* Ok, now we know that the move is good, so we can kill
7138 the previous line in Analysis Mode */
7139 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7140 && currentMove < forwardMostMove) {
7141 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7142 else forwardMostMove = currentMove;
7147 /* If we need the chess program but it's dead, restart it */
7148 ResurrectChessProgram();
7150 /* A user move restarts a paused game*/
7154 thinkOutput[0] = NULLCHAR;
7156 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7158 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7159 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7163 if (gameMode == BeginningOfGame) {
7164 if (appData.noChessProgram) {
7165 gameMode = EditGame;
7169 gameMode = MachinePlaysBlack;
7172 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7174 if (first.sendName) {
7175 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7176 SendToProgram(buf, &first);
7183 /* Relay move to ICS or chess engine */
7184 if (appData.icsActive) {
7185 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7186 gameMode == IcsExamining) {
7187 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7188 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7190 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7192 // also send plain move, in case ICS does not understand atomic claims
7193 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7197 if (first.sendTime && (gameMode == BeginningOfGame ||
7198 gameMode == MachinePlaysWhite ||
7199 gameMode == MachinePlaysBlack)) {
7200 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7202 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7203 // [HGM] book: if program might be playing, let it use book
7204 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7205 first.maybeThinking = TRUE;
7206 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7207 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7208 SendBoard(&first, currentMove+1);
7209 if(second.analyzing) {
7210 if(!second.useSetboard) SendToProgram("undo\n", &second);
7211 SendBoard(&second, currentMove+1);
7214 SendMoveToProgram(forwardMostMove-1, &first);
7215 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7217 if (currentMove == cmailOldMove + 1) {
7218 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7222 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7226 if(appData.testLegality)
7227 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7233 if (WhiteOnMove(currentMove)) {
7234 GameEnds(BlackWins, "Black mates", GE_PLAYER);
7236 GameEnds(WhiteWins, "White mates", GE_PLAYER);
7240 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7245 case MachinePlaysBlack:
7246 case MachinePlaysWhite:
7247 /* disable certain menu options while machine is thinking */
7248 SetMachineThinkingEnables();
7255 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7256 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7258 if(bookHit) { // [HGM] book: simulate book reply
7259 static char bookMove[MSG_SIZ]; // a bit generous?
7261 programStats.nodes = programStats.depth = programStats.time =
7262 programStats.score = programStats.got_only_move = 0;
7263 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7265 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7266 strcat(bookMove, bookHit);
7267 HandleMachineMove(bookMove, &first);
7273 MarkByFEN(char *fen)
7276 if(!appData.markers || !appData.highlightDragging) return;
7277 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7278 r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7282 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7283 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7284 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7285 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7286 if(*fen == 'T') marker[r][f++] = 0; else
7287 if(*fen == 'Y') marker[r][f++] = 1; else
7288 if(*fen == 'G') marker[r][f++] = 3; else
7289 if(*fen == 'B') marker[r][f++] = 4; else
7290 if(*fen == 'C') marker[r][f++] = 5; else
7291 if(*fen == 'M') marker[r][f++] = 6; else
7292 if(*fen == 'W') marker[r][f++] = 7; else
7293 if(*fen == 'D') marker[r][f++] = 8; else
7294 if(*fen == 'R') marker[r][f++] = 2; else {
7295 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7298 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7302 DrawPosition(TRUE, NULL);
7305 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7308 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7310 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7311 Markers *m = (Markers *) closure;
7312 if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7313 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7314 || kind == WhiteCapturesEnPassant
7315 || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 1;
7316 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 1;
7319 static int hoverSavedValid;
7322 MarkTargetSquares (int clear)
7325 if(clear) { // no reason to ever suppress clearing
7326 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7327 hoverSavedValid = 0;
7328 if(!sum) return; // nothing was cleared,no redraw needed
7331 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7332 !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7333 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7334 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7335 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7337 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7340 DrawPosition(FALSE, NULL);
7344 Explode (Board board, int fromX, int fromY, int toX, int toY)
7346 if(gameInfo.variant == VariantAtomic &&
7347 (board[toY][toX] != EmptySquare || // capture?
7348 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7349 board[fromY][fromX] == BlackPawn )
7351 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7357 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7360 CanPromote (ChessSquare piece, int y)
7362 int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7363 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7364 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7365 if(IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantXiangqi ||
7366 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7367 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7368 gameInfo.variant == VariantMakruk) return FALSE;
7369 return (piece == BlackPawn && y <= zone ||
7370 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7371 piece == BlackLance && y <= zone ||
7372 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7376 HoverEvent (int xPix, int yPix, int x, int y)
7378 static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7380 if(!first.highlight) return;
7381 if(fromX != oldFromX || fromY != oldFromY) oldX = oldY = -1; // kludge to fake entry on from-click
7382 if(x == oldX && y == oldY) return; // only do something if we enter new square
7383 oldFromX = fromX; oldFromY = fromY;
7384 if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7385 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7386 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7387 hoverSavedValid = 1;
7388 } else if(oldX != x || oldY != y) {
7389 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7390 if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7391 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7392 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7393 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7395 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7396 SendToProgram(buf, &first);
7399 // SetHighlights(fromX, fromY, x, y);
7403 void ReportClick(char *action, int x, int y)
7405 char buf[MSG_SIZ]; // Inform engine of what user does
7407 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7408 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7409 legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7410 if(!first.highlight || gameMode == EditPosition) return;
7411 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7412 SendToProgram(buf, &first);
7415 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7418 LeftClick (ClickType clickType, int xPix, int yPix)
7421 Boolean saveAnimate;
7422 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7423 char promoChoice = NULLCHAR;
7425 static TimeMark lastClickTime, prevClickTime;
7427 x = EventToSquare(xPix, BOARD_WIDTH);
7428 y = EventToSquare(yPix, BOARD_HEIGHT);
7429 if (!flipView && y >= 0) {
7430 y = BOARD_HEIGHT - 1 - y;
7432 if (flipView && x >= 0) {
7433 x = BOARD_WIDTH - 1 - x;
7436 if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7438 RightClick(clickType, xPix, yPix, &dummy, &dummy);
7443 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7445 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7447 if (clickType == Press) ErrorPopDown();
7448 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7450 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7451 defaultPromoChoice = promoSweep;
7452 promoSweep = EmptySquare; // terminate sweep
7453 promoDefaultAltered = TRUE;
7454 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7457 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7458 if(clickType == Release) return; // ignore upclick of click-click destination
7459 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7460 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7461 if(gameInfo.holdingsWidth &&
7462 (WhiteOnMove(currentMove)
7463 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7464 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7465 // click in right holdings, for determining promotion piece
7466 ChessSquare p = boards[currentMove][y][x];
7467 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7468 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7469 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7470 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7475 DrawPosition(FALSE, boards[currentMove]);
7479 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7480 if(clickType == Press
7481 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7482 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7483 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7486 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7487 // could be static click on premove from-square: abort premove
7489 ClearPremoveHighlights();
7492 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7493 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7495 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7496 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7497 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7498 defaultPromoChoice = DefaultPromoChoice(side);
7501 autoQueen = appData.alwaysPromoteToQueen;
7505 gatingPiece = EmptySquare;
7506 if (clickType != Press) {
7507 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7508 DragPieceEnd(xPix, yPix); dragging = 0;
7509 DrawPosition(FALSE, NULL);
7513 doubleClick = FALSE;
7514 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7515 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7517 fromX = x; fromY = y; toX = toY = killX = killY = -1;
7518 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7519 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7520 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7522 if (OKToStartUserMove(fromX, fromY)) {
7524 ReportClick("lift", x, y);
7525 MarkTargetSquares(0);
7526 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7527 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7528 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7529 promoSweep = defaultPromoChoice;
7530 selectFlag = 0; lastX = xPix; lastY = yPix;
7531 Sweep(0); // Pawn that is going to promote: preview promotion piece
7532 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7534 if (appData.highlightDragging) {
7535 SetHighlights(fromX, fromY, -1, -1);
7539 } else fromX = fromY = -1;
7543 printf("to click %d,%d\n",x,y);
7545 if (clickType == Press && gameMode != EditPosition) {
7550 // ignore off-board to clicks
7551 if(y < 0 || x < 0) return;
7553 /* Check if clicking again on the same color piece */
7554 fromP = boards[currentMove][fromY][fromX];
7555 toP = boards[currentMove][y][x];
7556 frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7557 if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7558 marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7559 ((WhitePawn <= fromP && fromP <= WhiteKing &&
7560 WhitePawn <= toP && toP <= WhiteKing &&
7561 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7562 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7563 (BlackPawn <= fromP && fromP <= BlackKing &&
7564 BlackPawn <= toP && toP <= BlackKing &&
7565 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7566 !(fromP == BlackKing && toP == BlackRook && frc)))) {
7567 /* Clicked again on same color piece -- changed his mind */
7568 second = (x == fromX && y == fromY);
7570 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7571 second = FALSE; // first double-click rather than scond click
7572 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7574 promoDefaultAltered = FALSE;
7575 MarkTargetSquares(1);
7576 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7577 if (appData.highlightDragging) {
7578 SetHighlights(x, y, -1, -1);
7582 if (OKToStartUserMove(x, y)) {
7583 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7584 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7585 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7586 gatingPiece = boards[currentMove][fromY][fromX];
7587 else gatingPiece = doubleClick ? fromP : EmptySquare;
7589 fromY = y; dragging = 1;
7590 if(!second) ReportClick("lift", x, y);
7591 MarkTargetSquares(0);
7592 DragPieceBegin(xPix, yPix, FALSE);
7593 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7594 promoSweep = defaultPromoChoice;
7595 selectFlag = 0; lastX = xPix; lastY = yPix;
7596 Sweep(0); // Pawn that is going to promote: preview promotion piece
7600 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7603 // ignore clicks on holdings
7604 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7606 printf("A type=%d\n",clickType);
7608 if(x == fromX && y == fromY && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7609 gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7613 if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7614 DragPieceEnd(xPix, yPix); dragging = 0;
7616 // a deferred attempt to click-click move an empty square on top of a piece
7617 boards[currentMove][y][x] = EmptySquare;
7619 DrawPosition(FALSE, boards[currentMove]);
7620 fromX = fromY = -1; clearFlag = 0;
7623 if (appData.animateDragging) {
7624 /* Undo animation damage if any */
7625 DrawPosition(FALSE, NULL);
7628 /* Second up/down in same square; just abort move */
7631 gatingPiece = EmptySquare;
7632 MarkTargetSquares(1);
7635 ClearPremoveHighlights();
7637 /* First upclick in same square; start click-click mode */
7638 SetHighlights(x, y, -1, -1);
7645 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7646 fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7647 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7648 DisplayMessage(_("only marked squares are legal"),"");
7649 DrawPosition(TRUE, NULL);
7650 return; // ignore to-click
7652 printf("(%d,%d)-(%d,%d) %d %d\n",fromX,fromY,toX,toY,x,y);
7653 /* we now have a different from- and (possibly off-board) to-square */
7654 /* Completed move */
7655 if(!sweepSelecting) {
7660 piece = boards[currentMove][fromY][fromX];
7662 saveAnimate = appData.animate;
7663 if (clickType == Press) {
7664 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7665 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7666 // must be Edit Position mode with empty-square selected
7667 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7668 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7671 if(dragging == 2) { // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7674 if(x == killX && y == killY) { // second click on this square, which was selected as first-leg target
7675 killX = killY = -1; // this informs us no second leg is coming, so treat as to-click without intermediate
7677 if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7678 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7679 if(appData.sweepSelect) {
7680 promoSweep = defaultPromoChoice;
7681 if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7682 selectFlag = 0; lastX = xPix; lastY = yPix;
7683 Sweep(0); // Pawn that is going to promote: preview promotion piece
7685 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7686 MarkTargetSquares(1);
7688 return; // promo popup appears on up-click
7690 /* Finish clickclick move */
7691 if (appData.animate || appData.highlightLastMove) {
7692 SetHighlights(fromX, fromY, toX, toY);
7696 } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7697 sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7698 if (appData.animate || appData.highlightLastMove) {
7699 SetHighlights(fromX, fromY, toX, toY);
7705 // [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
7706 /* Finish drag move */
7707 if (appData.highlightLastMove) {
7708 SetHighlights(fromX, fromY, toX, toY);
7713 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7714 if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7715 dragging *= 2; // flag button-less dragging if we are dragging
7716 MarkTargetSquares(1);
7717 if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7719 kill2X = killX; kill2Y = killY;
7720 killX = x; killY = y; //remeber this square as intermediate
7721 ReportClick("put", x, y); // and inform engine
7722 ReportClick("lift", x, y);
7723 MarkTargetSquares(0);
7727 DragPieceEnd(xPix, yPix); dragging = 0;
7728 /* Don't animate move and drag both */
7729 appData.animate = FALSE;
7732 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7733 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7734 ChessSquare piece = boards[currentMove][fromY][fromX];
7735 if(gameMode == EditPosition && piece != EmptySquare &&
7736 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7739 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7740 n = PieceToNumber(piece - (int)BlackPawn);
7741 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7742 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7743 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7745 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7746 n = PieceToNumber(piece);
7747 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7748 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7749 boards[currentMove][n][BOARD_WIDTH-2]++;
7751 boards[currentMove][fromY][fromX] = EmptySquare;
7755 MarkTargetSquares(1);
7756 DrawPosition(TRUE, boards[currentMove]);
7760 // off-board moves should not be highlighted
7761 if(x < 0 || y < 0) ClearHighlights();
7762 else ReportClick("put", x, y);
7764 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7766 if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7768 if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7769 SetHighlights(fromX, fromY, toX, toY);
7770 MarkTargetSquares(1);
7771 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7772 // [HGM] super: promotion to captured piece selected from holdings
7773 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7774 promotionChoice = TRUE;
7775 // kludge follows to temporarily execute move on display, without promoting yet
7776 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7777 boards[currentMove][toY][toX] = p;
7778 DrawPosition(FALSE, boards[currentMove]);
7779 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7780 boards[currentMove][toY][toX] = q;
7781 DisplayMessage("Click in holdings to choose piece", "");
7784 PromotionPopUp(promoChoice);
7786 int oldMove = currentMove;
7787 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7788 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7789 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7790 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7791 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7792 DrawPosition(TRUE, boards[currentMove]);
7793 MarkTargetSquares(1);
7796 appData.animate = saveAnimate;
7797 if (appData.animate || appData.animateDragging) {
7798 /* Undo animation damage if needed */
7799 DrawPosition(FALSE, NULL);
7804 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7805 { // front-end-free part taken out of PieceMenuPopup
7806 int whichMenu; int xSqr, ySqr;
7808 if(seekGraphUp) { // [HGM] seekgraph
7809 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7810 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7814 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7815 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7816 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7817 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7818 if(action == Press) {
7819 originalFlip = flipView;
7820 flipView = !flipView; // temporarily flip board to see game from partners perspective
7821 DrawPosition(TRUE, partnerBoard);
7822 DisplayMessage(partnerStatus, "");
7824 } else if(action == Release) {
7825 flipView = originalFlip;
7826 DrawPosition(TRUE, boards[currentMove]);
7832 xSqr = EventToSquare(x, BOARD_WIDTH);
7833 ySqr = EventToSquare(y, BOARD_HEIGHT);
7834 if (action == Release) {
7835 if(pieceSweep != EmptySquare) {
7836 EditPositionMenuEvent(pieceSweep, toX, toY);
7837 pieceSweep = EmptySquare;
7838 } else UnLoadPV(); // [HGM] pv
7840 if (action != Press) return -2; // return code to be ignored
7843 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7845 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7846 if (xSqr < 0 || ySqr < 0) return -1;
7847 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7848 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7849 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7850 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7854 if(!appData.icsEngineAnalyze) return -1;
7855 case IcsPlayingWhite:
7856 case IcsPlayingBlack:
7857 if(!appData.zippyPlay) goto noZip;
7860 case MachinePlaysWhite:
7861 case MachinePlaysBlack:
7862 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7863 if (!appData.dropMenu) {
7865 return 2; // flag front-end to grab mouse events
7867 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7868 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7871 if (xSqr < 0 || ySqr < 0) return -1;
7872 if (!appData.dropMenu || appData.testLegality &&
7873 gameInfo.variant != VariantBughouse &&
7874 gameInfo.variant != VariantCrazyhouse) return -1;
7875 whichMenu = 1; // drop menu
7881 if (((*fromX = xSqr) < 0) ||
7882 ((*fromY = ySqr) < 0)) {
7883 *fromX = *fromY = -1;
7887 *fromX = BOARD_WIDTH - 1 - *fromX;
7889 *fromY = BOARD_HEIGHT - 1 - *fromY;
7895 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7897 // char * hint = lastHint;
7898 FrontEndProgramStats stats;
7900 stats.which = cps == &first ? 0 : 1;
7901 stats.depth = cpstats->depth;
7902 stats.nodes = cpstats->nodes;
7903 stats.score = cpstats->score;
7904 stats.time = cpstats->time;
7905 stats.pv = cpstats->movelist;
7906 stats.hint = lastHint;
7907 stats.an_move_index = 0;
7908 stats.an_move_count = 0;
7910 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7911 stats.hint = cpstats->move_name;
7912 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7913 stats.an_move_count = cpstats->nr_moves;
7916 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
7918 SetProgramStats( &stats );
7922 ClearEngineOutputPane (int which)
7924 static FrontEndProgramStats dummyStats;
7925 dummyStats.which = which;
7926 dummyStats.pv = "#";
7927 SetProgramStats( &dummyStats );
7930 #define MAXPLAYERS 500
7933 TourneyStandings (int display)
7935 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7936 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7937 char result, *p, *names[MAXPLAYERS];
7939 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7940 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7941 names[0] = p = strdup(appData.participants);
7942 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7944 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7946 while(result = appData.results[nr]) {
7947 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7948 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7949 wScore = bScore = 0;
7951 case '+': wScore = 2; break;
7952 case '-': bScore = 2; break;
7953 case '=': wScore = bScore = 1; break;
7955 case '*': return strdup("busy"); // tourney not finished
7963 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7964 for(w=0; w<nPlayers; w++) {
7966 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7967 ranking[w] = b; points[w] = bScore; score[b] = -2;
7969 p = malloc(nPlayers*34+1);
7970 for(w=0; w<nPlayers && w<display; w++)
7971 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7977 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7978 { // count all piece types
7980 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7981 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7982 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7985 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7986 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7987 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7988 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7989 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7990 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7995 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7997 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7998 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8000 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8001 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8002 if(myPawns == 2 && nMine == 3) // KPP
8003 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8004 if(myPawns == 1 && nMine == 2) // KP
8005 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8006 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8007 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8008 if(myPawns) return FALSE;
8009 if(pCnt[WhiteRook+side])
8010 return pCnt[BlackRook-side] ||
8011 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8012 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8013 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8014 if(pCnt[WhiteCannon+side]) {
8015 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8016 return majorDefense || pCnt[BlackAlfil-side] >= 2;
8018 if(pCnt[WhiteKnight+side])
8019 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8024 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8026 VariantClass v = gameInfo.variant;
8028 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8029 if(v == VariantShatranj) return TRUE; // always winnable through baring
8030 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8031 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8033 if(v == VariantXiangqi) {
8034 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8036 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8037 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8038 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8039 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8040 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8041 if(stale) // we have at least one last-rank P plus perhaps C
8042 return majors // KPKX
8043 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8045 return pCnt[WhiteFerz+side] // KCAK
8046 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8047 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8048 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8050 } else if(v == VariantKnightmate) {
8051 if(nMine == 1) return FALSE;
8052 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8053 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8054 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8056 if(nMine == 1) return FALSE; // bare King
8057 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
8058 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8059 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8060 // by now we have King + 1 piece (or multiple Bishops on the same color)
8061 if(pCnt[WhiteKnight+side])
8062 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8063 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8064 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8066 return (pCnt[BlackKnight-side]); // KBKN, KFKN
8067 if(pCnt[WhiteAlfil+side])
8068 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8069 if(pCnt[WhiteWazir+side])
8070 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8077 CompareWithRights (Board b1, Board b2)
8080 if(!CompareBoards(b1, b2)) return FALSE;
8081 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8082 /* compare castling rights */
8083 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8084 rights++; /* King lost rights, while rook still had them */
8085 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8086 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8087 rights++; /* but at least one rook lost them */
8089 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8091 if( b1[CASTLING][5] != NoRights ) {
8092 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8099 Adjudicate (ChessProgramState *cps)
8100 { // [HGM] some adjudications useful with buggy engines
8101 // [HGM] adjudicate: made into separate routine, which now can be called after every move
8102 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8103 // Actually ending the game is now based on the additional internal condition canAdjudicate.
8104 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8105 int k, drop, count = 0; static int bare = 1;
8106 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8107 Boolean canAdjudicate = !appData.icsActive;
8109 // most tests only when we understand the game, i.e. legality-checking on
8110 if( appData.testLegality )
8111 { /* [HGM] Some more adjudications for obstinate engines */
8112 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8113 static int moveCount = 6;
8115 char *reason = NULL;
8117 /* Count what is on board. */
8118 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8120 /* Some material-based adjudications that have to be made before stalemate test */
8121 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8122 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8123 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8124 if(canAdjudicate && appData.checkMates) {
8126 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8127 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8128 "Xboard adjudication: King destroyed", GE_XBOARD );
8133 /* Bare King in Shatranj (loses) or Losers (wins) */
8134 if( nrW == 1 || nrB == 1) {
8135 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8136 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
8137 if(canAdjudicate && appData.checkMates) {
8139 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8140 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8141 "Xboard adjudication: Bare king", GE_XBOARD );
8145 if( gameInfo.variant == VariantShatranj && --bare < 0)
8147 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8148 if(canAdjudicate && appData.checkMates) {
8149 /* but only adjudicate if adjudication enabled */
8151 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8152 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8153 "Xboard adjudication: Bare king", GE_XBOARD );
8160 // don't wait for engine to announce game end if we can judge ourselves
8161 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8163 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8164 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
8165 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8166 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8169 reason = "Xboard adjudication: 3rd check";
8170 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8181 reason = "Xboard adjudication: Stalemate";
8182 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8183 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
8184 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8185 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
8186 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8187 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8188 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8189 EP_CHECKMATE : EP_WINS);
8190 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8191 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8195 reason = "Xboard adjudication: Checkmate";
8196 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8197 if(gameInfo.variant == VariantShogi) {
8198 if(forwardMostMove > backwardMostMove
8199 && moveList[forwardMostMove-1][1] == '@'
8200 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8201 reason = "XBoard adjudication: pawn-drop mate";
8202 boards[forwardMostMove][EP_STATUS] = EP_WINS;
8208 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8210 result = GameIsDrawn; break;
8212 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8214 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8218 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8220 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8221 GameEnds( result, reason, GE_XBOARD );
8225 /* Next absolutely insufficient mating material. */
8226 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8227 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8228 { /* includes KBK, KNK, KK of KBKB with like Bishops */
8230 /* always flag draws, for judging claims */
8231 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8233 if(canAdjudicate && appData.materialDraws) {
8234 /* but only adjudicate them if adjudication enabled */
8235 if(engineOpponent) {
8236 SendToProgram("force\n", engineOpponent); // suppress reply
8237 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8239 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8244 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8245 if(gameInfo.variant == VariantXiangqi ?
8246 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8248 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8249 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
8250 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
8251 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8253 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8254 { /* if the first 3 moves do not show a tactical win, declare draw */
8255 if(engineOpponent) {
8256 SendToProgram("force\n", engineOpponent); // suppress reply
8257 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8259 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8262 } else moveCount = 6;
8265 // Repetition draws and 50-move rule can be applied independently of legality testing
8267 /* Check for rep-draws */
8269 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8270 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8271 for(k = forwardMostMove-2;
8272 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8273 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8274 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8277 if(CompareBoards(boards[k], boards[forwardMostMove])) {
8278 /* compare castling rights */
8279 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8280 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8281 rights++; /* King lost rights, while rook still had them */
8282 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8283 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8284 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8285 rights++; /* but at least one rook lost them */
8287 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8288 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8290 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8291 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8292 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8295 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8296 && appData.drawRepeats > 1) {
8297 /* adjudicate after user-specified nr of repeats */
8298 int result = GameIsDrawn;
8299 char *details = "XBoard adjudication: repetition draw";
8300 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8301 // [HGM] xiangqi: check for forbidden perpetuals
8302 int m, ourPerpetual = 1, hisPerpetual = 1;
8303 for(m=forwardMostMove; m>k; m-=2) {
8304 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8305 ourPerpetual = 0; // the current mover did not always check
8306 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8307 hisPerpetual = 0; // the opponent did not always check
8309 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8310 ourPerpetual, hisPerpetual);
8311 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8312 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8313 details = "Xboard adjudication: perpetual checking";
8315 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8316 break; // (or we would have caught him before). Abort repetition-checking loop.
8318 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8319 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8321 details = "Xboard adjudication: repetition";
8323 } else // it must be XQ
8324 // Now check for perpetual chases
8325 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8326 hisPerpetual = PerpetualChase(k, forwardMostMove);
8327 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8328 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8329 static char resdet[MSG_SIZ];
8330 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8332 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8334 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8335 break; // Abort repetition-checking loop.
8337 // if neither of us is checking or chasing all the time, or both are, it is draw
8339 if(engineOpponent) {
8340 SendToProgram("force\n", engineOpponent); // suppress reply
8341 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8343 GameEnds( result, details, GE_XBOARD );
8346 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8347 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8351 /* Now we test for 50-move draws. Determine ply count */
8352 count = forwardMostMove;
8353 /* look for last irreversble move */
8354 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8356 /* if we hit starting position, add initial plies */
8357 if( count == backwardMostMove )
8358 count -= initialRulePlies;
8359 count = forwardMostMove - count;
8360 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8361 // adjust reversible move counter for checks in Xiangqi
8362 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8363 if(i < backwardMostMove) i = backwardMostMove;
8364 while(i <= forwardMostMove) {
8365 lastCheck = inCheck; // check evasion does not count
8366 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8367 if(inCheck || lastCheck) count--; // check does not count
8372 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8373 /* this is used to judge if draw claims are legal */
8374 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8375 if(engineOpponent) {
8376 SendToProgram("force\n", engineOpponent); // suppress reply
8377 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8379 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8383 /* if draw offer is pending, treat it as a draw claim
8384 * when draw condition present, to allow engines a way to
8385 * claim draws before making their move to avoid a race
8386 * condition occurring after their move
8388 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8390 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8391 p = "Draw claim: 50-move rule";
8392 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8393 p = "Draw claim: 3-fold repetition";
8394 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8395 p = "Draw claim: insufficient mating material";
8396 if( p != NULL && canAdjudicate) {
8397 if(engineOpponent) {
8398 SendToProgram("force\n", engineOpponent); // suppress reply
8399 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8401 GameEnds( GameIsDrawn, p, GE_XBOARD );
8406 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8407 if(engineOpponent) {
8408 SendToProgram("force\n", engineOpponent); // suppress reply
8409 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8411 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8417 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8418 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8419 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8424 int pieces[10], squares[10], cnt=0, r, f, res;
8426 static PPROBE_EGBB probeBB;
8427 if(!appData.testLegality) return 10;
8428 if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8429 if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8430 if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8431 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8432 ChessSquare piece = boards[forwardMostMove][r][f];
8433 int black = (piece >= BlackPawn);
8434 int type = piece - black*BlackPawn;
8435 if(piece == EmptySquare) continue;
8436 if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8437 if(type == WhiteKing) type = WhiteQueen + 1;
8438 type = egbbCode[type];
8439 squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8440 pieces[cnt] = type + black*6;
8441 if(++cnt > 5) return 11;
8443 pieces[cnt] = squares[cnt] = 0;
8445 if(loaded == 2) return 13; // loading failed before
8447 char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8450 loaded = 2; // prepare for failure
8451 if(!path) return 13; // no egbb installed
8452 strncpy(buf, path + 8, MSG_SIZ);
8453 if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8454 snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8455 lib = LoadLibrary(buf);
8456 if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8457 loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8458 probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8459 if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8460 p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8461 loaded = 1; // success!
8463 res = probeBB(forwardMostMove & 1, pieces, squares);
8464 return res > 0 ? 1 : res < 0 ? -1 : 0;
8468 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8469 { // [HGM] book: this routine intercepts moves to simulate book replies
8470 char *bookHit = NULL;
8472 if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8474 snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8475 SendToProgram(buf, cps);
8477 //first determine if the incoming move brings opponent into his book
8478 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8479 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8480 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8481 if(bookHit != NULL && !cps->bookSuspend) {
8482 // make sure opponent is not going to reply after receiving move to book position
8483 SendToProgram("force\n", cps);
8484 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8486 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8487 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8488 // now arrange restart after book miss
8490 // after a book hit we never send 'go', and the code after the call to this routine
8491 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8492 char buf[MSG_SIZ], *move = bookHit;
8494 int fromX, fromY, toX, toY;
8498 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8499 &fromX, &fromY, &toX, &toY, &promoChar)) {
8500 (void) CoordsToAlgebraic(boards[forwardMostMove],
8501 PosFlags(forwardMostMove),
8502 fromY, fromX, toY, toX, promoChar, move);
8504 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8508 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8509 SendToProgram(buf, cps);
8510 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8511 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8512 SendToProgram("go\n", cps);
8513 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8514 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8515 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8516 SendToProgram("go\n", cps);
8517 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8519 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8523 LoadError (char *errmess, ChessProgramState *cps)
8524 { // unloads engine and switches back to -ncp mode if it was first
8525 if(cps->initDone) return FALSE;
8526 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8527 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8530 appData.noChessProgram = TRUE;
8531 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8532 gameMode = BeginningOfGame; ModeHighlight();
8535 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8536 DisplayMessage("", ""); // erase waiting message
8537 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8542 ChessProgramState *savedState;
8544 DeferredBookMove (void)
8546 if(savedState->lastPing != savedState->lastPong)
8547 ScheduleDelayedEvent(DeferredBookMove, 10);
8549 HandleMachineMove(savedMessage, savedState);
8552 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8553 static ChessProgramState *stalledEngine;
8554 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8557 HandleMachineMove (char *message, ChessProgramState *cps)
8559 static char firstLeg[20];
8560 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8561 char realname[MSG_SIZ];
8562 int fromX, fromY, toX, toY;
8564 char promoChar, roar;
8566 int machineWhite, oldError;
8569 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8570 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8571 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8572 DisplayError(_("Invalid pairing from pairing engine"), 0);
8575 pairingReceived = 1;
8577 return; // Skim the pairing messages here.
8580 oldError = cps->userError; cps->userError = 0;
8582 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8584 * Kludge to ignore BEL characters
8586 while (*message == '\007') message++;
8589 * [HGM] engine debug message: ignore lines starting with '#' character
8591 if(cps->debug && *message == '#') return;
8594 * Look for book output
8596 if (cps == &first && bookRequested) {
8597 if (message[0] == '\t' || message[0] == ' ') {
8598 /* Part of the book output is here; append it */
8599 strcat(bookOutput, message);
8600 strcat(bookOutput, " \n");
8602 } else if (bookOutput[0] != NULLCHAR) {
8603 /* All of book output has arrived; display it */
8604 char *p = bookOutput;
8605 while (*p != NULLCHAR) {
8606 if (*p == '\t') *p = ' ';
8609 DisplayInformation(bookOutput);
8610 bookRequested = FALSE;
8611 /* Fall through to parse the current output */
8616 * Look for machine move.
8618 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8619 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8621 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8622 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8623 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8624 stalledEngine = cps;
8625 if(appData.ponderNextMove) { // bring opponent out of ponder
8626 if(gameMode == TwoMachinesPlay) {
8627 if(cps->other->pause)
8628 PauseEngine(cps->other);
8630 SendToProgram("easy\n", cps->other);
8639 /* This method is only useful on engines that support ping */
8640 if(abortEngineThink) {
8641 if (appData.debugMode) {
8642 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8644 SendToProgram("undo\n", cps);
8648 if (cps->lastPing != cps->lastPong) {
8649 /* Extra move from before last new; ignore */
8650 if (appData.debugMode) {
8651 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8659 case BeginningOfGame:
8660 /* Extra move from before last reset; ignore */
8661 if (appData.debugMode) {
8662 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8669 /* Extra move after we tried to stop. The mode test is
8670 not a reliable way of detecting this problem, but it's
8671 the best we can do on engines that don't support ping.
8673 if (appData.debugMode) {
8674 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8675 cps->which, gameMode);
8677 SendToProgram("undo\n", cps);
8680 case MachinePlaysWhite:
8681 case IcsPlayingWhite:
8682 machineWhite = TRUE;
8685 case MachinePlaysBlack:
8686 case IcsPlayingBlack:
8687 machineWhite = FALSE;
8690 case TwoMachinesPlay:
8691 machineWhite = (cps->twoMachinesColor[0] == 'w');
8694 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8695 if (appData.debugMode) {
8697 "Ignoring move out of turn by %s, gameMode %d"
8698 ", forwardMost %d\n",
8699 cps->which, gameMode, forwardMostMove);
8705 if(cps->alphaRank) AlphaRank(machineMove, 4);
8707 // [HGM] lion: (some very limited) support for Alien protocol
8708 killX = killY = kill2X = kill2Y = -1;
8709 if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8710 safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8713 if(p = strchr(machineMove, ',')) { // we got both legs in one (happens on book move)
8714 safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8715 safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8717 if(firstLeg[0]) { // there was a previous leg;
8718 // only support case where same piece makes two step
8719 char buf[20], *p = machineMove+1, *q = buf+1, f;
8720 safeStrCpy(buf, machineMove, 20);
8721 while(isdigit(*q)) q++; // find start of to-square
8722 safeStrCpy(machineMove, firstLeg, 20);
8723 while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8724 if(*p == *buf) // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8725 safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8726 sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8727 firstLeg[0] = NULLCHAR;
8730 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8731 &fromX, &fromY, &toX, &toY, &promoChar)) {
8732 /* Machine move could not be parsed; ignore it. */
8733 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8734 machineMove, _(cps->which));
8735 DisplayMoveError(buf1);
8736 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8737 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8738 if (gameMode == TwoMachinesPlay) {
8739 GameEnds(machineWhite ? BlackWins : WhiteWins,
8745 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8746 /* So we have to redo legality test with true e.p. status here, */
8747 /* to make sure an illegal e.p. capture does not slip through, */
8748 /* to cause a forfeit on a justified illegal-move complaint */
8749 /* of the opponent. */
8750 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8752 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8753 fromY, fromX, toY, toX, promoChar);
8754 if(moveType == IllegalMove) {
8755 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8756 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8757 GameEnds(machineWhite ? BlackWins : WhiteWins,
8760 } else if(!appData.fischerCastling)
8761 /* [HGM] Kludge to handle engines that send FRC-style castling
8762 when they shouldn't (like TSCP-Gothic) */
8764 case WhiteASideCastleFR:
8765 case BlackASideCastleFR:
8767 currentMoveString[2]++;
8769 case WhiteHSideCastleFR:
8770 case BlackHSideCastleFR:
8772 currentMoveString[2]--;
8774 default: ; // nothing to do, but suppresses warning of pedantic compilers
8777 hintRequested = FALSE;
8778 lastHint[0] = NULLCHAR;
8779 bookRequested = FALSE;
8780 /* Program may be pondering now */
8781 cps->maybeThinking = TRUE;
8782 if (cps->sendTime == 2) cps->sendTime = 1;
8783 if (cps->offeredDraw) cps->offeredDraw--;
8785 /* [AS] Save move info*/
8786 pvInfoList[ forwardMostMove ].score = programStats.score;
8787 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8788 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8790 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8792 /* Test suites abort the 'game' after one move */
8793 if(*appData.finger) {
8795 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8796 if(!f) f = fopen(appData.finger, "w");
8797 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8798 else { DisplayFatalError("Bad output file", errno, 0); return; }
8800 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8803 if(solvingTime >= 0) {
8804 snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8805 totalTime += solvingTime; first.matchWins++;
8807 snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8810 OutputKibitz(2, buf1);
8811 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8814 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8815 if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8818 while( count < adjudicateLossPlies ) {
8819 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8822 score = -score; /* Flip score for winning side */
8825 if( score > appData.adjudicateLossThreshold ) {
8832 if( count >= adjudicateLossPlies ) {
8833 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8835 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8836 "Xboard adjudication",
8843 if(Adjudicate(cps)) {
8844 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8845 return; // [HGM] adjudicate: for all automatic game ends
8849 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8851 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8852 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8854 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8856 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8858 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8859 char buf[3*MSG_SIZ];
8861 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8862 programStats.score / 100.,
8864 programStats.time / 100.,
8865 (unsigned int)programStats.nodes,
8866 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8867 programStats.movelist);
8873 /* [AS] Clear stats for next move */
8874 ClearProgramStats();
8875 thinkOutput[0] = NULLCHAR;
8876 hiddenThinkOutputState = 0;
8879 if (gameMode == TwoMachinesPlay) {
8880 /* [HGM] relaying draw offers moved to after reception of move */
8881 /* and interpreting offer as claim if it brings draw condition */
8882 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8883 SendToProgram("draw\n", cps->other);
8885 if (cps->other->sendTime) {
8886 SendTimeRemaining(cps->other,
8887 cps->other->twoMachinesColor[0] == 'w');
8889 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8890 if (firstMove && !bookHit) {
8892 if (cps->other->useColors) {
8893 SendToProgram(cps->other->twoMachinesColor, cps->other);
8895 SendToProgram("go\n", cps->other);
8897 cps->other->maybeThinking = TRUE;
8900 roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8902 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8904 if (!pausing && appData.ringBellAfterMoves) {
8905 if(!roar) RingBell();
8909 * Reenable menu items that were disabled while
8910 * machine was thinking
8912 if (gameMode != TwoMachinesPlay)
8913 SetUserThinkingEnables();
8915 // [HGM] book: after book hit opponent has received move and is now in force mode
8916 // force the book reply into it, and then fake that it outputted this move by jumping
8917 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8919 static char bookMove[MSG_SIZ]; // a bit generous?
8921 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8922 strcat(bookMove, bookHit);
8925 programStats.nodes = programStats.depth = programStats.time =
8926 programStats.score = programStats.got_only_move = 0;
8927 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8929 if(cps->lastPing != cps->lastPong) {
8930 savedMessage = message; // args for deferred call
8932 ScheduleDelayedEvent(DeferredBookMove, 10);
8941 /* Set special modes for chess engines. Later something general
8942 * could be added here; for now there is just one kludge feature,
8943 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8944 * when "xboard" is given as an interactive command.
8946 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8947 cps->useSigint = FALSE;
8948 cps->useSigterm = FALSE;
8950 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8951 ParseFeatures(message+8, cps);
8952 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8955 if (!strncmp(message, "setup ", 6) &&
8956 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8957 NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8958 ) { // [HGM] allow first engine to define opening position
8959 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8960 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8962 if(sscanf(message, "setup (%s", buf) == 1) {
8963 s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
8964 ASSIGN(appData.pieceToCharTable, buf);
8966 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8968 while(message[s] && message[s++] != ' ');
8969 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8970 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8971 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8972 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
8973 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8974 if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
8975 startedFromSetupPosition = FALSE;
8978 if(startedFromSetupPosition) return;
8979 ParseFEN(boards[0], &dummy, message+s, FALSE);
8980 DrawPosition(TRUE, boards[0]);
8981 CopyBoard(initialPosition, boards[0]);
8982 startedFromSetupPosition = TRUE;
8985 if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
8986 ChessSquare piece = WhitePawn;
8987 char *p=message+6, *q, *s = SUFFIXES, ID = *p;
8988 if(*p == '+') piece = CHUPROMOTED WhitePawn, ID = *++p;
8989 if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
8990 piece += CharToPiece(ID & 255) - WhitePawn;
8991 if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
8992 /* always accept definition of */ && piece != WhiteFalcon && piece != BlackFalcon
8993 /* wild-card pieces. */ && piece != WhiteCobra && piece != BlackCobra
8994 /* For variants we don't have */ && gameInfo.variant != VariantBerolina
8995 /* correct rules for, we cannot */ && gameInfo.variant != VariantCylinder
8996 /* enforce legality on our own! */ && gameInfo.variant != VariantUnknown
8997 && gameInfo.variant != VariantGreat
8998 && gameInfo.variant != VariantFairy ) return;
8999 if(piece < EmptySquare) {
9001 ASSIGN(pieceDesc[piece], buf1);
9002 if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9006 /* [HGM] Allow engine to set up a position. Don't ask me why one would
9007 * want this, I was asked to put it in, and obliged.
9009 if (!strncmp(message, "setboard ", 9)) {
9010 Board initial_position;
9012 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9014 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9015 DisplayError(_("Bad FEN received from engine"), 0);
9019 CopyBoard(boards[0], initial_position);
9020 initialRulePlies = FENrulePlies;
9021 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9022 else gameMode = MachinePlaysBlack;
9023 DrawPosition(FALSE, boards[currentMove]);
9029 * Look for communication commands
9031 if (!strncmp(message, "telluser ", 9)) {
9032 if(message[9] == '\\' && message[10] == '\\')
9033 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9035 DisplayNote(message + 9);
9038 if (!strncmp(message, "tellusererror ", 14)) {
9040 if(message[14] == '\\' && message[15] == '\\')
9041 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9043 DisplayError(message + 14, 0);
9046 if (!strncmp(message, "tellopponent ", 13)) {
9047 if (appData.icsActive) {
9049 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9053 DisplayNote(message + 13);
9057 if (!strncmp(message, "tellothers ", 11)) {
9058 if (appData.icsActive) {
9060 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9063 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9066 if (!strncmp(message, "tellall ", 8)) {
9067 if (appData.icsActive) {
9069 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9073 DisplayNote(message + 8);
9077 if (strncmp(message, "warning", 7) == 0) {
9078 /* Undocumented feature, use tellusererror in new code */
9079 DisplayError(message, 0);
9082 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9083 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9084 strcat(realname, " query");
9085 AskQuestion(realname, buf2, buf1, cps->pr);
9088 /* Commands from the engine directly to ICS. We don't allow these to be
9089 * sent until we are logged on. Crafty kibitzes have been known to
9090 * interfere with the login process.
9093 if (!strncmp(message, "tellics ", 8)) {
9094 SendToICS(message + 8);
9098 if (!strncmp(message, "tellicsnoalias ", 15)) {
9099 SendToICS(ics_prefix);
9100 SendToICS(message + 15);
9104 /* The following are for backward compatibility only */
9105 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9106 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9107 SendToICS(ics_prefix);
9113 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9114 if(initPing == cps->lastPong) {
9115 if(gameInfo.variant == VariantUnknown) {
9116 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9117 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9118 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9122 if(cps->lastPing == cps->lastPong && abortEngineThink) {
9123 abortEngineThink = FALSE;
9124 DisplayMessage("", "");
9129 if(!strncmp(message, "highlight ", 10)) {
9130 if(appData.testLegality && !*engineVariant && appData.markers) return;
9131 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9134 if(!strncmp(message, "click ", 6)) {
9135 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9136 if(appData.testLegality || !appData.oneClick) return;
9137 sscanf(message+6, "%c%d%c", &f, &y, &c);
9138 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9139 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9140 x = x*squareSize + (x+1)*lineGap + squareSize/2;
9141 y = y*squareSize + (y+1)*lineGap + squareSize/2;
9142 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9143 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9144 LeftClick(Release, lastLeftX, lastLeftY);
9145 controlKey = (c == ',');
9146 LeftClick(Press, x, y);
9147 LeftClick(Release, x, y);
9148 first.highlight = f;
9152 * If the move is illegal, cancel it and redraw the board.
9153 * Also deal with other error cases. Matching is rather loose
9154 * here to accommodate engines written before the spec.
9156 if (strncmp(message + 1, "llegal move", 11) == 0 ||
9157 strncmp(message, "Error", 5) == 0) {
9158 if (StrStr(message, "name") ||
9159 StrStr(message, "rating") || StrStr(message, "?") ||
9160 StrStr(message, "result") || StrStr(message, "board") ||
9161 StrStr(message, "bk") || StrStr(message, "computer") ||
9162 StrStr(message, "variant") || StrStr(message, "hint") ||
9163 StrStr(message, "random") || StrStr(message, "depth") ||
9164 StrStr(message, "accepted")) {
9167 if (StrStr(message, "protover")) {
9168 /* Program is responding to input, so it's apparently done
9169 initializing, and this error message indicates it is
9170 protocol version 1. So we don't need to wait any longer
9171 for it to initialize and send feature commands. */
9172 FeatureDone(cps, 1);
9173 cps->protocolVersion = 1;
9176 cps->maybeThinking = FALSE;
9178 if (StrStr(message, "draw")) {
9179 /* Program doesn't have "draw" command */
9180 cps->sendDrawOffers = 0;
9183 if (cps->sendTime != 1 &&
9184 (StrStr(message, "time") || StrStr(message, "otim"))) {
9185 /* Program apparently doesn't have "time" or "otim" command */
9189 if (StrStr(message, "analyze")) {
9190 cps->analysisSupport = FALSE;
9191 cps->analyzing = FALSE;
9192 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9193 EditGameEvent(); // [HGM] try to preserve loaded game
9194 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9195 DisplayError(buf2, 0);
9198 if (StrStr(message, "(no matching move)st")) {
9199 /* Special kludge for GNU Chess 4 only */
9200 cps->stKludge = TRUE;
9201 SendTimeControl(cps, movesPerSession, timeControl,
9202 timeIncrement, appData.searchDepth,
9206 if (StrStr(message, "(no matching move)sd")) {
9207 /* Special kludge for GNU Chess 4 only */
9208 cps->sdKludge = TRUE;
9209 SendTimeControl(cps, movesPerSession, timeControl,
9210 timeIncrement, appData.searchDepth,
9214 if (!StrStr(message, "llegal")) {
9217 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9218 gameMode == IcsIdle) return;
9219 if (forwardMostMove <= backwardMostMove) return;
9220 if (pausing) PauseEvent();
9221 if(appData.forceIllegal) {
9222 // [HGM] illegal: machine refused move; force position after move into it
9223 SendToProgram("force\n", cps);
9224 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9225 // we have a real problem now, as SendBoard will use the a2a3 kludge
9226 // when black is to move, while there might be nothing on a2 or black
9227 // might already have the move. So send the board as if white has the move.
9228 // But first we must change the stm of the engine, as it refused the last move
9229 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9230 if(WhiteOnMove(forwardMostMove)) {
9231 SendToProgram("a7a6\n", cps); // for the engine black still had the move
9232 SendBoard(cps, forwardMostMove); // kludgeless board
9234 SendToProgram("a2a3\n", cps); // for the engine white still had the move
9235 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9236 SendBoard(cps, forwardMostMove+1); // kludgeless board
9238 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9239 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9240 gameMode == TwoMachinesPlay)
9241 SendToProgram("go\n", cps);
9244 if (gameMode == PlayFromGameFile) {
9245 /* Stop reading this game file */
9246 gameMode = EditGame;
9249 /* [HGM] illegal-move claim should forfeit game when Xboard */
9250 /* only passes fully legal moves */
9251 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9252 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9253 "False illegal-move claim", GE_XBOARD );
9254 return; // do not take back move we tested as valid
9256 currentMove = forwardMostMove-1;
9257 DisplayMove(currentMove-1); /* before DisplayMoveError */
9258 SwitchClocks(forwardMostMove-1); // [HGM] race
9259 DisplayBothClocks();
9260 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9261 parseList[currentMove], _(cps->which));
9262 DisplayMoveError(buf1);
9263 DrawPosition(FALSE, boards[currentMove]);
9265 SetUserThinkingEnables();
9268 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9269 /* Program has a broken "time" command that
9270 outputs a string not ending in newline.
9274 if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9275 if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9276 sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1 ) return;
9280 * If chess program startup fails, exit with an error message.
9281 * Attempts to recover here are futile. [HGM] Well, we try anyway
9283 if ((StrStr(message, "unknown host") != NULL)
9284 || (StrStr(message, "No remote directory") != NULL)
9285 || (StrStr(message, "not found") != NULL)
9286 || (StrStr(message, "No such file") != NULL)
9287 || (StrStr(message, "can't alloc") != NULL)
9288 || (StrStr(message, "Permission denied") != NULL)) {
9290 cps->maybeThinking = FALSE;
9291 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9292 _(cps->which), cps->program, cps->host, message);
9293 RemoveInputSource(cps->isr);
9294 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9295 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9296 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9302 * Look for hint output
9304 if (sscanf(message, "Hint: %s", buf1) == 1) {
9305 if (cps == &first && hintRequested) {
9306 hintRequested = FALSE;
9307 if (ParseOneMove(buf1, forwardMostMove, &moveType,
9308 &fromX, &fromY, &toX, &toY, &promoChar)) {
9309 (void) CoordsToAlgebraic(boards[forwardMostMove],
9310 PosFlags(forwardMostMove),
9311 fromY, fromX, toY, toX, promoChar, buf1);
9312 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9313 DisplayInformation(buf2);
9315 /* Hint move could not be parsed!? */
9316 snprintf(buf2, sizeof(buf2),
9317 _("Illegal hint move \"%s\"\nfrom %s chess program"),
9318 buf1, _(cps->which));
9319 DisplayError(buf2, 0);
9322 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9328 * Ignore other messages if game is not in progress
9330 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9331 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9334 * look for win, lose, draw, or draw offer
9336 if (strncmp(message, "1-0", 3) == 0) {
9337 char *p, *q, *r = "";
9338 p = strchr(message, '{');
9346 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9348 } else if (strncmp(message, "0-1", 3) == 0) {
9349 char *p, *q, *r = "";
9350 p = strchr(message, '{');
9358 /* Kludge for Arasan 4.1 bug */
9359 if (strcmp(r, "Black resigns") == 0) {
9360 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9363 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9365 } else if (strncmp(message, "1/2", 3) == 0) {
9366 char *p, *q, *r = "";
9367 p = strchr(message, '{');
9376 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9379 } else if (strncmp(message, "White resign", 12) == 0) {
9380 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9382 } else if (strncmp(message, "Black resign", 12) == 0) {
9383 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9385 } else if (strncmp(message, "White matches", 13) == 0 ||
9386 strncmp(message, "Black matches", 13) == 0 ) {
9387 /* [HGM] ignore GNUShogi noises */
9389 } else if (strncmp(message, "White", 5) == 0 &&
9390 message[5] != '(' &&
9391 StrStr(message, "Black") == NULL) {
9392 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9394 } else if (strncmp(message, "Black", 5) == 0 &&
9395 message[5] != '(') {
9396 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9398 } else if (strcmp(message, "resign") == 0 ||
9399 strcmp(message, "computer resigns") == 0) {
9401 case MachinePlaysBlack:
9402 case IcsPlayingBlack:
9403 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9405 case MachinePlaysWhite:
9406 case IcsPlayingWhite:
9407 GameEnds(BlackWins, "White resigns", GE_ENGINE);
9409 case TwoMachinesPlay:
9410 if (cps->twoMachinesColor[0] == 'w')
9411 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9413 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9420 } else if (strncmp(message, "opponent mates", 14) == 0) {
9422 case MachinePlaysBlack:
9423 case IcsPlayingBlack:
9424 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9426 case MachinePlaysWhite:
9427 case IcsPlayingWhite:
9428 GameEnds(BlackWins, "Black mates", GE_ENGINE);
9430 case TwoMachinesPlay:
9431 if (cps->twoMachinesColor[0] == 'w')
9432 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9434 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9441 } else if (strncmp(message, "computer mates", 14) == 0) {
9443 case MachinePlaysBlack:
9444 case IcsPlayingBlack:
9445 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9447 case MachinePlaysWhite:
9448 case IcsPlayingWhite:
9449 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9451 case TwoMachinesPlay:
9452 if (cps->twoMachinesColor[0] == 'w')
9453 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9455 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9462 } else if (strncmp(message, "checkmate", 9) == 0) {
9463 if (WhiteOnMove(forwardMostMove)) {
9464 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9466 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9469 } else if (strstr(message, "Draw") != NULL ||
9470 strstr(message, "game is a draw") != NULL) {
9471 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9473 } else if (strstr(message, "offer") != NULL &&
9474 strstr(message, "draw") != NULL) {
9476 if (appData.zippyPlay && first.initDone) {
9477 /* Relay offer to ICS */
9478 SendToICS(ics_prefix);
9479 SendToICS("draw\n");
9482 cps->offeredDraw = 2; /* valid until this engine moves twice */
9483 if (gameMode == TwoMachinesPlay) {
9484 if (cps->other->offeredDraw) {
9485 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9486 /* [HGM] in two-machine mode we delay relaying draw offer */
9487 /* until after we also have move, to see if it is really claim */
9489 } else if (gameMode == MachinePlaysWhite ||
9490 gameMode == MachinePlaysBlack) {
9491 if (userOfferedDraw) {
9492 DisplayInformation(_("Machine accepts your draw offer"));
9493 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9495 DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9502 * Look for thinking output
9504 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9505 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9507 int plylev, mvleft, mvtot, curscore, time;
9508 char mvname[MOVE_LEN];
9512 int prefixHint = FALSE;
9513 mvname[0] = NULLCHAR;
9516 case MachinePlaysBlack:
9517 case IcsPlayingBlack:
9518 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9520 case MachinePlaysWhite:
9521 case IcsPlayingWhite:
9522 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9527 case IcsObserving: /* [DM] icsEngineAnalyze */
9528 if (!appData.icsEngineAnalyze) ignore = TRUE;
9530 case TwoMachinesPlay:
9531 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9541 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9543 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9544 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9545 char score_buf[MSG_SIZ];
9547 if(nodes>>32 == u64Const(0xFFFFFFFF)) // [HGM] negative node count read
9548 nodes += u64Const(0x100000000);
9550 if (plyext != ' ' && plyext != '\t') {
9554 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9555 if( cps->scoreIsAbsolute &&
9556 ( gameMode == MachinePlaysBlack ||
9557 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9558 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9559 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9560 !WhiteOnMove(currentMove)
9563 curscore = -curscore;
9566 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9568 if(*bestMove) { // rememer time best EPD move was first found
9569 int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9571 int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9572 ok &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9573 solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9576 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9579 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9580 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9581 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9582 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9583 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9584 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9588 /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9589 DisplayError(_("failed writing PV"), 0);
9592 tempStats.depth = plylev;
9593 tempStats.nodes = nodes;
9594 tempStats.time = time;
9595 tempStats.score = curscore;
9596 tempStats.got_only_move = 0;
9598 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9601 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9602 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9603 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9604 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9605 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9606 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9607 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9608 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9611 /* Buffer overflow protection */
9612 if (pv[0] != NULLCHAR) {
9613 if (strlen(pv) >= sizeof(tempStats.movelist)
9614 && appData.debugMode) {
9616 "PV is too long; using the first %u bytes.\n",
9617 (unsigned) sizeof(tempStats.movelist) - 1);
9620 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9622 sprintf(tempStats.movelist, " no PV\n");
9625 if (tempStats.seen_stat) {
9626 tempStats.ok_to_send = 1;
9629 if (strchr(tempStats.movelist, '(') != NULL) {
9630 tempStats.line_is_book = 1;
9631 tempStats.nr_moves = 0;
9632 tempStats.moves_left = 0;
9634 tempStats.line_is_book = 0;
9637 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9638 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9640 SendProgramStatsToFrontend( cps, &tempStats );
9643 [AS] Protect the thinkOutput buffer from overflow... this
9644 is only useful if buf1 hasn't overflowed first!
9646 if(curscore >= MATE_SCORE)
9647 snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9648 else if(curscore <= -MATE_SCORE)
9649 snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9651 snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9652 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9654 (gameMode == TwoMachinesPlay ?
9655 ToUpper(cps->twoMachinesColor[0]) : ' '),
9657 prefixHint ? lastHint : "",
9658 prefixHint ? " " : "" );
9660 if( buf1[0] != NULLCHAR ) {
9661 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9663 if( strlen(pv) > max_len ) {
9664 if( appData.debugMode) {
9665 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9667 pv[max_len+1] = '\0';
9670 strcat( thinkOutput, pv);
9673 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9674 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9675 DisplayMove(currentMove - 1);
9679 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9680 /* crafty (9.25+) says "(only move) <move>"
9681 * if there is only 1 legal move
9683 sscanf(p, "(only move) %s", buf1);
9684 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9685 sprintf(programStats.movelist, "%s (only move)", buf1);
9686 programStats.depth = 1;
9687 programStats.nr_moves = 1;
9688 programStats.moves_left = 1;
9689 programStats.nodes = 1;
9690 programStats.time = 1;
9691 programStats.got_only_move = 1;
9693 /* Not really, but we also use this member to
9694 mean "line isn't going to change" (Crafty
9695 isn't searching, so stats won't change) */
9696 programStats.line_is_book = 1;
9698 SendProgramStatsToFrontend( cps, &programStats );
9700 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9701 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9702 DisplayMove(currentMove - 1);
9705 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9706 &time, &nodes, &plylev, &mvleft,
9707 &mvtot, mvname) >= 5) {
9708 /* The stat01: line is from Crafty (9.29+) in response
9709 to the "." command */
9710 programStats.seen_stat = 1;
9711 cps->maybeThinking = TRUE;
9713 if (programStats.got_only_move || !appData.periodicUpdates)
9716 programStats.depth = plylev;
9717 programStats.time = time;
9718 programStats.nodes = nodes;
9719 programStats.moves_left = mvleft;
9720 programStats.nr_moves = mvtot;
9721 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9722 programStats.ok_to_send = 1;
9723 programStats.movelist[0] = '\0';
9725 SendProgramStatsToFrontend( cps, &programStats );
9729 } else if (strncmp(message,"++",2) == 0) {
9730 /* Crafty 9.29+ outputs this */
9731 programStats.got_fail = 2;
9734 } else if (strncmp(message,"--",2) == 0) {
9735 /* Crafty 9.29+ outputs this */
9736 programStats.got_fail = 1;
9739 } else if (thinkOutput[0] != NULLCHAR &&
9740 strncmp(message, " ", 4) == 0) {
9741 unsigned message_len;
9744 while (*p && *p == ' ') p++;
9746 message_len = strlen( p );
9748 /* [AS] Avoid buffer overflow */
9749 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9750 strcat(thinkOutput, " ");
9751 strcat(thinkOutput, p);
9754 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9755 strcat(programStats.movelist, " ");
9756 strcat(programStats.movelist, p);
9759 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9760 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9761 DisplayMove(currentMove - 1);
9769 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9770 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9772 ChessProgramStats cpstats;
9774 if (plyext != ' ' && plyext != '\t') {
9778 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9779 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9780 curscore = -curscore;
9783 cpstats.depth = plylev;
9784 cpstats.nodes = nodes;
9785 cpstats.time = time;
9786 cpstats.score = curscore;
9787 cpstats.got_only_move = 0;
9788 cpstats.movelist[0] = '\0';
9790 if (buf1[0] != NULLCHAR) {
9791 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9794 cpstats.ok_to_send = 0;
9795 cpstats.line_is_book = 0;
9796 cpstats.nr_moves = 0;
9797 cpstats.moves_left = 0;
9799 SendProgramStatsToFrontend( cps, &cpstats );
9806 /* Parse a game score from the character string "game", and
9807 record it as the history of the current game. The game
9808 score is NOT assumed to start from the standard position.
9809 The display is not updated in any way.
9812 ParseGameHistory (char *game)
9815 int fromX, fromY, toX, toY, boardIndex;
9820 if (appData.debugMode)
9821 fprintf(debugFP, "Parsing game history: %s\n", game);
9823 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9824 gameInfo.site = StrSave(appData.icsHost);
9825 gameInfo.date = PGNDate();
9826 gameInfo.round = StrSave("-");
9828 /* Parse out names of players */
9829 while (*game == ' ') game++;
9831 while (*game != ' ') *p++ = *game++;
9833 gameInfo.white = StrSave(buf);
9834 while (*game == ' ') game++;
9836 while (*game != ' ' && *game != '\n') *p++ = *game++;
9838 gameInfo.black = StrSave(buf);
9841 boardIndex = blackPlaysFirst ? 1 : 0;
9844 yyboardindex = boardIndex;
9845 moveType = (ChessMove) Myylex();
9847 case IllegalMove: /* maybe suicide chess, etc. */
9848 if (appData.debugMode) {
9849 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9850 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9851 setbuf(debugFP, NULL);
9853 case WhitePromotion:
9854 case BlackPromotion:
9855 case WhiteNonPromotion:
9856 case BlackNonPromotion:
9859 case WhiteCapturesEnPassant:
9860 case BlackCapturesEnPassant:
9861 case WhiteKingSideCastle:
9862 case WhiteQueenSideCastle:
9863 case BlackKingSideCastle:
9864 case BlackQueenSideCastle:
9865 case WhiteKingSideCastleWild:
9866 case WhiteQueenSideCastleWild:
9867 case BlackKingSideCastleWild:
9868 case BlackQueenSideCastleWild:
9870 case WhiteHSideCastleFR:
9871 case WhiteASideCastleFR:
9872 case BlackHSideCastleFR:
9873 case BlackASideCastleFR:
9875 fromX = currentMoveString[0] - AAA;
9876 fromY = currentMoveString[1] - ONE;
9877 toX = currentMoveString[2] - AAA;
9878 toY = currentMoveString[3] - ONE;
9879 promoChar = currentMoveString[4];
9883 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9884 fromX = moveType == WhiteDrop ?
9885 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9886 (int) CharToPiece(ToLower(currentMoveString[0]));
9888 toX = currentMoveString[2] - AAA;
9889 toY = currentMoveString[3] - ONE;
9890 promoChar = NULLCHAR;
9894 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9895 if (appData.debugMode) {
9896 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9897 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9898 setbuf(debugFP, NULL);
9900 DisplayError(buf, 0);
9902 case ImpossibleMove:
9904 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9905 if (appData.debugMode) {
9906 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9907 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9908 setbuf(debugFP, NULL);
9910 DisplayError(buf, 0);
9913 if (boardIndex < backwardMostMove) {
9914 /* Oops, gap. How did that happen? */
9915 DisplayError(_("Gap in move list"), 0);
9918 backwardMostMove = blackPlaysFirst ? 1 : 0;
9919 if (boardIndex > forwardMostMove) {
9920 forwardMostMove = boardIndex;
9924 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9925 strcat(parseList[boardIndex-1], " ");
9926 strcat(parseList[boardIndex-1], yy_text);
9938 case GameUnfinished:
9939 if (gameMode == IcsExamining) {
9940 if (boardIndex < backwardMostMove) {
9941 /* Oops, gap. How did that happen? */
9944 backwardMostMove = blackPlaysFirst ? 1 : 0;
9947 gameInfo.result = moveType;
9948 p = strchr(yy_text, '{');
9949 if (p == NULL) p = strchr(yy_text, '(');
9952 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9954 q = strchr(p, *p == '{' ? '}' : ')');
9955 if (q != NULL) *q = NULLCHAR;
9958 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9959 gameInfo.resultDetails = StrSave(p);
9962 if (boardIndex >= forwardMostMove &&
9963 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9964 backwardMostMove = blackPlaysFirst ? 1 : 0;
9967 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9968 fromY, fromX, toY, toX, promoChar,
9969 parseList[boardIndex]);
9970 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9971 /* currentMoveString is set as a side-effect of yylex */
9972 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9973 strcat(moveList[boardIndex], "\n");
9975 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9976 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9982 if(!IS_SHOGI(gameInfo.variant))
9983 strcat(parseList[boardIndex - 1], "+");
9987 strcat(parseList[boardIndex - 1], "#");
9994 /* Apply a move to the given board */
9996 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9998 ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
9999 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10001 /* [HGM] compute & store e.p. status and castling rights for new position */
10002 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10004 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10005 oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10006 board[EP_STATUS] = EP_NONE;
10007 board[EP_FILE] = board[EP_RANK] = 100;
10009 if (fromY == DROP_RANK) {
10010 /* must be first */
10011 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10012 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10015 piece = board[toY][toX] = (ChessSquare) fromX;
10017 // ChessSquare victim;
10020 if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10021 // victim = board[killY][killX],
10022 killed = board[killY][killX],
10023 board[killY][killX] = EmptySquare,
10024 board[EP_STATUS] = EP_CAPTURE;
10025 if( kill2X >= 0 && kill2Y >= 0)
10026 killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10029 if( board[toY][toX] != EmptySquare ) {
10030 board[EP_STATUS] = EP_CAPTURE;
10031 if( (fromX != toX || fromY != toY) && // not igui!
10032 (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10033 captured == BlackLion && board[fromY][fromX] != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules
10034 board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10038 pawn = board[fromY][fromX];
10039 if( pawn == WhiteLance || pawn == BlackLance ) {
10040 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10041 if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10042 else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10045 if( pawn == WhitePawn ) {
10046 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10047 board[EP_STATUS] = EP_PAWN_MOVE;
10048 if( toY-fromY>=2) {
10049 board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10050 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
10051 gameInfo.variant != VariantBerolina || toX < fromX)
10052 board[EP_STATUS] = toX | berolina;
10053 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10054 gameInfo.variant != VariantBerolina || toX > fromX)
10055 board[EP_STATUS] = toX;
10058 if( pawn == BlackPawn ) {
10059 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10060 board[EP_STATUS] = EP_PAWN_MOVE;
10061 if( toY-fromY<= -2) {
10062 board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10063 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
10064 gameInfo.variant != VariantBerolina || toX < fromX)
10065 board[EP_STATUS] = toX | berolina;
10066 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10067 gameInfo.variant != VariantBerolina || toX > fromX)
10068 board[EP_STATUS] = toX;
10072 if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10073 if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10074 if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10075 if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10077 for(i=0; i<nrCastlingRights; i++) {
10078 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10079 board[CASTLING][i] == toX && castlingRank[i] == toY
10080 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10083 if(gameInfo.variant == VariantSChess) { // update virginity
10084 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10085 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10086 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
10087 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
10090 if (fromX == toX && fromY == toY) return;
10092 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10093 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10094 if(gameInfo.variant == VariantKnightmate)
10095 king += (int) WhiteUnicorn - (int) WhiteKing;
10097 if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10098 && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) { // and tramples own
10099 board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10100 board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10101 board[EP_STATUS] = EP_NONE; // capture was fake!
10103 /* Code added by Tord: */
10104 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10105 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10106 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10107 board[EP_STATUS] = EP_NONE; // capture was fake!
10108 board[fromY][fromX] = EmptySquare;
10109 board[toY][toX] = EmptySquare;
10110 if((toX > fromX) != (piece == WhiteRook)) {
10111 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10113 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10115 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10116 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10117 board[EP_STATUS] = EP_NONE;
10118 board[fromY][fromX] = EmptySquare;
10119 board[toY][toX] = EmptySquare;
10120 if((toX > fromX) != (piece == BlackRook)) {
10121 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10123 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10125 /* End of code added by Tord */
10127 } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10128 board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10129 board[toY][toX] = piece;
10130 } else if (board[fromY][fromX] == king
10131 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10132 && toY == fromY && toX > fromX+1) {
10133 for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10134 board[fromY][toX-1] = board[fromY][rookX];
10135 board[fromY][rookX] = EmptySquare;
10136 board[fromY][fromX] = EmptySquare;
10137 board[toY][toX] = king;
10138 } else if (board[fromY][fromX] == king
10139 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10140 && toY == fromY && toX < fromX-1) {
10141 for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10142 board[fromY][toX+1] = board[fromY][rookX];
10143 board[fromY][rookX] = EmptySquare;
10144 board[fromY][fromX] = EmptySquare;
10145 board[toY][toX] = king;
10146 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10147 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10148 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10150 /* white pawn promotion */
10151 board[toY][toX] = CharToPiece(ToUpper(promoChar));
10152 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10153 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10154 board[fromY][fromX] = EmptySquare;
10155 } else if ((fromY >= BOARD_HEIGHT>>1)
10156 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10158 && gameInfo.variant != VariantXiangqi
10159 && gameInfo.variant != VariantBerolina
10160 && (pawn == WhitePawn)
10161 && (board[toY][toX] == EmptySquare)) {
10162 board[fromY][fromX] = EmptySquare;
10163 board[toY][toX] = piece;
10164 if(toY == epRank - 128 + 1)
10165 captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10167 captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10168 } else if ((fromY == BOARD_HEIGHT-4)
10170 && gameInfo.variant == VariantBerolina
10171 && (board[fromY][fromX] == WhitePawn)
10172 && (board[toY][toX] == EmptySquare)) {
10173 board[fromY][fromX] = EmptySquare;
10174 board[toY][toX] = WhitePawn;
10175 if(oldEP & EP_BEROLIN_A) {
10176 captured = board[fromY][fromX-1];
10177 board[fromY][fromX-1] = EmptySquare;
10178 }else{ captured = board[fromY][fromX+1];
10179 board[fromY][fromX+1] = EmptySquare;
10181 } else if (board[fromY][fromX] == king
10182 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10183 && toY == fromY && toX > fromX+1) {
10184 for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10185 board[fromY][toX-1] = board[fromY][rookX];
10186 board[fromY][rookX] = EmptySquare;
10187 board[fromY][fromX] = EmptySquare;
10188 board[toY][toX] = king;
10189 } else if (board[fromY][fromX] == king
10190 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10191 && toY == fromY && toX < fromX-1) {
10192 for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10193 board[fromY][toX+1] = board[fromY][rookX];
10194 board[fromY][rookX] = EmptySquare;
10195 board[fromY][fromX] = EmptySquare;
10196 board[toY][toX] = king;
10197 } else if (fromY == 7 && fromX == 3
10198 && board[fromY][fromX] == BlackKing
10199 && toY == 7 && toX == 5) {
10200 board[fromY][fromX] = EmptySquare;
10201 board[toY][toX] = BlackKing;
10202 board[fromY][7] = EmptySquare;
10203 board[toY][4] = BlackRook;
10204 } else if (fromY == 7 && fromX == 3
10205 && board[fromY][fromX] == BlackKing
10206 && toY == 7 && toX == 1) {
10207 board[fromY][fromX] = EmptySquare;
10208 board[toY][toX] = BlackKing;
10209 board[fromY][0] = EmptySquare;
10210 board[toY][2] = BlackRook;
10211 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10212 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10213 && toY < promoRank && promoChar
10215 /* black pawn promotion */
10216 board[toY][toX] = CharToPiece(ToLower(promoChar));
10217 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10218 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10219 board[fromY][fromX] = EmptySquare;
10220 } else if ((fromY < BOARD_HEIGHT>>1)
10221 && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10223 && gameInfo.variant != VariantXiangqi
10224 && gameInfo.variant != VariantBerolina
10225 && (pawn == BlackPawn)
10226 && (board[toY][toX] == EmptySquare)) {
10227 board[fromY][fromX] = EmptySquare;
10228 board[toY][toX] = piece;
10229 if(toY == epRank - 128 - 1)
10230 captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10232 captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10233 } else if ((fromY == 3)
10235 && gameInfo.variant == VariantBerolina
10236 && (board[fromY][fromX] == BlackPawn)
10237 && (board[toY][toX] == EmptySquare)) {
10238 board[fromY][fromX] = EmptySquare;
10239 board[toY][toX] = BlackPawn;
10240 if(oldEP & EP_BEROLIN_A) {
10241 captured = board[fromY][fromX-1];
10242 board[fromY][fromX-1] = EmptySquare;
10243 }else{ captured = board[fromY][fromX+1];
10244 board[fromY][fromX+1] = EmptySquare;
10247 ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10248 board[fromY][fromX] = EmptySquare;
10249 board[toY][toX] = piece;
10253 if (gameInfo.holdingsWidth != 0) {
10255 /* !!A lot more code needs to be written to support holdings */
10256 /* [HGM] OK, so I have written it. Holdings are stored in the */
10257 /* penultimate board files, so they are automaticlly stored */
10258 /* in the game history. */
10259 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10260 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10261 /* Delete from holdings, by decreasing count */
10262 /* and erasing image if necessary */
10263 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10264 if(p < (int) BlackPawn) { /* white drop */
10265 p -= (int)WhitePawn;
10266 p = PieceToNumber((ChessSquare)p);
10267 if(p >= gameInfo.holdingsSize) p = 0;
10268 if(--board[p][BOARD_WIDTH-2] <= 0)
10269 board[p][BOARD_WIDTH-1] = EmptySquare;
10270 if((int)board[p][BOARD_WIDTH-2] < 0)
10271 board[p][BOARD_WIDTH-2] = 0;
10272 } else { /* black drop */
10273 p -= (int)BlackPawn;
10274 p = PieceToNumber((ChessSquare)p);
10275 if(p >= gameInfo.holdingsSize) p = 0;
10276 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10277 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10278 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10279 board[BOARD_HEIGHT-1-p][1] = 0;
10282 if (captured != EmptySquare && gameInfo.holdingsSize > 0
10283 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
10284 /* [HGM] holdings: Add to holdings, if holdings exist */
10285 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10286 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10287 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10289 p = (int) captured;
10290 if (p >= (int) BlackPawn) {
10291 p -= (int)BlackPawn;
10292 if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10293 /* Restore shogi-promoted piece to its original first */
10294 captured = (ChessSquare) (DEMOTED captured);
10297 p = PieceToNumber((ChessSquare)p);
10298 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10299 board[p][BOARD_WIDTH-2]++;
10300 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10302 p -= (int)WhitePawn;
10303 if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10304 captured = (ChessSquare) (DEMOTED captured);
10307 p = PieceToNumber((ChessSquare)p);
10308 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10309 board[BOARD_HEIGHT-1-p][1]++;
10310 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10313 } else if (gameInfo.variant == VariantAtomic) {
10314 if (captured != EmptySquare) {
10316 for (y = toY-1; y <= toY+1; y++) {
10317 for (x = toX-1; x <= toX+1; x++) {
10318 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10319 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10320 board[y][x] = EmptySquare;
10324 board[toY][toX] = EmptySquare;
10328 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10329 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10331 if(promoChar == '+') {
10332 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10333 board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10334 if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10335 board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10336 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10337 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10338 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10339 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10340 board[toY][toX] = newPiece;
10342 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10343 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10344 // [HGM] superchess: take promotion piece out of holdings
10345 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10346 if((int)piece < (int)BlackPawn) { // determine stm from piece color
10347 if(!--board[k][BOARD_WIDTH-2])
10348 board[k][BOARD_WIDTH-1] = EmptySquare;
10350 if(!--board[BOARD_HEIGHT-1-k][1])
10351 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10356 /* Updates forwardMostMove */
10358 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10360 int x = toX, y = toY;
10361 char *s = parseList[forwardMostMove];
10362 ChessSquare p = boards[forwardMostMove][toY][toX];
10363 // forwardMostMove++; // [HGM] bare: moved downstream
10365 if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10366 (void) CoordsToAlgebraic(boards[forwardMostMove],
10367 PosFlags(forwardMostMove),
10368 fromY, fromX, y, x, promoChar,
10370 if(killX >= 0 && killY >= 0)
10371 sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10373 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10374 int timeLeft; static int lastLoadFlag=0; int king, piece;
10375 piece = boards[forwardMostMove][fromY][fromX];
10376 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10377 if(gameInfo.variant == VariantKnightmate)
10378 king += (int) WhiteUnicorn - (int) WhiteKing;
10379 if(forwardMostMove == 0) {
10380 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10381 fprintf(serverMoves, "%s;", UserName());
10382 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10383 fprintf(serverMoves, "%s;", second.tidy);
10384 fprintf(serverMoves, "%s;", first.tidy);
10385 if(gameMode == MachinePlaysWhite)
10386 fprintf(serverMoves, "%s;", UserName());
10387 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10388 fprintf(serverMoves, "%s;", second.tidy);
10389 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10390 lastLoadFlag = loadFlag;
10392 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10393 // print castling suffix
10394 if( toY == fromY && piece == king ) {
10396 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10398 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10401 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10402 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
10403 boards[forwardMostMove][toY][toX] == EmptySquare
10404 && fromX != toX && fromY != toY)
10405 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10406 // promotion suffix
10407 if(promoChar != NULLCHAR) {
10408 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10409 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10410 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10411 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10414 char buf[MOVE_LEN*2], *p; int len;
10415 fprintf(serverMoves, "/%d/%d",
10416 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10417 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10418 else timeLeft = blackTimeRemaining/1000;
10419 fprintf(serverMoves, "/%d", timeLeft);
10420 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10421 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10422 if(p = strchr(buf, '=')) *p = NULLCHAR;
10423 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10424 fprintf(serverMoves, "/%s", buf);
10426 fflush(serverMoves);
10429 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10430 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10433 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10434 if (commentList[forwardMostMove+1] != NULL) {
10435 free(commentList[forwardMostMove+1]);
10436 commentList[forwardMostMove+1] = NULL;
10438 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10439 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10440 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10441 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10442 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10443 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10444 adjustedClock = FALSE;
10445 gameInfo.result = GameUnfinished;
10446 if (gameInfo.resultDetails != NULL) {
10447 free(gameInfo.resultDetails);
10448 gameInfo.resultDetails = NULL;
10450 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10451 moveList[forwardMostMove - 1]);
10452 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10458 if(!IS_SHOGI(gameInfo.variant))
10459 strcat(parseList[forwardMostMove - 1], "+");
10463 strcat(parseList[forwardMostMove - 1], "#");
10468 /* Updates currentMove if not pausing */
10470 ShowMove (int fromX, int fromY, int toX, int toY)
10472 int instant = (gameMode == PlayFromGameFile) ?
10473 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10474 if(appData.noGUI) return;
10475 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10477 if (forwardMostMove == currentMove + 1) {
10478 AnimateMove(boards[forwardMostMove - 1],
10479 fromX, fromY, toX, toY);
10482 currentMove = forwardMostMove;
10485 killX = killY = -1; // [HGM] lion: used up
10487 if (instant) return;
10489 DisplayMove(currentMove - 1);
10490 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10491 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10492 SetHighlights(fromX, fromY, toX, toY);
10495 DrawPosition(FALSE, boards[currentMove]);
10496 DisplayBothClocks();
10497 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10501 SendEgtPath (ChessProgramState *cps)
10502 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10503 char buf[MSG_SIZ], name[MSG_SIZ], *p;
10505 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10508 char c, *q = name+1, *r, *s;
10510 name[0] = ','; // extract next format name from feature and copy with prefixed ','
10511 while(*p && *p != ',') *q++ = *p++;
10512 *q++ = ':'; *q = 0;
10513 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10514 strcmp(name, ",nalimov:") == 0 ) {
10515 // take nalimov path from the menu-changeable option first, if it is defined
10516 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10517 SendToProgram(buf,cps); // send egtbpath command for nalimov
10519 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10520 (s = StrStr(appData.egtFormats, name)) != NULL) {
10521 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10522 s = r = StrStr(s, ":") + 1; // beginning of path info
10523 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10524 c = *r; *r = 0; // temporarily null-terminate path info
10525 *--q = 0; // strip of trailig ':' from name
10526 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10528 SendToProgram(buf,cps); // send egtbpath command for this format
10530 if(*p == ',') p++; // read away comma to position for next format name
10535 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10537 int width = 8, height = 8, holdings = 0; // most common sizes
10538 if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10539 // correct the deviations default for each variant
10540 if( v == VariantXiangqi ) width = 9, height = 10;
10541 if( v == VariantShogi ) width = 9, height = 9, holdings = 7;
10542 if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10543 if( v == VariantCapablanca || v == VariantCapaRandom ||
10544 v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10546 if( v == VariantCourier ) width = 12;
10547 if( v == VariantSuper ) holdings = 8;
10548 if( v == VariantGreat ) width = 10, holdings = 8;
10549 if( v == VariantSChess ) holdings = 7;
10550 if( v == VariantGrand ) width = 10, height = 10, holdings = 7;
10551 if( v == VariantChuChess) width = 10, height = 10;
10552 if( v == VariantChu ) width = 12, height = 12;
10553 return boardWidth >= 0 && boardWidth != width || // -1 is default,
10554 boardHeight >= 0 && boardHeight != height || // and thus by definition OK
10555 holdingsSize >= 0 && holdingsSize != holdings;
10558 char variantError[MSG_SIZ];
10561 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10562 { // returns error message (recognizable by upper-case) if engine does not support the variant
10563 char *p, *variant = VariantName(v);
10564 static char b[MSG_SIZ];
10565 if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10566 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10567 holdingsSize, variant); // cook up sized variant name
10568 /* [HGM] varsize: try first if this deviant size variant is specifically known */
10569 if(StrStr(list, b) == NULL) {
10570 // specific sized variant not known, check if general sizing allowed
10571 if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10572 snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10573 boardWidth, boardHeight, holdingsSize, engine);
10576 /* [HGM] here we really should compare with the maximum supported board size */
10578 } else snprintf(b, MSG_SIZ,"%s", variant);
10579 if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10580 p = StrStr(list, b);
10581 while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10583 // occurs not at all in list, or only as sub-string
10584 snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10585 if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10586 int l = strlen(variantError);
10588 while(p != list && p[-1] != ',') p--;
10589 q = strchr(p, ',');
10590 if(q) *q = NULLCHAR;
10591 snprintf(variantError + l, MSG_SIZ - l, _(", but %s is"), p);
10600 InitChessProgram (ChessProgramState *cps, int setup)
10601 /* setup needed to setup FRC opening position */
10603 char buf[MSG_SIZ], *b;
10604 if (appData.noChessProgram) return;
10605 hintRequested = FALSE;
10606 bookRequested = FALSE;
10608 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10609 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10610 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10611 if(cps->memSize) { /* [HGM] memory */
10612 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10613 SendToProgram(buf, cps);
10615 SendEgtPath(cps); /* [HGM] EGT */
10616 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10617 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10618 SendToProgram(buf, cps);
10621 setboardSpoiledMachineBlack = FALSE;
10622 SendToProgram(cps->initString, cps);
10623 if (gameInfo.variant != VariantNormal &&
10624 gameInfo.variant != VariantLoadable
10625 /* [HGM] also send variant if board size non-standard */
10626 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10628 b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10629 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10632 char c, *q = cps->variants, *p = strchr(q, ',');
10633 if(p) *p = NULLCHAR;
10634 v = StringToVariant(q);
10635 DisplayError(variantError, 0);
10636 if(v != VariantUnknown && cps == &first) {
10638 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10639 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10640 ASSIGN(appData.variant, q);
10641 Reset(TRUE, FALSE);
10647 snprintf(buf, MSG_SIZ, "variant %s\n", b);
10648 SendToProgram(buf, cps);
10650 currentlyInitializedVariant = gameInfo.variant;
10652 /* [HGM] send opening position in FRC to first engine */
10654 SendToProgram("force\n", cps);
10656 /* engine is now in force mode! Set flag to wake it up after first move. */
10657 setboardSpoiledMachineBlack = 1;
10660 if (cps->sendICS) {
10661 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10662 SendToProgram(buf, cps);
10664 cps->maybeThinking = FALSE;
10665 cps->offeredDraw = 0;
10666 if (!appData.icsActive) {
10667 SendTimeControl(cps, movesPerSession, timeControl,
10668 timeIncrement, appData.searchDepth,
10671 if (appData.showThinking
10672 // [HGM] thinking: four options require thinking output to be sent
10673 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10675 SendToProgram("post\n", cps);
10677 SendToProgram("hard\n", cps);
10678 if (!appData.ponderNextMove) {
10679 /* Warning: "easy" is a toggle in GNU Chess, so don't send
10680 it without being sure what state we are in first. "hard"
10681 is not a toggle, so that one is OK.
10683 SendToProgram("easy\n", cps);
10685 if (cps->usePing) {
10686 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10687 SendToProgram(buf, cps);
10689 cps->initDone = TRUE;
10690 ClearEngineOutputPane(cps == &second);
10695 ResendOptions (ChessProgramState *cps)
10696 { // send the stored value of the options
10699 Option *opt = cps->option;
10700 for(i=0; i<cps->nrOptions; i++, opt++) {
10701 switch(opt->type) {
10705 snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10708 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10711 snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10717 SendToProgram(buf, cps);
10722 StartChessProgram (ChessProgramState *cps)
10727 if (appData.noChessProgram) return;
10728 cps->initDone = FALSE;
10730 if (strcmp(cps->host, "localhost") == 0) {
10731 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10732 } else if (*appData.remoteShell == NULLCHAR) {
10733 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10735 if (*appData.remoteUser == NULLCHAR) {
10736 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10739 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10740 cps->host, appData.remoteUser, cps->program);
10742 err = StartChildProcess(buf, "", &cps->pr);
10746 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10747 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10748 if(cps != &first) return;
10749 appData.noChessProgram = TRUE;
10752 // DisplayFatalError(buf, err, 1);
10753 // cps->pr = NoProc;
10754 // cps->isr = NULL;
10758 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10759 if (cps->protocolVersion > 1) {
10760 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10761 if(!cps->reload) { // do not clear options when reloading because of -xreuse
10762 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10763 cps->comboCnt = 0; // and values of combo boxes
10765 SendToProgram(buf, cps);
10766 if(cps->reload) ResendOptions(cps);
10768 SendToProgram("xboard\n", cps);
10773 TwoMachinesEventIfReady P((void))
10775 static int curMess = 0;
10776 if (first.lastPing != first.lastPong) {
10777 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10778 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10781 if (second.lastPing != second.lastPong) {
10782 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10783 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10786 DisplayMessage("", ""); curMess = 0;
10787 TwoMachinesEvent();
10791 MakeName (char *template)
10795 static char buf[MSG_SIZ];
10799 clock = time((time_t *)NULL);
10800 tm = localtime(&clock);
10802 while(*p++ = *template++) if(p[-1] == '%') {
10803 switch(*template++) {
10804 case 0: *p = 0; return buf;
10805 case 'Y': i = tm->tm_year+1900; break;
10806 case 'y': i = tm->tm_year-100; break;
10807 case 'M': i = tm->tm_mon+1; break;
10808 case 'd': i = tm->tm_mday; break;
10809 case 'h': i = tm->tm_hour; break;
10810 case 'm': i = tm->tm_min; break;
10811 case 's': i = tm->tm_sec; break;
10814 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10820 CountPlayers (char *p)
10823 while(p = strchr(p, '\n')) p++, n++; // count participants
10828 WriteTourneyFile (char *results, FILE *f)
10829 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10830 if(f == NULL) f = fopen(appData.tourneyFile, "w");
10831 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10832 // create a file with tournament description
10833 fprintf(f, "-participants {%s}\n", appData.participants);
10834 fprintf(f, "-seedBase %d\n", appData.seedBase);
10835 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10836 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10837 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10838 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10839 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10840 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10841 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10842 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10843 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10844 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10845 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10846 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10847 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10848 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10849 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10850 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10851 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10852 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10853 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10854 fprintf(f, "-smpCores %d\n", appData.smpCores);
10856 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10858 fprintf(f, "-mps %d\n", appData.movesPerSession);
10859 fprintf(f, "-tc %s\n", appData.timeControl);
10860 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10862 fprintf(f, "-results \"%s\"\n", results);
10867 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10870 Substitute (char *participants, int expunge)
10872 int i, changed, changes=0, nPlayers=0;
10873 char *p, *q, *r, buf[MSG_SIZ];
10874 if(participants == NULL) return;
10875 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10876 r = p = participants; q = appData.participants;
10877 while(*p && *p == *q) {
10878 if(*p == '\n') r = p+1, nPlayers++;
10881 if(*p) { // difference
10882 while(*p && *p++ != '\n');
10883 while(*q && *q++ != '\n');
10884 changed = nPlayers;
10885 changes = 1 + (strcmp(p, q) != 0);
10887 if(changes == 1) { // a single engine mnemonic was changed
10888 q = r; while(*q) nPlayers += (*q++ == '\n');
10889 p = buf; while(*r && (*p = *r++) != '\n') p++;
10891 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10892 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10893 if(mnemonic[i]) { // The substitute is valid
10895 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10896 flock(fileno(f), LOCK_EX);
10897 ParseArgsFromFile(f);
10898 fseek(f, 0, SEEK_SET);
10899 FREE(appData.participants); appData.participants = participants;
10900 if(expunge) { // erase results of replaced engine
10901 int len = strlen(appData.results), w, b, dummy;
10902 for(i=0; i<len; i++) {
10903 Pairing(i, nPlayers, &w, &b, &dummy);
10904 if((w == changed || b == changed) && appData.results[i] == '*') {
10905 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10910 for(i=0; i<len; i++) {
10911 Pairing(i, nPlayers, &w, &b, &dummy);
10912 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10915 WriteTourneyFile(appData.results, f);
10916 fclose(f); // release lock
10919 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10921 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10922 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10923 free(participants);
10928 CheckPlayers (char *participants)
10931 char buf[MSG_SIZ], *p;
10932 NamesToList(firstChessProgramNames, command, mnemonic, "all");
10933 while(p = strchr(participants, '\n')) {
10935 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10937 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10939 DisplayError(buf, 0);
10943 participants = p + 1;
10949 CreateTourney (char *name)
10952 if(matchMode && strcmp(name, appData.tourneyFile)) {
10953 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10955 if(name[0] == NULLCHAR) {
10956 if(appData.participants[0])
10957 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10960 f = fopen(name, "r");
10961 if(f) { // file exists
10962 ASSIGN(appData.tourneyFile, name);
10963 ParseArgsFromFile(f); // parse it
10965 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10966 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10967 DisplayError(_("Not enough participants"), 0);
10970 if(CheckPlayers(appData.participants)) return 0;
10971 ASSIGN(appData.tourneyFile, name);
10972 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10973 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10976 appData.noChessProgram = FALSE;
10977 appData.clockMode = TRUE;
10983 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10985 char buf[MSG_SIZ], *p, *q;
10986 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10987 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10988 skip = !all && group[0]; // if group requested, we start in skip mode
10989 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10990 p = names; q = buf; header = 0;
10991 while(*p && *p != '\n') *q++ = *p++;
10993 if(*p == '\n') p++;
10994 if(buf[0] == '#') {
10995 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10996 depth++; // we must be entering a new group
10997 if(all) continue; // suppress printing group headers when complete list requested
10999 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11001 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11002 if(engineList[i]) free(engineList[i]);
11003 engineList[i] = strdup(buf);
11004 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11005 if(engineMnemonic[i]) free(engineMnemonic[i]);
11006 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11008 sscanf(q + 8, "%s", buf + strlen(buf));
11011 engineMnemonic[i] = strdup(buf);
11014 engineList[i] = engineMnemonic[i] = NULL;
11018 // following implemented as macro to avoid type limitations
11019 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11022 SwapEngines (int n)
11023 { // swap settings for first engine and other engine (so far only some selected options)
11028 SWAP(chessProgram, p)
11030 SWAP(hasOwnBookUCI, h)
11031 SWAP(protocolVersion, h)
11033 SWAP(scoreIsAbsolute, h)
11038 SWAP(engOptions, p)
11039 SWAP(engInitString, p)
11040 SWAP(computerString, p)
11042 SWAP(fenOverride, p)
11044 SWAP(accumulateTC, h)
11051 GetEngineLine (char *s, int n)
11055 extern char *icsNames;
11056 if(!s || !*s) return 0;
11057 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11058 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11059 if(!mnemonic[i]) return 0;
11060 if(n == 11) return 1; // just testing if there was a match
11061 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11062 if(n == 1) SwapEngines(n);
11063 ParseArgsFromString(buf);
11064 if(n == 1) SwapEngines(n);
11065 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11066 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11067 ParseArgsFromString(buf);
11073 SetPlayer (int player, char *p)
11074 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
11076 char buf[MSG_SIZ], *engineName;
11077 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11078 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11079 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11081 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11082 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11083 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11084 ParseArgsFromString(buf);
11085 } else { // no engine with this nickname is installed!
11086 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11087 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11088 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11090 DisplayError(buf, 0);
11097 char *recentEngines;
11100 RecentEngineEvent (int nr)
11103 // SwapEngines(1); // bump first to second
11104 // ReplaceEngine(&second, 1); // and load it there
11105 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11106 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11107 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11108 ReplaceEngine(&first, 0);
11109 FloatToFront(&appData.recentEngineList, command[n]);
11114 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11115 { // determine players from game number
11116 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11118 if(appData.tourneyType == 0) {
11119 roundsPerCycle = (nPlayers - 1) | 1;
11120 pairingsPerRound = nPlayers / 2;
11121 } else if(appData.tourneyType > 0) {
11122 roundsPerCycle = nPlayers - appData.tourneyType;
11123 pairingsPerRound = appData.tourneyType;
11125 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11126 gamesPerCycle = gamesPerRound * roundsPerCycle;
11127 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11128 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11129 curRound = nr / gamesPerRound; nr %= gamesPerRound;
11130 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11131 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11132 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11134 if(appData.cycleSync) *syncInterval = gamesPerCycle;
11135 if(appData.roundSync) *syncInterval = gamesPerRound;
11137 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11139 if(appData.tourneyType == 0) {
11140 if(curPairing == (nPlayers-1)/2 ) {
11141 *whitePlayer = curRound;
11142 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11144 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11145 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11146 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11147 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11149 } else if(appData.tourneyType > 1) {
11150 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11151 *whitePlayer = curRound + appData.tourneyType;
11152 } else if(appData.tourneyType > 0) {
11153 *whitePlayer = curPairing;
11154 *blackPlayer = curRound + appData.tourneyType;
11157 // take care of white/black alternation per round.
11158 // For cycles and games this is already taken care of by default, derived from matchGame!
11159 return curRound & 1;
11163 NextTourneyGame (int nr, int *swapColors)
11164 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11166 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11168 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11169 tf = fopen(appData.tourneyFile, "r");
11170 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11171 ParseArgsFromFile(tf); fclose(tf);
11172 InitTimeControls(); // TC might be altered from tourney file
11174 nPlayers = CountPlayers(appData.participants); // count participants
11175 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11176 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11179 p = q = appData.results;
11180 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11181 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11182 DisplayMessage(_("Waiting for other game(s)"),"");
11183 waitingForGame = TRUE;
11184 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11187 waitingForGame = FALSE;
11190 if(appData.tourneyType < 0) {
11191 if(nr>=0 && !pairingReceived) {
11193 if(pairing.pr == NoProc) {
11194 if(!appData.pairingEngine[0]) {
11195 DisplayFatalError(_("No pairing engine specified"), 0, 1);
11198 StartChessProgram(&pairing); // starts the pairing engine
11200 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11201 SendToProgram(buf, &pairing);
11202 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11203 SendToProgram(buf, &pairing);
11204 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11206 pairingReceived = 0; // ... so we continue here
11208 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11209 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11210 matchGame = 1; roundNr = nr / syncInterval + 1;
11213 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11215 // redefine engines, engine dir, etc.
11216 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11217 if(first.pr == NoProc) {
11218 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11219 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
11221 if(second.pr == NoProc) {
11223 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11224 SwapEngines(1); // and make that valid for second engine by swapping
11225 InitEngine(&second, 1);
11227 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
11228 UpdateLogos(FALSE); // leave display to ModeHiglight()
11234 { // performs game initialization that does not invoke engines, and then tries to start the game
11235 int res, firstWhite, swapColors = 0;
11236 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11237 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
11239 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11240 if(strcmp(buf, currentDebugFile)) { // name has changed
11241 FILE *f = fopen(buf, "w");
11242 if(f) { // if opening the new file failed, just keep using the old one
11243 ASSIGN(currentDebugFile, buf);
11247 if(appData.serverFileName) {
11248 if(serverFP) fclose(serverFP);
11249 serverFP = fopen(appData.serverFileName, "w");
11250 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11251 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11255 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11256 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11257 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
11258 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11259 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11260 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11261 Reset(FALSE, first.pr != NoProc);
11262 res = LoadGameOrPosition(matchGame); // setup game
11263 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11264 if(!res) return; // abort when bad game/pos file
11265 TwoMachinesEvent();
11269 UserAdjudicationEvent (int result)
11271 ChessMove gameResult = GameIsDrawn;
11274 gameResult = WhiteWins;
11276 else if( result < 0 ) {
11277 gameResult = BlackWins;
11280 if( gameMode == TwoMachinesPlay ) {
11281 GameEnds( gameResult, "User adjudication", GE_XBOARD );
11286 // [HGM] save: calculate checksum of game to make games easily identifiable
11288 StringCheckSum (char *s)
11291 if(s==NULL) return 0;
11292 while(*s) i = i*259 + *s++;
11300 for(i=backwardMostMove; i<forwardMostMove; i++) {
11301 sum += pvInfoList[i].depth;
11302 sum += StringCheckSum(parseList[i]);
11303 sum += StringCheckSum(commentList[i]);
11306 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11307 return sum + StringCheckSum(commentList[i]);
11308 } // end of save patch
11311 GameEnds (ChessMove result, char *resultDetails, int whosays)
11313 GameMode nextGameMode;
11315 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11317 if(endingGame) return; /* [HGM] crash: forbid recursion */
11319 if(twoBoards) { // [HGM] dual: switch back to one board
11320 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11321 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11323 if (appData.debugMode) {
11324 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11325 result, resultDetails ? resultDetails : "(null)", whosays);
11328 fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11330 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11332 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11333 /* If we are playing on ICS, the server decides when the
11334 game is over, but the engine can offer to draw, claim
11338 if (appData.zippyPlay && first.initDone) {
11339 if (result == GameIsDrawn) {
11340 /* In case draw still needs to be claimed */
11341 SendToICS(ics_prefix);
11342 SendToICS("draw\n");
11343 } else if (StrCaseStr(resultDetails, "resign")) {
11344 SendToICS(ics_prefix);
11345 SendToICS("resign\n");
11349 endingGame = 0; /* [HGM] crash */
11353 /* If we're loading the game from a file, stop */
11354 if (whosays == GE_FILE) {
11355 (void) StopLoadGameTimer();
11359 /* Cancel draw offers */
11360 first.offeredDraw = second.offeredDraw = 0;
11362 /* If this is an ICS game, only ICS can really say it's done;
11363 if not, anyone can. */
11364 isIcsGame = (gameMode == IcsPlayingWhite ||
11365 gameMode == IcsPlayingBlack ||
11366 gameMode == IcsObserving ||
11367 gameMode == IcsExamining);
11369 if (!isIcsGame || whosays == GE_ICS) {
11370 /* OK -- not an ICS game, or ICS said it was done */
11372 if (!isIcsGame && !appData.noChessProgram)
11373 SetUserThinkingEnables();
11375 /* [HGM] if a machine claims the game end we verify this claim */
11376 if(gameMode == TwoMachinesPlay && appData.testClaims) {
11377 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11379 ChessMove trueResult = (ChessMove) -1;
11381 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
11382 first.twoMachinesColor[0] :
11383 second.twoMachinesColor[0] ;
11385 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11386 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11387 /* [HGM] verify: engine mate claims accepted if they were flagged */
11388 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11390 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11391 /* [HGM] verify: engine mate claims accepted if they were flagged */
11392 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11394 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11395 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11398 // now verify win claims, but not in drop games, as we don't understand those yet
11399 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11400 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11401 (result == WhiteWins && claimer == 'w' ||
11402 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
11403 if (appData.debugMode) {
11404 fprintf(debugFP, "result=%d sp=%d move=%d\n",
11405 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11407 if(result != trueResult) {
11408 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11409 result = claimer == 'w' ? BlackWins : WhiteWins;
11410 resultDetails = buf;
11413 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11414 && (forwardMostMove <= backwardMostMove ||
11415 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11416 (claimer=='b')==(forwardMostMove&1))
11418 /* [HGM] verify: draws that were not flagged are false claims */
11419 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11420 result = claimer == 'w' ? BlackWins : WhiteWins;
11421 resultDetails = buf;
11423 /* (Claiming a loss is accepted no questions asked!) */
11424 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11425 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11426 result = GameUnfinished;
11427 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11429 /* [HGM] bare: don't allow bare King to win */
11430 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11431 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11432 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11433 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11434 && result != GameIsDrawn)
11435 { int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11436 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11437 int p = (signed char)boards[forwardMostMove][i][j] - color;
11438 if(p >= 0 && p <= (int)WhiteKing) k++;
11439 oppoKings += (p + color == WhiteKing + BlackPawn - color);
11441 if (appData.debugMode) {
11442 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11443 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11445 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11446 result = GameIsDrawn;
11447 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11448 resultDetails = buf;
11454 if(serverMoves != NULL && !loadFlag) { char c = '=';
11455 if(result==WhiteWins) c = '+';
11456 if(result==BlackWins) c = '-';
11457 if(resultDetails != NULL)
11458 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11460 if (resultDetails != NULL) {
11461 gameInfo.result = result;
11462 gameInfo.resultDetails = StrSave(resultDetails);
11464 /* display last move only if game was not loaded from file */
11465 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11466 DisplayMove(currentMove - 1);
11468 if (forwardMostMove != 0) {
11469 if (gameMode != PlayFromGameFile && gameMode != EditGame
11470 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11472 if (*appData.saveGameFile != NULLCHAR) {
11473 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11474 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11476 SaveGameToFile(appData.saveGameFile, TRUE);
11477 } else if (appData.autoSaveGames) {
11478 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11480 if (*appData.savePositionFile != NULLCHAR) {
11481 SavePositionToFile(appData.savePositionFile);
11483 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11487 /* Tell program how game ended in case it is learning */
11488 /* [HGM] Moved this to after saving the PGN, just in case */
11489 /* engine died and we got here through time loss. In that */
11490 /* case we will get a fatal error writing the pipe, which */
11491 /* would otherwise lose us the PGN. */
11492 /* [HGM] crash: not needed anymore, but doesn't hurt; */
11493 /* output during GameEnds should never be fatal anymore */
11494 if (gameMode == MachinePlaysWhite ||
11495 gameMode == MachinePlaysBlack ||
11496 gameMode == TwoMachinesPlay ||
11497 gameMode == IcsPlayingWhite ||
11498 gameMode == IcsPlayingBlack ||
11499 gameMode == BeginningOfGame) {
11501 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11503 if (first.pr != NoProc) {
11504 SendToProgram(buf, &first);
11506 if (second.pr != NoProc &&
11507 gameMode == TwoMachinesPlay) {
11508 SendToProgram(buf, &second);
11513 if (appData.icsActive) {
11514 if (appData.quietPlay &&
11515 (gameMode == IcsPlayingWhite ||
11516 gameMode == IcsPlayingBlack)) {
11517 SendToICS(ics_prefix);
11518 SendToICS("set shout 1\n");
11520 nextGameMode = IcsIdle;
11521 ics_user_moved = FALSE;
11522 /* clean up premove. It's ugly when the game has ended and the
11523 * premove highlights are still on the board.
11526 gotPremove = FALSE;
11527 ClearPremoveHighlights();
11528 DrawPosition(FALSE, boards[currentMove]);
11530 if (whosays == GE_ICS) {
11533 if (gameMode == IcsPlayingWhite)
11535 else if(gameMode == IcsPlayingBlack)
11536 PlayIcsLossSound();
11539 if (gameMode == IcsPlayingBlack)
11541 else if(gameMode == IcsPlayingWhite)
11542 PlayIcsLossSound();
11545 PlayIcsDrawSound();
11548 PlayIcsUnfinishedSound();
11551 if(appData.quitNext) { ExitEvent(0); return; }
11552 } else if (gameMode == EditGame ||
11553 gameMode == PlayFromGameFile ||
11554 gameMode == AnalyzeMode ||
11555 gameMode == AnalyzeFile) {
11556 nextGameMode = gameMode;
11558 nextGameMode = EndOfGame;
11563 nextGameMode = gameMode;
11566 if (appData.noChessProgram) {
11567 gameMode = nextGameMode;
11569 endingGame = 0; /* [HGM] crash */
11574 /* Put first chess program into idle state */
11575 if (first.pr != NoProc &&
11576 (gameMode == MachinePlaysWhite ||
11577 gameMode == MachinePlaysBlack ||
11578 gameMode == TwoMachinesPlay ||
11579 gameMode == IcsPlayingWhite ||
11580 gameMode == IcsPlayingBlack ||
11581 gameMode == BeginningOfGame)) {
11582 SendToProgram("force\n", &first);
11583 if (first.usePing) {
11585 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11586 SendToProgram(buf, &first);
11589 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11590 /* Kill off first chess program */
11591 if (first.isr != NULL)
11592 RemoveInputSource(first.isr);
11595 if (first.pr != NoProc) {
11597 DoSleep( appData.delayBeforeQuit );
11598 SendToProgram("quit\n", &first);
11599 DestroyChildProcess(first.pr, 4 + first.useSigterm);
11600 first.reload = TRUE;
11604 if (second.reuse) {
11605 /* Put second chess program into idle state */
11606 if (second.pr != NoProc &&
11607 gameMode == TwoMachinesPlay) {
11608 SendToProgram("force\n", &second);
11609 if (second.usePing) {
11611 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11612 SendToProgram(buf, &second);
11615 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11616 /* Kill off second chess program */
11617 if (second.isr != NULL)
11618 RemoveInputSource(second.isr);
11621 if (second.pr != NoProc) {
11622 DoSleep( appData.delayBeforeQuit );
11623 SendToProgram("quit\n", &second);
11624 DestroyChildProcess(second.pr, 4 + second.useSigterm);
11625 second.reload = TRUE;
11627 second.pr = NoProc;
11630 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11631 char resChar = '=';
11635 if (first.twoMachinesColor[0] == 'w') {
11638 second.matchWins++;
11643 if (first.twoMachinesColor[0] == 'b') {
11646 second.matchWins++;
11649 case GameUnfinished:
11655 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11656 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11657 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11658 ReserveGame(nextGame, resChar); // sets nextGame
11659 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11660 else ranking = strdup("busy"); //suppress popup when aborted but not finished
11661 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11663 if (nextGame <= appData.matchGames && !abortMatch) {
11664 gameMode = nextGameMode;
11665 matchGame = nextGame; // this will be overruled in tourney mode!
11666 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11667 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11668 endingGame = 0; /* [HGM] crash */
11671 gameMode = nextGameMode;
11672 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11673 first.tidy, second.tidy,
11674 first.matchWins, second.matchWins,
11675 appData.matchGames - (first.matchWins + second.matchWins));
11676 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11677 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11678 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11679 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11680 first.twoMachinesColor = "black\n";
11681 second.twoMachinesColor = "white\n";
11683 first.twoMachinesColor = "white\n";
11684 second.twoMachinesColor = "black\n";
11688 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11689 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11691 gameMode = nextGameMode;
11693 endingGame = 0; /* [HGM] crash */
11694 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11695 if(matchMode == TRUE) { // match through command line: exit with or without popup
11697 ToNrEvent(forwardMostMove);
11698 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11700 } else DisplayFatalError(buf, 0, 0);
11701 } else { // match through menu; just stop, with or without popup
11702 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11705 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11706 } else DisplayNote(buf);
11708 if(ranking) free(ranking);
11712 /* Assumes program was just initialized (initString sent).
11713 Leaves program in force mode. */
11715 FeedMovesToProgram (ChessProgramState *cps, int upto)
11719 if (appData.debugMode)
11720 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11721 startedFromSetupPosition ? "position and " : "",
11722 backwardMostMove, upto, cps->which);
11723 if(currentlyInitializedVariant != gameInfo.variant) {
11725 // [HGM] variantswitch: make engine aware of new variant
11726 if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11727 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11728 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11729 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11730 SendToProgram(buf, cps);
11731 currentlyInitializedVariant = gameInfo.variant;
11733 SendToProgram("force\n", cps);
11734 if (startedFromSetupPosition) {
11735 SendBoard(cps, backwardMostMove);
11736 if (appData.debugMode) {
11737 fprintf(debugFP, "feedMoves\n");
11740 for (i = backwardMostMove; i < upto; i++) {
11741 SendMoveToProgram(i, cps);
11747 ResurrectChessProgram ()
11749 /* The chess program may have exited.
11750 If so, restart it and feed it all the moves made so far. */
11751 static int doInit = 0;
11753 if (appData.noChessProgram) return 1;
11755 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11756 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11757 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11758 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11760 if (first.pr != NoProc) return 1;
11761 StartChessProgram(&first);
11763 InitChessProgram(&first, FALSE);
11764 FeedMovesToProgram(&first, currentMove);
11766 if (!first.sendTime) {
11767 /* can't tell gnuchess what its clock should read,
11768 so we bow to its notion. */
11770 timeRemaining[0][currentMove] = whiteTimeRemaining;
11771 timeRemaining[1][currentMove] = blackTimeRemaining;
11774 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11775 appData.icsEngineAnalyze) && first.analysisSupport) {
11776 SendToProgram("analyze\n", &first);
11777 first.analyzing = TRUE;
11783 * Button procedures
11786 Reset (int redraw, int init)
11790 if (appData.debugMode) {
11791 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11792 redraw, init, gameMode);
11794 pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11795 for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11796 CleanupTail(); // [HGM] vari: delete any stored variations
11797 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11798 pausing = pauseExamInvalid = FALSE;
11799 startedFromSetupPosition = blackPlaysFirst = FALSE;
11801 whiteFlag = blackFlag = FALSE;
11802 userOfferedDraw = FALSE;
11803 hintRequested = bookRequested = FALSE;
11804 first.maybeThinking = FALSE;
11805 second.maybeThinking = FALSE;
11806 first.bookSuspend = FALSE; // [HGM] book
11807 second.bookSuspend = FALSE;
11808 thinkOutput[0] = NULLCHAR;
11809 lastHint[0] = NULLCHAR;
11810 ClearGameInfo(&gameInfo);
11811 gameInfo.variant = StringToVariant(appData.variant);
11812 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11813 ics_user_moved = ics_clock_paused = FALSE;
11814 ics_getting_history = H_FALSE;
11816 white_holding[0] = black_holding[0] = NULLCHAR;
11817 ClearProgramStats();
11818 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11822 flipView = appData.flipView;
11823 ClearPremoveHighlights();
11824 gotPremove = FALSE;
11825 alarmSounded = FALSE;
11826 killX = killY = -1; // [HGM] lion
11828 GameEnds(EndOfFile, NULL, GE_PLAYER);
11829 if(appData.serverMovesName != NULL) {
11830 /* [HGM] prepare to make moves file for broadcasting */
11831 clock_t t = clock();
11832 if(serverMoves != NULL) fclose(serverMoves);
11833 serverMoves = fopen(appData.serverMovesName, "r");
11834 if(serverMoves != NULL) {
11835 fclose(serverMoves);
11836 /* delay 15 sec before overwriting, so all clients can see end */
11837 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11839 serverMoves = fopen(appData.serverMovesName, "w");
11843 gameMode = BeginningOfGame;
11845 if(appData.icsActive) gameInfo.variant = VariantNormal;
11846 currentMove = forwardMostMove = backwardMostMove = 0;
11847 MarkTargetSquares(1);
11848 InitPosition(redraw);
11849 for (i = 0; i < MAX_MOVES; i++) {
11850 if (commentList[i] != NULL) {
11851 free(commentList[i]);
11852 commentList[i] = NULL;
11856 timeRemaining[0][0] = whiteTimeRemaining;
11857 timeRemaining[1][0] = blackTimeRemaining;
11859 if (first.pr == NoProc) {
11860 StartChessProgram(&first);
11863 InitChessProgram(&first, startedFromSetupPosition);
11866 DisplayMessage("", "");
11867 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11868 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11869 ClearMap(); // [HGM] exclude: invalidate map
11873 AutoPlayGameLoop ()
11876 if (!AutoPlayOneMove())
11878 if (matchMode || appData.timeDelay == 0)
11880 if (appData.timeDelay < 0)
11882 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11890 ReloadGame(1); // next game
11896 int fromX, fromY, toX, toY;
11898 if (appData.debugMode) {
11899 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11902 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11905 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11906 pvInfoList[currentMove].depth = programStats.depth;
11907 pvInfoList[currentMove].score = programStats.score;
11908 pvInfoList[currentMove].time = 0;
11909 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11910 else { // append analysis of final position as comment
11912 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11913 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11915 programStats.depth = 0;
11918 if (currentMove >= forwardMostMove) {
11919 if(gameMode == AnalyzeFile) {
11920 if(appData.loadGameIndex == -1) {
11921 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11922 ScheduleDelayedEvent(AnalyzeNextGame, 10);
11924 ExitAnalyzeMode(); SendToProgram("force\n", &first);
11927 // gameMode = EndOfGame;
11928 // ModeHighlight();
11930 /* [AS] Clear current move marker at the end of a game */
11931 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11936 toX = moveList[currentMove][2] - AAA;
11937 toY = moveList[currentMove][3] - ONE;
11939 if (moveList[currentMove][1] == '@') {
11940 if (appData.highlightLastMove) {
11941 SetHighlights(-1, -1, toX, toY);
11944 int viaX = moveList[currentMove][5] - AAA;
11945 int viaY = moveList[currentMove][6] - ONE;
11946 fromX = moveList[currentMove][0] - AAA;
11947 fromY = moveList[currentMove][1] - ONE;
11949 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11951 if(moveList[currentMove][4] == ';') { // multi-leg
11952 ChessSquare piece = boards[currentMove][viaY][viaX];
11953 AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
11954 boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
11955 AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
11956 boards[currentMove][viaY][viaX] = piece;
11958 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11960 if (appData.highlightLastMove) {
11961 SetHighlights(fromX, fromY, toX, toY);
11964 DisplayMove(currentMove);
11965 SendMoveToProgram(currentMove++, &first);
11966 DisplayBothClocks();
11967 DrawPosition(FALSE, boards[currentMove]);
11968 // [HGM] PV info: always display, routine tests if empty
11969 DisplayComment(currentMove - 1, commentList[currentMove]);
11975 LoadGameOneMove (ChessMove readAhead)
11977 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11978 char promoChar = NULLCHAR;
11979 ChessMove moveType;
11980 char move[MSG_SIZ];
11983 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11984 gameMode != AnalyzeMode && gameMode != Training) {
11989 yyboardindex = forwardMostMove;
11990 if (readAhead != EndOfFile) {
11991 moveType = readAhead;
11993 if (gameFileFP == NULL)
11995 moveType = (ChessMove) Myylex();
11999 switch (moveType) {
12001 if (appData.debugMode)
12002 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12005 /* append the comment but don't display it */
12006 AppendComment(currentMove, p, FALSE);
12009 case WhiteCapturesEnPassant:
12010 case BlackCapturesEnPassant:
12011 case WhitePromotion:
12012 case BlackPromotion:
12013 case WhiteNonPromotion:
12014 case BlackNonPromotion:
12017 case WhiteKingSideCastle:
12018 case WhiteQueenSideCastle:
12019 case BlackKingSideCastle:
12020 case BlackQueenSideCastle:
12021 case WhiteKingSideCastleWild:
12022 case WhiteQueenSideCastleWild:
12023 case BlackKingSideCastleWild:
12024 case BlackQueenSideCastleWild:
12026 case WhiteHSideCastleFR:
12027 case WhiteASideCastleFR:
12028 case BlackHSideCastleFR:
12029 case BlackASideCastleFR:
12031 if (appData.debugMode)
12032 fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12033 fromX = currentMoveString[0] - AAA;
12034 fromY = currentMoveString[1] - ONE;
12035 toX = currentMoveString[2] - AAA;
12036 toY = currentMoveString[3] - ONE;
12037 promoChar = currentMoveString[4];
12038 if(promoChar == ';') promoChar = NULLCHAR;
12043 if (appData.debugMode)
12044 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12045 fromX = moveType == WhiteDrop ?
12046 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12047 (int) CharToPiece(ToLower(currentMoveString[0]));
12049 toX = currentMoveString[2] - AAA;
12050 toY = currentMoveString[3] - ONE;
12056 case GameUnfinished:
12057 if (appData.debugMode)
12058 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12059 p = strchr(yy_text, '{');
12060 if (p == NULL) p = strchr(yy_text, '(');
12063 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12065 q = strchr(p, *p == '{' ? '}' : ')');
12066 if (q != NULL) *q = NULLCHAR;
12069 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12070 GameEnds(moveType, p, GE_FILE);
12072 if (cmailMsgLoaded) {
12074 flipView = WhiteOnMove(currentMove);
12075 if (moveType == GameUnfinished) flipView = !flipView;
12076 if (appData.debugMode)
12077 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12082 if (appData.debugMode)
12083 fprintf(debugFP, "Parser hit end of file\n");
12084 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12090 if (WhiteOnMove(currentMove)) {
12091 GameEnds(BlackWins, "Black mates", GE_FILE);
12093 GameEnds(WhiteWins, "White mates", GE_FILE);
12097 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12103 case MoveNumberOne:
12104 if (lastLoadGameStart == GNUChessGame) {
12105 /* GNUChessGames have numbers, but they aren't move numbers */
12106 if (appData.debugMode)
12107 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12108 yy_text, (int) moveType);
12109 return LoadGameOneMove(EndOfFile); /* tail recursion */
12111 /* else fall thru */
12116 /* Reached start of next game in file */
12117 if (appData.debugMode)
12118 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12119 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12125 if (WhiteOnMove(currentMove)) {
12126 GameEnds(BlackWins, "Black mates", GE_FILE);
12128 GameEnds(WhiteWins, "White mates", GE_FILE);
12132 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12138 case PositionDiagram: /* should not happen; ignore */
12139 case ElapsedTime: /* ignore */
12140 case NAG: /* ignore */
12141 if (appData.debugMode)
12142 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12143 yy_text, (int) moveType);
12144 return LoadGameOneMove(EndOfFile); /* tail recursion */
12147 if (appData.testLegality) {
12148 if (appData.debugMode)
12149 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12150 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12151 (forwardMostMove / 2) + 1,
12152 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12153 DisplayError(move, 0);
12156 if (appData.debugMode)
12157 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12158 yy_text, currentMoveString);
12159 if(currentMoveString[1] == '@') {
12160 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12163 fromX = currentMoveString[0] - AAA;
12164 fromY = currentMoveString[1] - ONE;
12166 toX = currentMoveString[2] - AAA;
12167 toY = currentMoveString[3] - ONE;
12168 promoChar = currentMoveString[4];
12172 case AmbiguousMove:
12173 if (appData.debugMode)
12174 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12175 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12176 (forwardMostMove / 2) + 1,
12177 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12178 DisplayError(move, 0);
12183 case ImpossibleMove:
12184 if (appData.debugMode)
12185 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12186 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12187 (forwardMostMove / 2) + 1,
12188 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12189 DisplayError(move, 0);
12195 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12196 DrawPosition(FALSE, boards[currentMove]);
12197 DisplayBothClocks();
12198 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12199 DisplayComment(currentMove - 1, commentList[currentMove]);
12201 (void) StopLoadGameTimer();
12203 cmailOldMove = forwardMostMove;
12206 /* currentMoveString is set as a side-effect of yylex */
12208 thinkOutput[0] = NULLCHAR;
12209 MakeMove(fromX, fromY, toX, toY, promoChar);
12210 killX = killY = -1; // [HGM] lion: used up
12211 currentMove = forwardMostMove;
12216 /* Load the nth game from the given file */
12218 LoadGameFromFile (char *filename, int n, char *title, int useList)
12223 if (strcmp(filename, "-") == 0) {
12227 f = fopen(filename, "rb");
12229 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12230 DisplayError(buf, errno);
12234 if (fseek(f, 0, 0) == -1) {
12235 /* f is not seekable; probably a pipe */
12238 if (useList && n == 0) {
12239 int error = GameListBuild(f);
12241 DisplayError(_("Cannot build game list"), error);
12242 } else if (!ListEmpty(&gameList) &&
12243 ((ListGame *) gameList.tailPred)->number > 1) {
12244 GameListPopUp(f, title);
12251 return LoadGame(f, n, title, FALSE);
12256 MakeRegisteredMove ()
12258 int fromX, fromY, toX, toY;
12260 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12261 switch (cmailMoveType[lastLoadGameNumber - 1]) {
12264 if (appData.debugMode)
12265 fprintf(debugFP, "Restoring %s for game %d\n",
12266 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12268 thinkOutput[0] = NULLCHAR;
12269 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12270 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12271 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12272 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12273 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12274 promoChar = cmailMove[lastLoadGameNumber - 1][4];
12275 MakeMove(fromX, fromY, toX, toY, promoChar);
12276 ShowMove(fromX, fromY, toX, toY);
12278 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12285 if (WhiteOnMove(currentMove)) {
12286 GameEnds(BlackWins, "Black mates", GE_PLAYER);
12288 GameEnds(WhiteWins, "White mates", GE_PLAYER);
12293 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12300 if (WhiteOnMove(currentMove)) {
12301 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12303 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12308 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12319 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12321 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12325 if (gameNumber > nCmailGames) {
12326 DisplayError(_("No more games in this message"), 0);
12329 if (f == lastLoadGameFP) {
12330 int offset = gameNumber - lastLoadGameNumber;
12332 cmailMsg[0] = NULLCHAR;
12333 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12334 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12335 nCmailMovesRegistered--;
12337 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12338 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12339 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12342 if (! RegisterMove()) return FALSE;
12346 retVal = LoadGame(f, gameNumber, title, useList);
12348 /* Make move registered during previous look at this game, if any */
12349 MakeRegisteredMove();
12351 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12352 commentList[currentMove]
12353 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12354 DisplayComment(currentMove - 1, commentList[currentMove]);
12360 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12362 ReloadGame (int offset)
12364 int gameNumber = lastLoadGameNumber + offset;
12365 if (lastLoadGameFP == NULL) {
12366 DisplayError(_("No game has been loaded yet"), 0);
12369 if (gameNumber <= 0) {
12370 DisplayError(_("Can't back up any further"), 0);
12373 if (cmailMsgLoaded) {
12374 return CmailLoadGame(lastLoadGameFP, gameNumber,
12375 lastLoadGameTitle, lastLoadGameUseList);
12377 return LoadGame(lastLoadGameFP, gameNumber,
12378 lastLoadGameTitle, lastLoadGameUseList);
12382 int keys[EmptySquare+1];
12385 PositionMatches (Board b1, Board b2)
12388 switch(appData.searchMode) {
12389 case 1: return CompareWithRights(b1, b2);
12391 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12392 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12396 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12397 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12398 sum += keys[b1[r][f]] - keys[b2[r][f]];
12402 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12403 sum += keys[b1[r][f]] - keys[b2[r][f]];
12415 int pieceList[256], quickBoard[256];
12416 ChessSquare pieceType[256] = { EmptySquare };
12417 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12418 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12419 int soughtTotal, turn;
12420 Boolean epOK, flipSearch;
12423 unsigned char piece, to;
12426 #define DSIZE (250000)
12428 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12429 Move *moveDatabase = initialSpace;
12430 unsigned int movePtr, dataSize = DSIZE;
12433 MakePieceList (Board board, int *counts)
12435 int r, f, n=Q_PROMO, total=0;
12436 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12437 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12438 int sq = f + (r<<4);
12439 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12440 quickBoard[sq] = ++n;
12442 pieceType[n] = board[r][f];
12443 counts[board[r][f]]++;
12444 if(board[r][f] == WhiteKing) pieceList[1] = n; else
12445 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12449 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12454 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12456 int sq = fromX + (fromY<<4);
12457 int piece = quickBoard[sq], rook;
12458 quickBoard[sq] = 0;
12459 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12460 if(piece == pieceList[1] && fromY == toY) {
12461 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12462 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12463 moveDatabase[movePtr++].piece = Q_WCASTL;
12464 quickBoard[sq] = piece;
12465 piece = quickBoard[from]; quickBoard[from] = 0;
12466 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12467 } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12468 quickBoard[sq] = 0; // remove Rook
12469 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12470 moveDatabase[movePtr++].piece = Q_WCASTL;
12471 quickBoard[sq] = pieceList[1]; // put King
12473 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12476 if(piece == pieceList[2] && fromY == toY) {
12477 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12478 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12479 moveDatabase[movePtr++].piece = Q_BCASTL;
12480 quickBoard[sq] = piece;
12481 piece = quickBoard[from]; quickBoard[from] = 0;
12482 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12483 } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12484 quickBoard[sq] = 0; // remove Rook
12485 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12486 moveDatabase[movePtr++].piece = Q_BCASTL;
12487 quickBoard[sq] = pieceList[2]; // put King
12489 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12492 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12493 quickBoard[(fromY<<4)+toX] = 0;
12494 moveDatabase[movePtr].piece = Q_EP;
12495 moveDatabase[movePtr++].to = (fromY<<4)+toX;
12496 moveDatabase[movePtr].to = sq;
12498 if(promoPiece != pieceType[piece]) {
12499 moveDatabase[movePtr++].piece = Q_PROMO;
12500 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12502 moveDatabase[movePtr].piece = piece;
12503 quickBoard[sq] = piece;
12508 PackGame (Board board)
12510 Move *newSpace = NULL;
12511 moveDatabase[movePtr].piece = 0; // terminate previous game
12512 if(movePtr > dataSize) {
12513 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12514 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12515 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12518 Move *p = moveDatabase, *q = newSpace;
12519 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
12520 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12521 moveDatabase = newSpace;
12522 } else { // calloc failed, we must be out of memory. Too bad...
12523 dataSize = 0; // prevent calloc events for all subsequent games
12524 return 0; // and signal this one isn't cached
12528 MakePieceList(board, counts);
12533 QuickCompare (Board board, int *minCounts, int *maxCounts)
12534 { // compare according to search mode
12536 switch(appData.searchMode)
12538 case 1: // exact position match
12539 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12540 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12541 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12544 case 2: // can have extra material on empty squares
12545 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12546 if(board[r][f] == EmptySquare) continue;
12547 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12550 case 3: // material with exact Pawn structure
12551 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12552 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12553 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12554 } // fall through to material comparison
12555 case 4: // exact material
12556 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12558 case 6: // material range with given imbalance
12559 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12560 // fall through to range comparison
12561 case 5: // material range
12562 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12568 QuickScan (Board board, Move *move)
12569 { // reconstruct game,and compare all positions in it
12570 int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12572 int piece = move->piece;
12573 int to = move->to, from = pieceList[piece];
12574 if(found < 0) { // if already found just scan to game end for final piece count
12575 if(QuickCompare(soughtBoard, minSought, maxSought) ||
12576 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12577 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12578 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12580 static int lastCounts[EmptySquare+1];
12582 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12583 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12584 } else stretch = 0;
12585 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12586 if(found >= 0 && !appData.minPieces) return found;
12588 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12589 if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12590 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12591 piece = (++move)->piece;
12592 from = pieceList[piece];
12593 counts[pieceType[piece]]--;
12594 pieceType[piece] = (ChessSquare) move->to;
12595 counts[move->to]++;
12596 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12597 counts[pieceType[quickBoard[to]]]--;
12598 quickBoard[to] = 0; total--;
12601 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12602 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12603 from = pieceList[piece]; // so this must be King
12604 quickBoard[from] = 0;
12605 pieceList[piece] = to;
12606 from = pieceList[(++move)->piece]; // for FRC this has to be done here
12607 quickBoard[from] = 0; // rook
12608 quickBoard[to] = piece;
12609 to = move->to; piece = move->piece;
12613 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12614 if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12615 quickBoard[from] = 0;
12617 quickBoard[to] = piece;
12618 pieceList[piece] = to;
12628 flipSearch = FALSE;
12629 CopyBoard(soughtBoard, boards[currentMove]);
12630 soughtTotal = MakePieceList(soughtBoard, maxSought);
12631 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12632 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12633 CopyBoard(reverseBoard, boards[currentMove]);
12634 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12635 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12636 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12637 reverseBoard[r][f] = piece;
12639 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12640 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12641 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12642 || (boards[currentMove][CASTLING][2] == NoRights ||
12643 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12644 && (boards[currentMove][CASTLING][5] == NoRights ||
12645 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12648 CopyBoard(flipBoard, soughtBoard);
12649 CopyBoard(rotateBoard, reverseBoard);
12650 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12651 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
12652 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12655 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12656 if(appData.searchMode >= 5) {
12657 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12658 MakePieceList(soughtBoard, minSought);
12659 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12661 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12662 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12665 GameInfo dummyInfo;
12666 static int creatingBook;
12669 GameContainsPosition (FILE *f, ListGame *lg)
12671 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12672 int fromX, fromY, toX, toY;
12674 static int initDone=FALSE;
12676 // weed out games based on numerical tag comparison
12677 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12678 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12679 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12680 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12682 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12685 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12686 else CopyBoard(boards[scratch], initialPosition); // default start position
12689 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12690 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12693 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12694 fseek(f, lg->offset, 0);
12697 yyboardindex = scratch;
12698 quickFlag = plyNr+1;
12703 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12709 if(plyNr) return -1; // after we have seen moves, this is for new game
12712 case AmbiguousMove: // we cannot reconstruct the game beyond these two
12713 case ImpossibleMove:
12714 case WhiteWins: // game ends here with these four
12717 case GameUnfinished:
12721 if(appData.testLegality) return -1;
12722 case WhiteCapturesEnPassant:
12723 case BlackCapturesEnPassant:
12724 case WhitePromotion:
12725 case BlackPromotion:
12726 case WhiteNonPromotion:
12727 case BlackNonPromotion:
12730 case WhiteKingSideCastle:
12731 case WhiteQueenSideCastle:
12732 case BlackKingSideCastle:
12733 case BlackQueenSideCastle:
12734 case WhiteKingSideCastleWild:
12735 case WhiteQueenSideCastleWild:
12736 case BlackKingSideCastleWild:
12737 case BlackQueenSideCastleWild:
12738 case WhiteHSideCastleFR:
12739 case WhiteASideCastleFR:
12740 case BlackHSideCastleFR:
12741 case BlackASideCastleFR:
12742 fromX = currentMoveString[0] - AAA;
12743 fromY = currentMoveString[1] - ONE;
12744 toX = currentMoveString[2] - AAA;
12745 toY = currentMoveString[3] - ONE;
12746 promoChar = currentMoveString[4];
12750 fromX = next == WhiteDrop ?
12751 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12752 (int) CharToPiece(ToLower(currentMoveString[0]));
12754 toX = currentMoveString[2] - AAA;
12755 toY = currentMoveString[3] - ONE;
12759 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12761 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12762 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12763 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12764 if(appData.findMirror) {
12765 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12766 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12771 /* Load the nth game from open file f */
12773 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12777 int gn = gameNumber;
12778 ListGame *lg = NULL;
12779 int numPGNTags = 0;
12781 GameMode oldGameMode;
12782 VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12783 char oldName[MSG_SIZ];
12785 safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12787 if (appData.debugMode)
12788 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12790 if (gameMode == Training )
12791 SetTrainingModeOff();
12793 oldGameMode = gameMode;
12794 if (gameMode != BeginningOfGame) {
12795 Reset(FALSE, TRUE);
12797 killX = killY = -1; // [HGM] lion: in case we did not Reset
12800 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12801 fclose(lastLoadGameFP);
12805 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12808 fseek(f, lg->offset, 0);
12809 GameListHighlight(gameNumber);
12810 pos = lg->position;
12814 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12815 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12817 DisplayError(_("Game number out of range"), 0);
12822 if (fseek(f, 0, 0) == -1) {
12823 if (f == lastLoadGameFP ?
12824 gameNumber == lastLoadGameNumber + 1 :
12828 DisplayError(_("Can't seek on game file"), 0);
12833 lastLoadGameFP = f;
12834 lastLoadGameNumber = gameNumber;
12835 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12836 lastLoadGameUseList = useList;
12840 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12841 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12842 lg->gameInfo.black);
12844 } else if (*title != NULLCHAR) {
12845 if (gameNumber > 1) {
12846 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12849 DisplayTitle(title);
12853 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12854 gameMode = PlayFromGameFile;
12858 currentMove = forwardMostMove = backwardMostMove = 0;
12859 CopyBoard(boards[0], initialPosition);
12863 * Skip the first gn-1 games in the file.
12864 * Also skip over anything that precedes an identifiable
12865 * start of game marker, to avoid being confused by
12866 * garbage at the start of the file. Currently
12867 * recognized start of game markers are the move number "1",
12868 * the pattern "gnuchess .* game", the pattern
12869 * "^[#;%] [^ ]* game file", and a PGN tag block.
12870 * A game that starts with one of the latter two patterns
12871 * will also have a move number 1, possibly
12872 * following a position diagram.
12873 * 5-4-02: Let's try being more lenient and allowing a game to
12874 * start with an unnumbered move. Does that break anything?
12876 cm = lastLoadGameStart = EndOfFile;
12878 yyboardindex = forwardMostMove;
12879 cm = (ChessMove) Myylex();
12882 if (cmailMsgLoaded) {
12883 nCmailGames = CMAIL_MAX_GAMES - gn;
12886 DisplayError(_("Game not found in file"), 0);
12893 lastLoadGameStart = cm;
12896 case MoveNumberOne:
12897 switch (lastLoadGameStart) {
12902 case MoveNumberOne:
12904 gn--; /* count this game */
12905 lastLoadGameStart = cm;
12914 switch (lastLoadGameStart) {
12917 case MoveNumberOne:
12919 gn--; /* count this game */
12920 lastLoadGameStart = cm;
12923 lastLoadGameStart = cm; /* game counted already */
12931 yyboardindex = forwardMostMove;
12932 cm = (ChessMove) Myylex();
12933 } while (cm == PGNTag || cm == Comment);
12940 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12941 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
12942 != CMAIL_OLD_RESULT) {
12944 cmailResult[ CMAIL_MAX_GAMES
12945 - gn - 1] = CMAIL_OLD_RESULT;
12952 /* Only a NormalMove can be at the start of a game
12953 * without a position diagram. */
12954 if (lastLoadGameStart == EndOfFile ) {
12956 lastLoadGameStart = MoveNumberOne;
12965 if (appData.debugMode)
12966 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12968 if (cm == XBoardGame) {
12969 /* Skip any header junk before position diagram and/or move 1 */
12971 yyboardindex = forwardMostMove;
12972 cm = (ChessMove) Myylex();
12974 if (cm == EndOfFile ||
12975 cm == GNUChessGame || cm == XBoardGame) {
12976 /* Empty game; pretend end-of-file and handle later */
12981 if (cm == MoveNumberOne || cm == PositionDiagram ||
12982 cm == PGNTag || cm == Comment)
12985 } else if (cm == GNUChessGame) {
12986 if (gameInfo.event != NULL) {
12987 free(gameInfo.event);
12989 gameInfo.event = StrSave(yy_text);
12992 startedFromSetupPosition = FALSE;
12993 while (cm == PGNTag) {
12994 if (appData.debugMode)
12995 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12996 err = ParsePGNTag(yy_text, &gameInfo);
12997 if (!err) numPGNTags++;
12999 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13000 if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13001 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13002 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13003 InitPosition(TRUE);
13004 oldVariant = gameInfo.variant;
13005 if (appData.debugMode)
13006 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13010 if (gameInfo.fen != NULL) {
13011 Board initial_position;
13012 startedFromSetupPosition = TRUE;
13013 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13015 DisplayError(_("Bad FEN position in file"), 0);
13018 CopyBoard(boards[0], initial_position);
13019 if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13020 CopyBoard(initialPosition, initial_position);
13021 if (blackPlaysFirst) {
13022 currentMove = forwardMostMove = backwardMostMove = 1;
13023 CopyBoard(boards[1], initial_position);
13024 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13025 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13026 timeRemaining[0][1] = whiteTimeRemaining;
13027 timeRemaining[1][1] = blackTimeRemaining;
13028 if (commentList[0] != NULL) {
13029 commentList[1] = commentList[0];
13030 commentList[0] = NULL;
13033 currentMove = forwardMostMove = backwardMostMove = 0;
13035 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13037 initialRulePlies = FENrulePlies;
13038 for( i=0; i< nrCastlingRights; i++ )
13039 initialRights[i] = initial_position[CASTLING][i];
13041 yyboardindex = forwardMostMove;
13042 free(gameInfo.fen);
13043 gameInfo.fen = NULL;
13046 yyboardindex = forwardMostMove;
13047 cm = (ChessMove) Myylex();
13049 /* Handle comments interspersed among the tags */
13050 while (cm == Comment) {
13052 if (appData.debugMode)
13053 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13055 AppendComment(currentMove, p, FALSE);
13056 yyboardindex = forwardMostMove;
13057 cm = (ChessMove) Myylex();
13061 /* don't rely on existence of Event tag since if game was
13062 * pasted from clipboard the Event tag may not exist
13064 if (numPGNTags > 0){
13066 if (gameInfo.variant == VariantNormal) {
13067 VariantClass v = StringToVariant(gameInfo.event);
13068 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13069 if(v < VariantShogi) gameInfo.variant = v;
13072 if( appData.autoDisplayTags ) {
13073 tags = PGNTags(&gameInfo);
13074 TagsPopUp(tags, CmailMsg());
13079 /* Make something up, but don't display it now */
13084 if (cm == PositionDiagram) {
13087 Board initial_position;
13089 if (appData.debugMode)
13090 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13092 if (!startedFromSetupPosition) {
13094 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13095 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13106 initial_position[i][j++] = CharToPiece(*p);
13109 while (*p == ' ' || *p == '\t' ||
13110 *p == '\n' || *p == '\r') p++;
13112 if (strncmp(p, "black", strlen("black"))==0)
13113 blackPlaysFirst = TRUE;
13115 blackPlaysFirst = FALSE;
13116 startedFromSetupPosition = TRUE;
13118 CopyBoard(boards[0], initial_position);
13119 if (blackPlaysFirst) {
13120 currentMove = forwardMostMove = backwardMostMove = 1;
13121 CopyBoard(boards[1], initial_position);
13122 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13123 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13124 timeRemaining[0][1] = whiteTimeRemaining;
13125 timeRemaining[1][1] = blackTimeRemaining;
13126 if (commentList[0] != NULL) {
13127 commentList[1] = commentList[0];
13128 commentList[0] = NULL;
13131 currentMove = forwardMostMove = backwardMostMove = 0;
13134 yyboardindex = forwardMostMove;
13135 cm = (ChessMove) Myylex();
13138 if(!creatingBook) {
13139 if (first.pr == NoProc) {
13140 StartChessProgram(&first);
13142 InitChessProgram(&first, FALSE);
13143 if(gameInfo.variant == VariantUnknown && *oldName) {
13144 safeStrCpy(engineVariant, oldName, MSG_SIZ);
13145 gameInfo.variant = v;
13147 SendToProgram("force\n", &first);
13148 if (startedFromSetupPosition) {
13149 SendBoard(&first, forwardMostMove);
13150 if (appData.debugMode) {
13151 fprintf(debugFP, "Load Game\n");
13153 DisplayBothClocks();
13157 /* [HGM] server: flag to write setup moves in broadcast file as one */
13158 loadFlag = appData.suppressLoadMoves;
13160 while (cm == Comment) {
13162 if (appData.debugMode)
13163 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13165 AppendComment(currentMove, p, FALSE);
13166 yyboardindex = forwardMostMove;
13167 cm = (ChessMove) Myylex();
13170 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13171 cm == WhiteWins || cm == BlackWins ||
13172 cm == GameIsDrawn || cm == GameUnfinished) {
13173 DisplayMessage("", _("No moves in game"));
13174 if (cmailMsgLoaded) {
13175 if (appData.debugMode)
13176 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13180 DrawPosition(FALSE, boards[currentMove]);
13181 DisplayBothClocks();
13182 gameMode = EditGame;
13189 // [HGM] PV info: routine tests if comment empty
13190 if (!matchMode && (pausing || appData.timeDelay != 0)) {
13191 DisplayComment(currentMove - 1, commentList[currentMove]);
13193 if (!matchMode && appData.timeDelay != 0)
13194 DrawPosition(FALSE, boards[currentMove]);
13196 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13197 programStats.ok_to_send = 1;
13200 /* if the first token after the PGN tags is a move
13201 * and not move number 1, retrieve it from the parser
13203 if (cm != MoveNumberOne)
13204 LoadGameOneMove(cm);
13206 /* load the remaining moves from the file */
13207 while (LoadGameOneMove(EndOfFile)) {
13208 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13209 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13212 /* rewind to the start of the game */
13213 currentMove = backwardMostMove;
13215 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13217 if (oldGameMode == AnalyzeFile) {
13218 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13219 AnalyzeFileEvent();
13221 if (oldGameMode == AnalyzeMode) {
13222 AnalyzeFileEvent();
13225 if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13226 long int w, b; // [HGM] adjourn: restore saved clock times
13227 char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13228 if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13229 timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13230 timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13234 if(creatingBook) return TRUE;
13235 if (!matchMode && pos > 0) {
13236 ToNrEvent(pos); // [HGM] no autoplay if selected on position
13238 if (matchMode || appData.timeDelay == 0) {
13240 } else if (appData.timeDelay > 0) {
13241 AutoPlayGameLoop();
13244 if (appData.debugMode)
13245 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13247 loadFlag = 0; /* [HGM] true game starts */
13251 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13253 ReloadPosition (int offset)
13255 int positionNumber = lastLoadPositionNumber + offset;
13256 if (lastLoadPositionFP == NULL) {
13257 DisplayError(_("No position has been loaded yet"), 0);
13260 if (positionNumber <= 0) {
13261 DisplayError(_("Can't back up any further"), 0);
13264 return LoadPosition(lastLoadPositionFP, positionNumber,
13265 lastLoadPositionTitle);
13268 /* Load the nth position from the given file */
13270 LoadPositionFromFile (char *filename, int n, char *title)
13275 if (strcmp(filename, "-") == 0) {
13276 return LoadPosition(stdin, n, "stdin");
13278 f = fopen(filename, "rb");
13280 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13281 DisplayError(buf, errno);
13284 return LoadPosition(f, n, title);
13289 /* Load the nth position from the given open file, and close it */
13291 LoadPosition (FILE *f, int positionNumber, char *title)
13293 char *p, line[MSG_SIZ];
13294 Board initial_position;
13295 int i, j, fenMode, pn;
13297 if (gameMode == Training )
13298 SetTrainingModeOff();
13300 if (gameMode != BeginningOfGame) {
13301 Reset(FALSE, TRUE);
13303 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13304 fclose(lastLoadPositionFP);
13306 if (positionNumber == 0) positionNumber = 1;
13307 lastLoadPositionFP = f;
13308 lastLoadPositionNumber = positionNumber;
13309 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13310 if (first.pr == NoProc && !appData.noChessProgram) {
13311 StartChessProgram(&first);
13312 InitChessProgram(&first, FALSE);
13314 pn = positionNumber;
13315 if (positionNumber < 0) {
13316 /* Negative position number means to seek to that byte offset */
13317 if (fseek(f, -positionNumber, 0) == -1) {
13318 DisplayError(_("Can't seek on position file"), 0);
13323 if (fseek(f, 0, 0) == -1) {
13324 if (f == lastLoadPositionFP ?
13325 positionNumber == lastLoadPositionNumber + 1 :
13326 positionNumber == 1) {
13329 DisplayError(_("Can't seek on position file"), 0);
13334 /* See if this file is FEN or old-style xboard */
13335 if (fgets(line, MSG_SIZ, f) == NULL) {
13336 DisplayError(_("Position not found in file"), 0);
13339 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13340 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13343 if (fenMode || line[0] == '#') pn--;
13345 /* skip positions before number pn */
13346 if (fgets(line, MSG_SIZ, f) == NULL) {
13348 DisplayError(_("Position not found in file"), 0);
13351 if (fenMode || line[0] == '#') pn--;
13357 if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13358 DisplayError(_("Bad FEN position in file"), 0);
13361 if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13362 sscanf(p+3, "%s", bestMove);
13363 } else *bestMove = NULLCHAR;
13365 (void) fgets(line, MSG_SIZ, f);
13366 (void) fgets(line, MSG_SIZ, f);
13368 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13369 (void) fgets(line, MSG_SIZ, f);
13370 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13373 initial_position[i][j++] = CharToPiece(*p);
13377 blackPlaysFirst = FALSE;
13379 (void) fgets(line, MSG_SIZ, f);
13380 if (strncmp(line, "black", strlen("black"))==0)
13381 blackPlaysFirst = TRUE;
13384 startedFromSetupPosition = TRUE;
13386 CopyBoard(boards[0], initial_position);
13387 if (blackPlaysFirst) {
13388 currentMove = forwardMostMove = backwardMostMove = 1;
13389 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13390 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13391 CopyBoard(boards[1], initial_position);
13392 DisplayMessage("", _("Black to play"));
13394 currentMove = forwardMostMove = backwardMostMove = 0;
13395 DisplayMessage("", _("White to play"));
13397 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13398 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13399 SendToProgram("force\n", &first);
13400 SendBoard(&first, forwardMostMove);
13402 if (appData.debugMode) {
13404 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13405 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13406 fprintf(debugFP, "Load Position\n");
13409 if (positionNumber > 1) {
13410 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13411 DisplayTitle(line);
13413 DisplayTitle(title);
13415 gameMode = EditGame;
13418 timeRemaining[0][1] = whiteTimeRemaining;
13419 timeRemaining[1][1] = blackTimeRemaining;
13420 DrawPosition(FALSE, boards[currentMove]);
13427 CopyPlayerNameIntoFileName (char **dest, char *src)
13429 while (*src != NULLCHAR && *src != ',') {
13434 *(*dest)++ = *src++;
13440 DefaultFileName (char *ext)
13442 static char def[MSG_SIZ];
13445 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13447 CopyPlayerNameIntoFileName(&p, gameInfo.white);
13449 CopyPlayerNameIntoFileName(&p, gameInfo.black);
13451 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13458 /* Save the current game to the given file */
13460 SaveGameToFile (char *filename, int append)
13464 int result, i, t,tot=0;
13466 if (strcmp(filename, "-") == 0) {
13467 return SaveGame(stdout, 0, NULL);
13469 for(i=0; i<10; i++) { // upto 10 tries
13470 f = fopen(filename, append ? "a" : "w");
13471 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13472 if(f || errno != 13) break;
13473 DoSleep(t = 5 + random()%11); // wait 5-15 msec
13477 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13478 DisplayError(buf, errno);
13481 safeStrCpy(buf, lastMsg, MSG_SIZ);
13482 DisplayMessage(_("Waiting for access to save file"), "");
13483 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13484 DisplayMessage(_("Saving game"), "");
13485 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
13486 result = SaveGame(f, 0, NULL);
13487 DisplayMessage(buf, "");
13494 SavePart (char *str)
13496 static char buf[MSG_SIZ];
13499 p = strchr(str, ' ');
13500 if (p == NULL) return str;
13501 strncpy(buf, str, p - str);
13502 buf[p - str] = NULLCHAR;
13506 #define PGN_MAX_LINE 75
13508 #define PGN_SIDE_WHITE 0
13509 #define PGN_SIDE_BLACK 1
13512 FindFirstMoveOutOfBook (int side)
13516 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13517 int index = backwardMostMove;
13518 int has_book_hit = 0;
13520 if( (index % 2) != side ) {
13524 while( index < forwardMostMove ) {
13525 /* Check to see if engine is in book */
13526 int depth = pvInfoList[index].depth;
13527 int score = pvInfoList[index].score;
13533 else if( score == 0 && depth == 63 ) {
13534 in_book = 1; /* Zappa */
13536 else if( score == 2 && depth == 99 ) {
13537 in_book = 1; /* Abrok */
13540 has_book_hit += in_book;
13556 GetOutOfBookInfo (char * buf)
13560 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13562 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13563 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13567 if( oob[0] >= 0 || oob[1] >= 0 ) {
13568 for( i=0; i<2; i++ ) {
13572 if( i > 0 && oob[0] >= 0 ) {
13573 strcat( buf, " " );
13576 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13577 sprintf( buf+strlen(buf), "%s%.2f",
13578 pvInfoList[idx].score >= 0 ? "+" : "",
13579 pvInfoList[idx].score / 100.0 );
13585 /* Save game in PGN style */
13587 SaveGamePGN2 (FILE *f)
13589 int i, offset, linelen, newblock;
13592 int movelen, numlen, blank;
13593 char move_buffer[100]; /* [AS] Buffer for move+PV info */
13595 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13597 PrintPGNTags(f, &gameInfo);
13599 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13601 if (backwardMostMove > 0 || startedFromSetupPosition) {
13602 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13603 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13604 fprintf(f, "\n{--------------\n");
13605 PrintPosition(f, backwardMostMove);
13606 fprintf(f, "--------------}\n");
13610 /* [AS] Out of book annotation */
13611 if( appData.saveOutOfBookInfo ) {
13614 GetOutOfBookInfo( buf );
13616 if( buf[0] != '\0' ) {
13617 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13624 i = backwardMostMove;
13628 while (i < forwardMostMove) {
13629 /* Print comments preceding this move */
13630 if (commentList[i] != NULL) {
13631 if (linelen > 0) fprintf(f, "\n");
13632 fprintf(f, "%s", commentList[i]);
13637 /* Format move number */
13639 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13642 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13644 numtext[0] = NULLCHAR;
13646 numlen = strlen(numtext);
13649 /* Print move number */
13650 blank = linelen > 0 && numlen > 0;
13651 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13660 fprintf(f, "%s", numtext);
13664 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13665 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13668 blank = linelen > 0 && movelen > 0;
13669 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13678 fprintf(f, "%s", move_buffer);
13679 linelen += movelen;
13681 /* [AS] Add PV info if present */
13682 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13683 /* [HGM] add time */
13684 char buf[MSG_SIZ]; int seconds;
13686 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13692 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13695 seconds = (seconds + 4)/10; // round to full seconds
13697 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13699 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13702 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13703 pvInfoList[i].score >= 0 ? "+" : "",
13704 pvInfoList[i].score / 100.0,
13705 pvInfoList[i].depth,
13708 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13710 /* Print score/depth */
13711 blank = linelen > 0 && movelen > 0;
13712 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13721 fprintf(f, "%s", move_buffer);
13722 linelen += movelen;
13728 /* Start a new line */
13729 if (linelen > 0) fprintf(f, "\n");
13731 /* Print comments after last move */
13732 if (commentList[i] != NULL) {
13733 fprintf(f, "%s\n", commentList[i]);
13737 if (gameInfo.resultDetails != NULL &&
13738 gameInfo.resultDetails[0] != NULLCHAR) {
13739 char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13740 if(gameInfo.result == GameUnfinished && appData.clockMode &&
13741 (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13742 snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13743 fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13745 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13749 /* Save game in PGN style and close the file */
13751 SaveGamePGN (FILE *f)
13755 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13759 /* Save game in old style and close the file */
13761 SaveGameOldStyle (FILE *f)
13766 tm = time((time_t *) NULL);
13768 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13771 if (backwardMostMove > 0 || startedFromSetupPosition) {
13772 fprintf(f, "\n[--------------\n");
13773 PrintPosition(f, backwardMostMove);
13774 fprintf(f, "--------------]\n");
13779 i = backwardMostMove;
13780 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13782 while (i < forwardMostMove) {
13783 if (commentList[i] != NULL) {
13784 fprintf(f, "[%s]\n", commentList[i]);
13787 if ((i % 2) == 1) {
13788 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
13791 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
13793 if (commentList[i] != NULL) {
13797 if (i >= forwardMostMove) {
13801 fprintf(f, "%s\n", parseList[i]);
13806 if (commentList[i] != NULL) {
13807 fprintf(f, "[%s]\n", commentList[i]);
13810 /* This isn't really the old style, but it's close enough */
13811 if (gameInfo.resultDetails != NULL &&
13812 gameInfo.resultDetails[0] != NULLCHAR) {
13813 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13814 gameInfo.resultDetails);
13816 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13823 /* Save the current game to open file f and close the file */
13825 SaveGame (FILE *f, int dummy, char *dummy2)
13827 if (gameMode == EditPosition) EditPositionDone(TRUE);
13828 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13829 if (appData.oldSaveStyle)
13830 return SaveGameOldStyle(f);
13832 return SaveGamePGN(f);
13835 /* Save the current position to the given file */
13837 SavePositionToFile (char *filename)
13842 if (strcmp(filename, "-") == 0) {
13843 return SavePosition(stdout, 0, NULL);
13845 f = fopen(filename, "a");
13847 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13848 DisplayError(buf, errno);
13851 safeStrCpy(buf, lastMsg, MSG_SIZ);
13852 DisplayMessage(_("Waiting for access to save file"), "");
13853 flock(fileno(f), LOCK_EX); // [HGM] lock
13854 DisplayMessage(_("Saving position"), "");
13855 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
13856 SavePosition(f, 0, NULL);
13857 DisplayMessage(buf, "");
13863 /* Save the current position to the given open file and close the file */
13865 SavePosition (FILE *f, int dummy, char *dummy2)
13870 if (gameMode == EditPosition) EditPositionDone(TRUE);
13871 if (appData.oldSaveStyle) {
13872 tm = time((time_t *) NULL);
13874 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13876 fprintf(f, "[--------------\n");
13877 PrintPosition(f, currentMove);
13878 fprintf(f, "--------------]\n");
13880 fen = PositionToFEN(currentMove, NULL, 1);
13881 fprintf(f, "%s\n", fen);
13889 ReloadCmailMsgEvent (int unregister)
13892 static char *inFilename = NULL;
13893 static char *outFilename;
13895 struct stat inbuf, outbuf;
13898 /* Any registered moves are unregistered if unregister is set, */
13899 /* i.e. invoked by the signal handler */
13901 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13902 cmailMoveRegistered[i] = FALSE;
13903 if (cmailCommentList[i] != NULL) {
13904 free(cmailCommentList[i]);
13905 cmailCommentList[i] = NULL;
13908 nCmailMovesRegistered = 0;
13911 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13912 cmailResult[i] = CMAIL_NOT_RESULT;
13916 if (inFilename == NULL) {
13917 /* Because the filenames are static they only get malloced once */
13918 /* and they never get freed */
13919 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13920 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13922 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13923 sprintf(outFilename, "%s.out", appData.cmailGameName);
13926 status = stat(outFilename, &outbuf);
13928 cmailMailedMove = FALSE;
13930 status = stat(inFilename, &inbuf);
13931 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13934 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13935 counts the games, notes how each one terminated, etc.
13937 It would be nice to remove this kludge and instead gather all
13938 the information while building the game list. (And to keep it
13939 in the game list nodes instead of having a bunch of fixed-size
13940 parallel arrays.) Note this will require getting each game's
13941 termination from the PGN tags, as the game list builder does
13942 not process the game moves. --mann
13944 cmailMsgLoaded = TRUE;
13945 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13947 /* Load first game in the file or popup game menu */
13948 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13950 #endif /* !WIN32 */
13958 char string[MSG_SIZ];
13960 if ( cmailMailedMove
13961 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13962 return TRUE; /* Allow free viewing */
13965 /* Unregister move to ensure that we don't leave RegisterMove */
13966 /* with the move registered when the conditions for registering no */
13968 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13969 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13970 nCmailMovesRegistered --;
13972 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13974 free(cmailCommentList[lastLoadGameNumber - 1]);
13975 cmailCommentList[lastLoadGameNumber - 1] = NULL;
13979 if (cmailOldMove == -1) {
13980 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13984 if (currentMove > cmailOldMove + 1) {
13985 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13989 if (currentMove < cmailOldMove) {
13990 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13994 if (forwardMostMove > currentMove) {
13995 /* Silently truncate extra moves */
13999 if ( (currentMove == cmailOldMove + 1)
14000 || ( (currentMove == cmailOldMove)
14001 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14002 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14003 if (gameInfo.result != GameUnfinished) {
14004 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14007 if (commentList[currentMove] != NULL) {
14008 cmailCommentList[lastLoadGameNumber - 1]
14009 = StrSave(commentList[currentMove]);
14011 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14013 if (appData.debugMode)
14014 fprintf(debugFP, "Saving %s for game %d\n",
14015 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14017 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14019 f = fopen(string, "w");
14020 if (appData.oldSaveStyle) {
14021 SaveGameOldStyle(f); /* also closes the file */
14023 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14024 f = fopen(string, "w");
14025 SavePosition(f, 0, NULL); /* also closes the file */
14027 fprintf(f, "{--------------\n");
14028 PrintPosition(f, currentMove);
14029 fprintf(f, "--------------}\n\n");
14031 SaveGame(f, 0, NULL); /* also closes the file*/
14034 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14035 nCmailMovesRegistered ++;
14036 } else if (nCmailGames == 1) {
14037 DisplayError(_("You have not made a move yet"), 0);
14048 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14049 FILE *commandOutput;
14050 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14051 int nBytes = 0; /* Suppress warnings on uninitialized variables */
14057 if (! cmailMsgLoaded) {
14058 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14062 if (nCmailGames == nCmailResults) {
14063 DisplayError(_("No unfinished games"), 0);
14067 #if CMAIL_PROHIBIT_REMAIL
14068 if (cmailMailedMove) {
14069 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);
14070 DisplayError(msg, 0);
14075 if (! (cmailMailedMove || RegisterMove())) return;
14077 if ( cmailMailedMove
14078 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14079 snprintf(string, MSG_SIZ, partCommandString,
14080 appData.debugMode ? " -v" : "", appData.cmailGameName);
14081 commandOutput = popen(string, "r");
14083 if (commandOutput == NULL) {
14084 DisplayError(_("Failed to invoke cmail"), 0);
14086 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14087 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14089 if (nBuffers > 1) {
14090 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14091 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14092 nBytes = MSG_SIZ - 1;
14094 (void) memcpy(msg, buffer, nBytes);
14096 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14098 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14099 cmailMailedMove = TRUE; /* Prevent >1 moves */
14102 for (i = 0; i < nCmailGames; i ++) {
14103 if (cmailResult[i] == CMAIL_NOT_RESULT) {
14108 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14110 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14112 appData.cmailGameName,
14114 LoadGameFromFile(buffer, 1, buffer, FALSE);
14115 cmailMsgLoaded = FALSE;
14119 DisplayInformation(msg);
14120 pclose(commandOutput);
14123 if ((*cmailMsg) != '\0') {
14124 DisplayInformation(cmailMsg);
14129 #endif /* !WIN32 */
14138 int prependComma = 0;
14140 char string[MSG_SIZ]; /* Space for game-list */
14143 if (!cmailMsgLoaded) return "";
14145 if (cmailMailedMove) {
14146 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14148 /* Create a list of games left */
14149 snprintf(string, MSG_SIZ, "[");
14150 for (i = 0; i < nCmailGames; i ++) {
14151 if (! ( cmailMoveRegistered[i]
14152 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14153 if (prependComma) {
14154 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14156 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14160 strcat(string, number);
14163 strcat(string, "]");
14165 if (nCmailMovesRegistered + nCmailResults == 0) {
14166 switch (nCmailGames) {
14168 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14172 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14176 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14181 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14183 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14188 if (nCmailResults == nCmailGames) {
14189 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14191 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14196 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14208 if (gameMode == Training)
14209 SetTrainingModeOff();
14212 cmailMsgLoaded = FALSE;
14213 if (appData.icsActive) {
14214 SendToICS(ics_prefix);
14215 SendToICS("refresh\n");
14220 ExitEvent (int status)
14224 /* Give up on clean exit */
14228 /* Keep trying for clean exit */
14232 if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14233 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14235 if (telnetISR != NULL) {
14236 RemoveInputSource(telnetISR);
14238 if (icsPR != NoProc) {
14239 DestroyChildProcess(icsPR, TRUE);
14242 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14243 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14245 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14246 /* make sure this other one finishes before killing it! */
14247 if(endingGame) { int count = 0;
14248 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14249 while(endingGame && count++ < 10) DoSleep(1);
14250 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14253 /* Kill off chess programs */
14254 if (first.pr != NoProc) {
14257 DoSleep( appData.delayBeforeQuit );
14258 SendToProgram("quit\n", &first);
14259 DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14261 if (second.pr != NoProc) {
14262 DoSleep( appData.delayBeforeQuit );
14263 SendToProgram("quit\n", &second);
14264 DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14266 if (first.isr != NULL) {
14267 RemoveInputSource(first.isr);
14269 if (second.isr != NULL) {
14270 RemoveInputSource(second.isr);
14273 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14274 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14276 ShutDownFrontEnd();
14281 PauseEngine (ChessProgramState *cps)
14283 SendToProgram("pause\n", cps);
14288 UnPauseEngine (ChessProgramState *cps)
14290 SendToProgram("resume\n", cps);
14297 if (appData.debugMode)
14298 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14302 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14304 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14305 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14306 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14308 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14309 HandleMachineMove(stashedInputMove, stalledEngine);
14310 stalledEngine = NULL;
14313 if (gameMode == MachinePlaysWhite ||
14314 gameMode == TwoMachinesPlay ||
14315 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14316 if(first.pause) UnPauseEngine(&first);
14317 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14318 if(second.pause) UnPauseEngine(&second);
14319 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14322 DisplayBothClocks();
14324 if (gameMode == PlayFromGameFile) {
14325 if (appData.timeDelay >= 0)
14326 AutoPlayGameLoop();
14327 } else if (gameMode == IcsExamining && pauseExamInvalid) {
14328 Reset(FALSE, TRUE);
14329 SendToICS(ics_prefix);
14330 SendToICS("refresh\n");
14331 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14332 ForwardInner(forwardMostMove);
14334 pauseExamInvalid = FALSE;
14336 switch (gameMode) {
14340 pauseExamForwardMostMove = forwardMostMove;
14341 pauseExamInvalid = FALSE;
14344 case IcsPlayingWhite:
14345 case IcsPlayingBlack:
14349 case PlayFromGameFile:
14350 (void) StopLoadGameTimer();
14354 case BeginningOfGame:
14355 if (appData.icsActive) return;
14356 /* else fall through */
14357 case MachinePlaysWhite:
14358 case MachinePlaysBlack:
14359 case TwoMachinesPlay:
14360 if (forwardMostMove == 0)
14361 return; /* don't pause if no one has moved */
14362 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14363 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14364 if(onMove->pause) { // thinking engine can be paused
14365 PauseEngine(onMove); // do it
14366 if(onMove->other->pause) // pondering opponent can always be paused immediately
14367 PauseEngine(onMove->other);
14369 SendToProgram("easy\n", onMove->other);
14371 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14372 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14374 PauseEngine(&first);
14376 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14377 } else { // human on move, pause pondering by either method
14379 PauseEngine(&first);
14380 else if(appData.ponderNextMove)
14381 SendToProgram("easy\n", &first);
14384 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14394 EditCommentEvent ()
14396 char title[MSG_SIZ];
14398 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14399 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14401 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14402 WhiteOnMove(currentMove - 1) ? " " : ".. ",
14403 parseList[currentMove - 1]);
14406 EditCommentPopUp(currentMove, title, commentList[currentMove]);
14413 char *tags = PGNTags(&gameInfo);
14415 EditTagsPopUp(tags, NULL);
14422 if(second.analyzing) {
14423 SendToProgram("exit\n", &second);
14424 second.analyzing = FALSE;
14426 if (second.pr == NoProc) StartChessProgram(&second);
14427 InitChessProgram(&second, FALSE);
14428 FeedMovesToProgram(&second, currentMove);
14430 SendToProgram("analyze\n", &second);
14431 second.analyzing = TRUE;
14435 /* Toggle ShowThinking */
14437 ToggleShowThinking()
14439 appData.showThinking = !appData.showThinking;
14440 ShowThinkingEvent();
14444 AnalyzeModeEvent ()
14448 if (!first.analysisSupport) {
14449 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14450 DisplayError(buf, 0);
14453 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14454 if (appData.icsActive) {
14455 if (gameMode != IcsObserving) {
14456 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14457 DisplayError(buf, 0);
14459 if (appData.icsEngineAnalyze) {
14460 if (appData.debugMode)
14461 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14467 /* if enable, user wants to disable icsEngineAnalyze */
14468 if (appData.icsEngineAnalyze) {
14473 appData.icsEngineAnalyze = TRUE;
14474 if (appData.debugMode)
14475 fprintf(debugFP, "ICS engine analyze starting... \n");
14478 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14479 if (appData.noChessProgram || gameMode == AnalyzeMode)
14482 if (gameMode != AnalyzeFile) {
14483 if (!appData.icsEngineAnalyze) {
14485 if (gameMode != EditGame) return 0;
14487 if (!appData.showThinking) ToggleShowThinking();
14488 ResurrectChessProgram();
14489 SendToProgram("analyze\n", &first);
14490 first.analyzing = TRUE;
14491 /*first.maybeThinking = TRUE;*/
14492 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14493 EngineOutputPopUp();
14495 if (!appData.icsEngineAnalyze) {
14496 gameMode = AnalyzeMode;
14497 ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14503 StartAnalysisClock();
14504 GetTimeMark(&lastNodeCountTime);
14510 AnalyzeFileEvent ()
14512 if (appData.noChessProgram || gameMode == AnalyzeFile)
14515 if (!first.analysisSupport) {
14517 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14518 DisplayError(buf, 0);
14522 if (gameMode != AnalyzeMode) {
14523 keepInfo = 1; // mere annotating should not alter PGN tags
14526 if (gameMode != EditGame) return;
14527 if (!appData.showThinking) ToggleShowThinking();
14528 ResurrectChessProgram();
14529 SendToProgram("analyze\n", &first);
14530 first.analyzing = TRUE;
14531 /*first.maybeThinking = TRUE;*/
14532 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14533 EngineOutputPopUp();
14535 gameMode = AnalyzeFile;
14539 StartAnalysisClock();
14540 GetTimeMark(&lastNodeCountTime);
14542 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14543 AnalysisPeriodicEvent(1);
14547 MachineWhiteEvent ()
14550 char *bookHit = NULL;
14552 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14556 if (gameMode == PlayFromGameFile ||
14557 gameMode == TwoMachinesPlay ||
14558 gameMode == Training ||
14559 gameMode == AnalyzeMode ||
14560 gameMode == EndOfGame)
14563 if (gameMode == EditPosition)
14564 EditPositionDone(TRUE);
14566 if (!WhiteOnMove(currentMove)) {
14567 DisplayError(_("It is not White's turn"), 0);
14571 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14574 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14575 gameMode == AnalyzeFile)
14578 ResurrectChessProgram(); /* in case it isn't running */
14579 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14580 gameMode = MachinePlaysWhite;
14583 gameMode = MachinePlaysWhite;
14587 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14589 if (first.sendName) {
14590 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14591 SendToProgram(buf, &first);
14593 if (first.sendTime) {
14594 if (first.useColors) {
14595 SendToProgram("black\n", &first); /*gnu kludge*/
14597 SendTimeRemaining(&first, TRUE);
14599 if (first.useColors) {
14600 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14602 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14603 SetMachineThinkingEnables();
14604 first.maybeThinking = TRUE;
14608 if (appData.autoFlipView && !flipView) {
14609 flipView = !flipView;
14610 DrawPosition(FALSE, NULL);
14611 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14614 if(bookHit) { // [HGM] book: simulate book reply
14615 static char bookMove[MSG_SIZ]; // a bit generous?
14617 programStats.nodes = programStats.depth = programStats.time =
14618 programStats.score = programStats.got_only_move = 0;
14619 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14621 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14622 strcat(bookMove, bookHit);
14623 HandleMachineMove(bookMove, &first);
14628 MachineBlackEvent ()
14631 char *bookHit = NULL;
14633 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14637 if (gameMode == PlayFromGameFile ||
14638 gameMode == TwoMachinesPlay ||
14639 gameMode == Training ||
14640 gameMode == AnalyzeMode ||
14641 gameMode == EndOfGame)
14644 if (gameMode == EditPosition)
14645 EditPositionDone(TRUE);
14647 if (WhiteOnMove(currentMove)) {
14648 DisplayError(_("It is not Black's turn"), 0);
14652 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14655 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14656 gameMode == AnalyzeFile)
14659 ResurrectChessProgram(); /* in case it isn't running */
14660 gameMode = MachinePlaysBlack;
14664 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14666 if (first.sendName) {
14667 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14668 SendToProgram(buf, &first);
14670 if (first.sendTime) {
14671 if (first.useColors) {
14672 SendToProgram("white\n", &first); /*gnu kludge*/
14674 SendTimeRemaining(&first, FALSE);
14676 if (first.useColors) {
14677 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14679 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14680 SetMachineThinkingEnables();
14681 first.maybeThinking = TRUE;
14684 if (appData.autoFlipView && flipView) {
14685 flipView = !flipView;
14686 DrawPosition(FALSE, NULL);
14687 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14689 if(bookHit) { // [HGM] book: simulate book reply
14690 static char bookMove[MSG_SIZ]; // a bit generous?
14692 programStats.nodes = programStats.depth = programStats.time =
14693 programStats.score = programStats.got_only_move = 0;
14694 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14696 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14697 strcat(bookMove, bookHit);
14698 HandleMachineMove(bookMove, &first);
14704 DisplayTwoMachinesTitle ()
14707 if (appData.matchGames > 0) {
14708 if(appData.tourneyFile[0]) {
14709 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14710 gameInfo.white, _("vs."), gameInfo.black,
14711 nextGame+1, appData.matchGames+1,
14712 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14714 if (first.twoMachinesColor[0] == 'w') {
14715 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14716 gameInfo.white, _("vs."), gameInfo.black,
14717 first.matchWins, second.matchWins,
14718 matchGame - 1 - (first.matchWins + second.matchWins));
14720 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14721 gameInfo.white, _("vs."), gameInfo.black,
14722 second.matchWins, first.matchWins,
14723 matchGame - 1 - (first.matchWins + second.matchWins));
14726 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14732 SettingsMenuIfReady ()
14734 if (second.lastPing != second.lastPong) {
14735 DisplayMessage("", _("Waiting for second chess program"));
14736 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14740 DisplayMessage("", "");
14741 SettingsPopUp(&second);
14745 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14748 if (cps->pr == NoProc) {
14749 StartChessProgram(cps);
14750 if (cps->protocolVersion == 1) {
14752 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14754 /* kludge: allow timeout for initial "feature" command */
14755 if(retry != TwoMachinesEventIfReady) FreezeUI();
14756 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14757 DisplayMessage("", buf);
14758 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14766 TwoMachinesEvent P((void))
14770 ChessProgramState *onmove;
14771 char *bookHit = NULL;
14772 static int stalling = 0;
14776 if (appData.noChessProgram) return;
14778 switch (gameMode) {
14779 case TwoMachinesPlay:
14781 case MachinePlaysWhite:
14782 case MachinePlaysBlack:
14783 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14784 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14788 case BeginningOfGame:
14789 case PlayFromGameFile:
14792 if (gameMode != EditGame) return;
14795 EditPositionDone(TRUE);
14806 // forwardMostMove = currentMove;
14807 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14808 startingEngine = TRUE;
14810 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14812 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14813 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14814 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14817 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14819 if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14820 gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14821 startingEngine = matchMode = FALSE;
14822 DisplayError("second engine does not play this", 0);
14823 gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14824 EditGameEvent(); // switch back to EditGame mode
14829 InitChessProgram(&second, FALSE); // unbalances ping of second engine
14830 SendToProgram("force\n", &second);
14832 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14835 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14836 if(appData.matchPause>10000 || appData.matchPause<10)
14837 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14838 wait = SubtractTimeMarks(&now, &pauseStart);
14839 if(wait < appData.matchPause) {
14840 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14843 // we are now committed to starting the game
14845 DisplayMessage("", "");
14846 if (startedFromSetupPosition) {
14847 SendBoard(&second, backwardMostMove);
14848 if (appData.debugMode) {
14849 fprintf(debugFP, "Two Machines\n");
14852 for (i = backwardMostMove; i < forwardMostMove; i++) {
14853 SendMoveToProgram(i, &second);
14856 gameMode = TwoMachinesPlay;
14857 pausing = startingEngine = FALSE;
14858 ModeHighlight(); // [HGM] logo: this triggers display update of logos
14860 DisplayTwoMachinesTitle();
14862 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14867 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14868 SendToProgram(first.computerString, &first);
14869 if (first.sendName) {
14870 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14871 SendToProgram(buf, &first);
14873 SendToProgram(second.computerString, &second);
14874 if (second.sendName) {
14875 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14876 SendToProgram(buf, &second);
14880 if (!first.sendTime || !second.sendTime) {
14881 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14882 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14884 if (onmove->sendTime) {
14885 if (onmove->useColors) {
14886 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14888 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14890 if (onmove->useColors) {
14891 SendToProgram(onmove->twoMachinesColor, onmove);
14893 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14894 // SendToProgram("go\n", onmove);
14895 onmove->maybeThinking = TRUE;
14896 SetMachineThinkingEnables();
14900 if(bookHit) { // [HGM] book: simulate book reply
14901 static char bookMove[MSG_SIZ]; // a bit generous?
14903 programStats.nodes = programStats.depth = programStats.time =
14904 programStats.score = programStats.got_only_move = 0;
14905 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14907 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14908 strcat(bookMove, bookHit);
14909 savedMessage = bookMove; // args for deferred call
14910 savedState = onmove;
14911 ScheduleDelayedEvent(DeferredBookMove, 1);
14918 if (gameMode == Training) {
14919 SetTrainingModeOff();
14920 gameMode = PlayFromGameFile;
14921 DisplayMessage("", _("Training mode off"));
14923 gameMode = Training;
14924 animateTraining = appData.animate;
14926 /* make sure we are not already at the end of the game */
14927 if (currentMove < forwardMostMove) {
14928 SetTrainingModeOn();
14929 DisplayMessage("", _("Training mode on"));
14931 gameMode = PlayFromGameFile;
14932 DisplayError(_("Already at end of game"), 0);
14941 if (!appData.icsActive) return;
14942 switch (gameMode) {
14943 case IcsPlayingWhite:
14944 case IcsPlayingBlack:
14947 case BeginningOfGame:
14955 EditPositionDone(TRUE);
14968 gameMode = IcsIdle;
14978 switch (gameMode) {
14980 SetTrainingModeOff();
14982 case MachinePlaysWhite:
14983 case MachinePlaysBlack:
14984 case BeginningOfGame:
14985 SendToProgram("force\n", &first);
14986 if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
14987 if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
14989 abortEngineThink = TRUE;
14990 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
14991 SendToProgram(buf, &first);
14992 DisplayMessage("Aborting engine think", "");
14996 SetUserThinkingEnables();
14998 case PlayFromGameFile:
14999 (void) StopLoadGameTimer();
15000 if (gameFileFP != NULL) {
15005 EditPositionDone(TRUE);
15010 SendToProgram("force\n", &first);
15012 case TwoMachinesPlay:
15013 GameEnds(EndOfFile, NULL, GE_PLAYER);
15014 ResurrectChessProgram();
15015 SetUserThinkingEnables();
15018 ResurrectChessProgram();
15020 case IcsPlayingBlack:
15021 case IcsPlayingWhite:
15022 DisplayError(_("Warning: You are still playing a game"), 0);
15025 DisplayError(_("Warning: You are still observing a game"), 0);
15028 DisplayError(_("Warning: You are still examining a game"), 0);
15039 first.offeredDraw = second.offeredDraw = 0;
15041 if (gameMode == PlayFromGameFile) {
15042 whiteTimeRemaining = timeRemaining[0][currentMove];
15043 blackTimeRemaining = timeRemaining[1][currentMove];
15047 if (gameMode == MachinePlaysWhite ||
15048 gameMode == MachinePlaysBlack ||
15049 gameMode == TwoMachinesPlay ||
15050 gameMode == EndOfGame) {
15051 i = forwardMostMove;
15052 while (i > currentMove) {
15053 SendToProgram("undo\n", &first);
15056 if(!adjustedClock) {
15057 whiteTimeRemaining = timeRemaining[0][currentMove];
15058 blackTimeRemaining = timeRemaining[1][currentMove];
15059 DisplayBothClocks();
15061 if (whiteFlag || blackFlag) {
15062 whiteFlag = blackFlag = 0;
15067 gameMode = EditGame;
15074 EditPositionEvent ()
15076 if (gameMode == EditPosition) {
15082 if (gameMode != EditGame) return;
15084 gameMode = EditPosition;
15087 if (currentMove > 0)
15088 CopyBoard(boards[0], boards[currentMove]);
15090 blackPlaysFirst = !WhiteOnMove(currentMove);
15092 currentMove = forwardMostMove = backwardMostMove = 0;
15093 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15095 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15101 /* [DM] icsEngineAnalyze - possible call from other functions */
15102 if (appData.icsEngineAnalyze) {
15103 appData.icsEngineAnalyze = FALSE;
15105 DisplayMessage("",_("Close ICS engine analyze..."));
15107 if (first.analysisSupport && first.analyzing) {
15108 SendToBoth("exit\n");
15109 first.analyzing = second.analyzing = FALSE;
15111 thinkOutput[0] = NULLCHAR;
15115 EditPositionDone (Boolean fakeRights)
15117 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15119 startedFromSetupPosition = TRUE;
15120 InitChessProgram(&first, FALSE);
15121 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15122 boards[0][EP_STATUS] = EP_NONE;
15123 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15124 if(boards[0][0][BOARD_WIDTH>>1] == king) {
15125 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15126 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15127 } else boards[0][CASTLING][2] = NoRights;
15128 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15129 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15130 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15131 } else boards[0][CASTLING][5] = NoRights;
15132 if(gameInfo.variant == VariantSChess) {
15134 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15135 boards[0][VIRGIN][i] = 0;
15136 if(boards[0][0][i] == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15137 if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15141 SendToProgram("force\n", &first);
15142 if (blackPlaysFirst) {
15143 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15144 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15145 currentMove = forwardMostMove = backwardMostMove = 1;
15146 CopyBoard(boards[1], boards[0]);
15148 currentMove = forwardMostMove = backwardMostMove = 0;
15150 SendBoard(&first, forwardMostMove);
15151 if (appData.debugMode) {
15152 fprintf(debugFP, "EditPosDone\n");
15155 DisplayMessage("", "");
15156 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15157 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15158 gameMode = EditGame;
15160 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15161 ClearHighlights(); /* [AS] */
15164 /* Pause for `ms' milliseconds */
15165 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15167 TimeDelay (long ms)
15174 } while (SubtractTimeMarks(&m2, &m1) < ms);
15177 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15179 SendMultiLineToICS (char *buf)
15181 char temp[MSG_SIZ+1], *p;
15188 strncpy(temp, buf, len);
15193 if (*p == '\n' || *p == '\r')
15198 strcat(temp, "\n");
15200 SendToPlayer(temp, strlen(temp));
15204 SetWhiteToPlayEvent ()
15206 if (gameMode == EditPosition) {
15207 blackPlaysFirst = FALSE;
15208 DisplayBothClocks(); /* works because currentMove is 0 */
15209 } else if (gameMode == IcsExamining) {
15210 SendToICS(ics_prefix);
15211 SendToICS("tomove white\n");
15216 SetBlackToPlayEvent ()
15218 if (gameMode == EditPosition) {
15219 blackPlaysFirst = TRUE;
15220 currentMove = 1; /* kludge */
15221 DisplayBothClocks();
15223 } else if (gameMode == IcsExamining) {
15224 SendToICS(ics_prefix);
15225 SendToICS("tomove black\n");
15230 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15233 ChessSquare piece = boards[0][y][x];
15234 static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15235 static int lastVariant;
15237 if (gameMode != EditPosition && gameMode != IcsExamining) return;
15239 switch (selection) {
15241 fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15242 MarkTargetSquares(1);
15243 CopyBoard(currentBoard, boards[0]);
15244 CopyBoard(menuBoard, initialPosition);
15245 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15246 SendToICS(ics_prefix);
15247 SendToICS("bsetup clear\n");
15248 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15249 SendToICS(ics_prefix);
15250 SendToICS("clearboard\n");
15253 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15254 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15255 for (y = 0; y < BOARD_HEIGHT; y++) {
15256 if (gameMode == IcsExamining) {
15257 if (boards[currentMove][y][x] != EmptySquare) {
15258 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15262 } else if(boards[0][y][x] != DarkSquare) {
15263 if(boards[0][y][x] != p) nonEmpty++;
15264 boards[0][y][x] = p;
15268 if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15270 for(r = 0; r < BOARD_HEIGHT; r++) {
15271 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
15272 ChessSquare p = menuBoard[r][x];
15273 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15276 DisplayMessage("Clicking clock again restores position", "");
15277 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15278 if(!nonEmpty) { // asked to clear an empty board
15279 CopyBoard(boards[0], menuBoard);
15281 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15282 CopyBoard(boards[0], initialPosition);
15284 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15285 && !CompareBoards(nullBoard, erasedBoard)) {
15286 CopyBoard(boards[0], erasedBoard);
15288 CopyBoard(erasedBoard, currentBoard);
15292 if (gameMode == EditPosition) {
15293 DrawPosition(FALSE, boards[0]);
15298 SetWhiteToPlayEvent();
15302 SetBlackToPlayEvent();
15306 if (gameMode == IcsExamining) {
15307 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15308 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15311 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15312 if(x == BOARD_LEFT-2) {
15313 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15314 boards[0][y][1] = 0;
15316 if(x == BOARD_RGHT+1) {
15317 if(y >= gameInfo.holdingsSize) break;
15318 boards[0][y][BOARD_WIDTH-2] = 0;
15321 boards[0][y][x] = EmptySquare;
15322 DrawPosition(FALSE, boards[0]);
15327 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15328 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
15329 selection = (ChessSquare) (PROMOTED piece);
15330 } else if(piece == EmptySquare) selection = WhiteSilver;
15331 else selection = (ChessSquare)((int)piece - 1);
15335 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15336 piece > (int)BlackMan && piece <= (int)BlackKing ) {
15337 selection = (ChessSquare) (DEMOTED piece);
15338 } else if(piece == EmptySquare) selection = BlackSilver;
15339 else selection = (ChessSquare)((int)piece + 1);
15344 if(gameInfo.variant == VariantShatranj ||
15345 gameInfo.variant == VariantXiangqi ||
15346 gameInfo.variant == VariantCourier ||
15347 gameInfo.variant == VariantASEAN ||
15348 gameInfo.variant == VariantMakruk )
15349 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15354 if(gameInfo.variant == VariantXiangqi)
15355 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15356 if(gameInfo.variant == VariantKnightmate)
15357 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15360 if (gameMode == IcsExamining) {
15361 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15362 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15363 PieceToChar(selection), AAA + x, ONE + y);
15366 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15368 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15369 n = PieceToNumber(selection - BlackPawn);
15370 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15371 boards[0][BOARD_HEIGHT-1-n][0] = selection;
15372 boards[0][BOARD_HEIGHT-1-n][1]++;
15374 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15375 n = PieceToNumber(selection);
15376 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15377 boards[0][n][BOARD_WIDTH-1] = selection;
15378 boards[0][n][BOARD_WIDTH-2]++;
15381 boards[0][y][x] = selection;
15382 DrawPosition(TRUE, boards[0]);
15384 fromX = fromY = -1;
15392 DropMenuEvent (ChessSquare selection, int x, int y)
15394 ChessMove moveType;
15396 switch (gameMode) {
15397 case IcsPlayingWhite:
15398 case MachinePlaysBlack:
15399 if (!WhiteOnMove(currentMove)) {
15400 DisplayMoveError(_("It is Black's turn"));
15403 moveType = WhiteDrop;
15405 case IcsPlayingBlack:
15406 case MachinePlaysWhite:
15407 if (WhiteOnMove(currentMove)) {
15408 DisplayMoveError(_("It is White's turn"));
15411 moveType = BlackDrop;
15414 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15420 if (moveType == BlackDrop && selection < BlackPawn) {
15421 selection = (ChessSquare) ((int) selection
15422 + (int) BlackPawn - (int) WhitePawn);
15424 if (boards[currentMove][y][x] != EmptySquare) {
15425 DisplayMoveError(_("That square is occupied"));
15429 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15435 /* Accept a pending offer of any kind from opponent */
15437 if (appData.icsActive) {
15438 SendToICS(ics_prefix);
15439 SendToICS("accept\n");
15440 } else if (cmailMsgLoaded) {
15441 if (currentMove == cmailOldMove &&
15442 commentList[cmailOldMove] != NULL &&
15443 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15444 "Black offers a draw" : "White offers a draw")) {
15446 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15447 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15449 DisplayError(_("There is no pending offer on this move"), 0);
15450 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15453 /* Not used for offers from chess program */
15460 /* Decline a pending offer of any kind from opponent */
15462 if (appData.icsActive) {
15463 SendToICS(ics_prefix);
15464 SendToICS("decline\n");
15465 } else if (cmailMsgLoaded) {
15466 if (currentMove == cmailOldMove &&
15467 commentList[cmailOldMove] != NULL &&
15468 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15469 "Black offers a draw" : "White offers a draw")) {
15471 AppendComment(cmailOldMove, "Draw declined", TRUE);
15472 DisplayComment(cmailOldMove - 1, "Draw declined");
15475 DisplayError(_("There is no pending offer on this move"), 0);
15478 /* Not used for offers from chess program */
15485 /* Issue ICS rematch command */
15486 if (appData.icsActive) {
15487 SendToICS(ics_prefix);
15488 SendToICS("rematch\n");
15495 /* Call your opponent's flag (claim a win on time) */
15496 if (appData.icsActive) {
15497 SendToICS(ics_prefix);
15498 SendToICS("flag\n");
15500 switch (gameMode) {
15503 case MachinePlaysWhite:
15506 GameEnds(GameIsDrawn, "Both players ran out of time",
15509 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15511 DisplayError(_("Your opponent is not out of time"), 0);
15514 case MachinePlaysBlack:
15517 GameEnds(GameIsDrawn, "Both players ran out of time",
15520 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15522 DisplayError(_("Your opponent is not out of time"), 0);
15530 ClockClick (int which)
15531 { // [HGM] code moved to back-end from winboard.c
15532 if(which) { // black clock
15533 if (gameMode == EditPosition || gameMode == IcsExamining) {
15534 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15535 SetBlackToPlayEvent();
15536 } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15537 gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15538 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15539 } else if (shiftKey) {
15540 AdjustClock(which, -1);
15541 } else if (gameMode == IcsPlayingWhite ||
15542 gameMode == MachinePlaysBlack) {
15545 } else { // white clock
15546 if (gameMode == EditPosition || gameMode == IcsExamining) {
15547 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15548 SetWhiteToPlayEvent();
15549 } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15550 gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15551 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15552 } else if (shiftKey) {
15553 AdjustClock(which, -1);
15554 } else if (gameMode == IcsPlayingBlack ||
15555 gameMode == MachinePlaysWhite) {
15564 /* Offer draw or accept pending draw offer from opponent */
15566 if (appData.icsActive) {
15567 /* Note: tournament rules require draw offers to be
15568 made after you make your move but before you punch
15569 your clock. Currently ICS doesn't let you do that;
15570 instead, you immediately punch your clock after making
15571 a move, but you can offer a draw at any time. */
15573 SendToICS(ics_prefix);
15574 SendToICS("draw\n");
15575 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15576 } else if (cmailMsgLoaded) {
15577 if (currentMove == cmailOldMove &&
15578 commentList[cmailOldMove] != NULL &&
15579 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15580 "Black offers a draw" : "White offers a draw")) {
15581 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15582 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15583 } else if (currentMove == cmailOldMove + 1) {
15584 char *offer = WhiteOnMove(cmailOldMove) ?
15585 "White offers a draw" : "Black offers a draw";
15586 AppendComment(currentMove, offer, TRUE);
15587 DisplayComment(currentMove - 1, offer);
15588 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15590 DisplayError(_("You must make your move before offering a draw"), 0);
15591 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15593 } else if (first.offeredDraw) {
15594 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15596 if (first.sendDrawOffers) {
15597 SendToProgram("draw\n", &first);
15598 userOfferedDraw = TRUE;
15606 /* Offer Adjourn or accept pending Adjourn offer from opponent */
15608 if (appData.icsActive) {
15609 SendToICS(ics_prefix);
15610 SendToICS("adjourn\n");
15612 /* Currently GNU Chess doesn't offer or accept Adjourns */
15620 /* Offer Abort or accept pending Abort offer from opponent */
15622 if (appData.icsActive) {
15623 SendToICS(ics_prefix);
15624 SendToICS("abort\n");
15626 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15633 /* Resign. You can do this even if it's not your turn. */
15635 if (appData.icsActive) {
15636 SendToICS(ics_prefix);
15637 SendToICS("resign\n");
15639 switch (gameMode) {
15640 case MachinePlaysWhite:
15641 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15643 case MachinePlaysBlack:
15644 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15647 if (cmailMsgLoaded) {
15649 if (WhiteOnMove(cmailOldMove)) {
15650 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15652 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15654 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15665 StopObservingEvent ()
15667 /* Stop observing current games */
15668 SendToICS(ics_prefix);
15669 SendToICS("unobserve\n");
15673 StopExaminingEvent ()
15675 /* Stop observing current game */
15676 SendToICS(ics_prefix);
15677 SendToICS("unexamine\n");
15681 ForwardInner (int target)
15683 int limit; int oldSeekGraphUp = seekGraphUp;
15685 if (appData.debugMode)
15686 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15687 target, currentMove, forwardMostMove);
15689 if (gameMode == EditPosition)
15692 seekGraphUp = FALSE;
15693 MarkTargetSquares(1);
15694 fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15696 if (gameMode == PlayFromGameFile && !pausing)
15699 if (gameMode == IcsExamining && pausing)
15700 limit = pauseExamForwardMostMove;
15702 limit = forwardMostMove;
15704 if (target > limit) target = limit;
15706 if (target > 0 && moveList[target - 1][0]) {
15707 int fromX, fromY, toX, toY;
15708 toX = moveList[target - 1][2] - AAA;
15709 toY = moveList[target - 1][3] - ONE;
15710 if (moveList[target - 1][1] == '@') {
15711 if (appData.highlightLastMove) {
15712 SetHighlights(-1, -1, toX, toY);
15715 int viaX = moveList[target - 1][5] - AAA;
15716 int viaY = moveList[target - 1][6] - ONE;
15717 fromX = moveList[target - 1][0] - AAA;
15718 fromY = moveList[target - 1][1] - ONE;
15719 if (target == currentMove + 1) {
15720 if(moveList[target - 1][4] == ';') { // multi-leg
15721 ChessSquare piece = boards[currentMove][viaY][viaX];
15722 AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15723 boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15724 AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15725 boards[currentMove][viaY][viaX] = piece;
15727 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15729 if (appData.highlightLastMove) {
15730 SetHighlights(fromX, fromY, toX, toY);
15734 if (gameMode == EditGame || gameMode == AnalyzeMode ||
15735 gameMode == Training || gameMode == PlayFromGameFile ||
15736 gameMode == AnalyzeFile) {
15737 while (currentMove < target) {
15738 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15739 SendMoveToProgram(currentMove++, &first);
15742 currentMove = target;
15745 if (gameMode == EditGame || gameMode == EndOfGame) {
15746 whiteTimeRemaining = timeRemaining[0][currentMove];
15747 blackTimeRemaining = timeRemaining[1][currentMove];
15749 DisplayBothClocks();
15750 DisplayMove(currentMove - 1);
15751 DrawPosition(oldSeekGraphUp, boards[currentMove]);
15752 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15753 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15754 DisplayComment(currentMove - 1, commentList[currentMove]);
15756 ClearMap(); // [HGM] exclude: invalidate map
15763 if (gameMode == IcsExamining && !pausing) {
15764 SendToICS(ics_prefix);
15765 SendToICS("forward\n");
15767 ForwardInner(currentMove + 1);
15774 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15775 /* to optimze, we temporarily turn off analysis mode while we feed
15776 * the remaining moves to the engine. Otherwise we get analysis output
15779 if (first.analysisSupport) {
15780 SendToProgram("exit\nforce\n", &first);
15781 first.analyzing = FALSE;
15785 if (gameMode == IcsExamining && !pausing) {
15786 SendToICS(ics_prefix);
15787 SendToICS("forward 999999\n");
15789 ForwardInner(forwardMostMove);
15792 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15793 /* we have fed all the moves, so reactivate analysis mode */
15794 SendToProgram("analyze\n", &first);
15795 first.analyzing = TRUE;
15796 /*first.maybeThinking = TRUE;*/
15797 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15802 BackwardInner (int target)
15804 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15806 if (appData.debugMode)
15807 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15808 target, currentMove, forwardMostMove);
15810 if (gameMode == EditPosition) return;
15811 seekGraphUp = FALSE;
15812 MarkTargetSquares(1);
15813 fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15814 if (currentMove <= backwardMostMove) {
15816 DrawPosition(full_redraw, boards[currentMove]);
15819 if (gameMode == PlayFromGameFile && !pausing)
15822 if (moveList[target][0]) {
15823 int fromX, fromY, toX, toY;
15824 toX = moveList[target][2] - AAA;
15825 toY = moveList[target][3] - ONE;
15826 if (moveList[target][1] == '@') {
15827 if (appData.highlightLastMove) {
15828 SetHighlights(-1, -1, toX, toY);
15831 fromX = moveList[target][0] - AAA;
15832 fromY = moveList[target][1] - ONE;
15833 if (target == currentMove - 1) {
15834 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15836 if (appData.highlightLastMove) {
15837 SetHighlights(fromX, fromY, toX, toY);
15841 if (gameMode == EditGame || gameMode==AnalyzeMode ||
15842 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15843 while (currentMove > target) {
15844 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15845 // null move cannot be undone. Reload program with move history before it.
15847 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15848 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15850 SendBoard(&first, i);
15851 if(second.analyzing) SendBoard(&second, i);
15852 for(currentMove=i; currentMove<target; currentMove++) {
15853 SendMoveToProgram(currentMove, &first);
15854 if(second.analyzing) SendMoveToProgram(currentMove, &second);
15858 SendToBoth("undo\n");
15862 currentMove = target;
15865 if (gameMode == EditGame || gameMode == EndOfGame) {
15866 whiteTimeRemaining = timeRemaining[0][currentMove];
15867 blackTimeRemaining = timeRemaining[1][currentMove];
15869 DisplayBothClocks();
15870 DisplayMove(currentMove - 1);
15871 DrawPosition(full_redraw, boards[currentMove]);
15872 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15873 // [HGM] PV info: routine tests if comment empty
15874 DisplayComment(currentMove - 1, commentList[currentMove]);
15875 ClearMap(); // [HGM] exclude: invalidate map
15881 if (gameMode == IcsExamining && !pausing) {
15882 SendToICS(ics_prefix);
15883 SendToICS("backward\n");
15885 BackwardInner(currentMove - 1);
15892 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15893 /* to optimize, we temporarily turn off analysis mode while we undo
15894 * all the moves. Otherwise we get analysis output after each undo.
15896 if (first.analysisSupport) {
15897 SendToProgram("exit\nforce\n", &first);
15898 first.analyzing = FALSE;
15902 if (gameMode == IcsExamining && !pausing) {
15903 SendToICS(ics_prefix);
15904 SendToICS("backward 999999\n");
15906 BackwardInner(backwardMostMove);
15909 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15910 /* we have fed all the moves, so reactivate analysis mode */
15911 SendToProgram("analyze\n", &first);
15912 first.analyzing = TRUE;
15913 /*first.maybeThinking = TRUE;*/
15914 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15921 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15922 if (to >= forwardMostMove) to = forwardMostMove;
15923 if (to <= backwardMostMove) to = backwardMostMove;
15924 if (to < currentMove) {
15932 RevertEvent (Boolean annotate)
15934 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15937 if (gameMode != IcsExamining) {
15938 DisplayError(_("You are not examining a game"), 0);
15942 DisplayError(_("You can't revert while pausing"), 0);
15945 SendToICS(ics_prefix);
15946 SendToICS("revert\n");
15950 RetractMoveEvent ()
15952 switch (gameMode) {
15953 case MachinePlaysWhite:
15954 case MachinePlaysBlack:
15955 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15956 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15959 if (forwardMostMove < 2) return;
15960 currentMove = forwardMostMove = forwardMostMove - 2;
15961 whiteTimeRemaining = timeRemaining[0][currentMove];
15962 blackTimeRemaining = timeRemaining[1][currentMove];
15963 DisplayBothClocks();
15964 DisplayMove(currentMove - 1);
15965 ClearHighlights();/*!! could figure this out*/
15966 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15967 SendToProgram("remove\n", &first);
15968 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15971 case BeginningOfGame:
15975 case IcsPlayingWhite:
15976 case IcsPlayingBlack:
15977 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15978 SendToICS(ics_prefix);
15979 SendToICS("takeback 2\n");
15981 SendToICS(ics_prefix);
15982 SendToICS("takeback 1\n");
15991 ChessProgramState *cps;
15993 switch (gameMode) {
15994 case MachinePlaysWhite:
15995 if (!WhiteOnMove(forwardMostMove)) {
15996 DisplayError(_("It is your turn"), 0);
16001 case MachinePlaysBlack:
16002 if (WhiteOnMove(forwardMostMove)) {
16003 DisplayError(_("It is your turn"), 0);
16008 case TwoMachinesPlay:
16009 if (WhiteOnMove(forwardMostMove) ==
16010 (first.twoMachinesColor[0] == 'w')) {
16016 case BeginningOfGame:
16020 SendToProgram("?\n", cps);
16024 TruncateGameEvent ()
16027 if (gameMode != EditGame) return;
16034 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16035 if (forwardMostMove > currentMove) {
16036 if (gameInfo.resultDetails != NULL) {
16037 free(gameInfo.resultDetails);
16038 gameInfo.resultDetails = NULL;
16039 gameInfo.result = GameUnfinished;
16041 forwardMostMove = currentMove;
16042 HistorySet(parseList, backwardMostMove, forwardMostMove,
16050 if (appData.noChessProgram) return;
16051 switch (gameMode) {
16052 case MachinePlaysWhite:
16053 if (WhiteOnMove(forwardMostMove)) {
16054 DisplayError(_("Wait until your turn."), 0);
16058 case BeginningOfGame:
16059 case MachinePlaysBlack:
16060 if (!WhiteOnMove(forwardMostMove)) {
16061 DisplayError(_("Wait until your turn."), 0);
16066 DisplayError(_("No hint available"), 0);
16069 SendToProgram("hint\n", &first);
16070 hintRequested = TRUE;
16074 SaveSelected (FILE *g, int dummy, char *dummy2)
16076 ListGame * lg = (ListGame *) gameList.head;
16080 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16081 DisplayError(_("Game list not loaded or empty"), 0);
16085 creatingBook = TRUE; // suppresses stuff during load game
16087 /* Get list size */
16088 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16089 if(lg->position >= 0) { // selected?
16090 LoadGame(f, nItem, "", TRUE);
16091 SaveGamePGN2(g); // leaves g open
16094 lg = (ListGame *) lg->node.succ;
16098 creatingBook = FALSE;
16106 ListGame * lg = (ListGame *) gameList.head;
16109 static int secondTime = FALSE;
16111 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16112 DisplayError(_("Game list not loaded or empty"), 0);
16116 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16119 DisplayNote(_("Book file exists! Try again for overwrite."));
16123 creatingBook = TRUE;
16124 secondTime = FALSE;
16126 /* Get list size */
16127 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16128 if(lg->position >= 0) {
16129 LoadGame(f, nItem, "", TRUE);
16130 AddGameToBook(TRUE);
16133 lg = (ListGame *) lg->node.succ;
16136 creatingBook = FALSE;
16143 if (appData.noChessProgram) return;
16144 switch (gameMode) {
16145 case MachinePlaysWhite:
16146 if (WhiteOnMove(forwardMostMove)) {
16147 DisplayError(_("Wait until your turn."), 0);
16151 case BeginningOfGame:
16152 case MachinePlaysBlack:
16153 if (!WhiteOnMove(forwardMostMove)) {
16154 DisplayError(_("Wait until your turn."), 0);
16159 EditPositionDone(TRUE);
16161 case TwoMachinesPlay:
16166 SendToProgram("bk\n", &first);
16167 bookOutput[0] = NULLCHAR;
16168 bookRequested = TRUE;
16174 char *tags = PGNTags(&gameInfo);
16175 TagsPopUp(tags, CmailMsg());
16179 /* end button procedures */
16182 PrintPosition (FILE *fp, int move)
16186 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16187 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16188 char c = PieceToChar(boards[move][i][j]);
16189 fputc(c == 'x' ? '.' : c, fp);
16190 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16193 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16194 fprintf(fp, "white to play\n");
16196 fprintf(fp, "black to play\n");
16200 PrintOpponents (FILE *fp)
16202 if (gameInfo.white != NULL) {
16203 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16209 /* Find last component of program's own name, using some heuristics */
16211 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16214 int local = (strcmp(host, "localhost") == 0);
16215 while (!local && (p = strchr(prog, ';')) != NULL) {
16217 while (*p == ' ') p++;
16220 if (*prog == '"' || *prog == '\'') {
16221 q = strchr(prog + 1, *prog);
16223 q = strchr(prog, ' ');
16225 if (q == NULL) q = prog + strlen(prog);
16227 while (p >= prog && *p != '/' && *p != '\\') p--;
16229 if(p == prog && *p == '"') p++;
16231 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16232 memcpy(buf, p, q - p);
16233 buf[q - p] = NULLCHAR;
16241 TimeControlTagValue ()
16244 if (!appData.clockMode) {
16245 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16246 } else if (movesPerSession > 0) {
16247 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16248 } else if (timeIncrement == 0) {
16249 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16251 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16253 return StrSave(buf);
16259 /* This routine is used only for certain modes */
16260 VariantClass v = gameInfo.variant;
16261 ChessMove r = GameUnfinished;
16264 if(keepInfo) return;
16266 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16267 r = gameInfo.result;
16268 p = gameInfo.resultDetails;
16269 gameInfo.resultDetails = NULL;
16271 ClearGameInfo(&gameInfo);
16272 gameInfo.variant = v;
16274 switch (gameMode) {
16275 case MachinePlaysWhite:
16276 gameInfo.event = StrSave( appData.pgnEventHeader );
16277 gameInfo.site = StrSave(HostName());
16278 gameInfo.date = PGNDate();
16279 gameInfo.round = StrSave("-");
16280 gameInfo.white = StrSave(first.tidy);
16281 gameInfo.black = StrSave(UserName());
16282 gameInfo.timeControl = TimeControlTagValue();
16285 case MachinePlaysBlack:
16286 gameInfo.event = StrSave( appData.pgnEventHeader );
16287 gameInfo.site = StrSave(HostName());
16288 gameInfo.date = PGNDate();
16289 gameInfo.round = StrSave("-");
16290 gameInfo.white = StrSave(UserName());
16291 gameInfo.black = StrSave(first.tidy);
16292 gameInfo.timeControl = TimeControlTagValue();
16295 case TwoMachinesPlay:
16296 gameInfo.event = StrSave( appData.pgnEventHeader );
16297 gameInfo.site = StrSave(HostName());
16298 gameInfo.date = PGNDate();
16301 snprintf(buf, MSG_SIZ, "%d", roundNr);
16302 gameInfo.round = StrSave(buf);
16304 gameInfo.round = StrSave("-");
16306 if (first.twoMachinesColor[0] == 'w') {
16307 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16308 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16310 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16311 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16313 gameInfo.timeControl = TimeControlTagValue();
16317 gameInfo.event = StrSave("Edited game");
16318 gameInfo.site = StrSave(HostName());
16319 gameInfo.date = PGNDate();
16320 gameInfo.round = StrSave("-");
16321 gameInfo.white = StrSave("-");
16322 gameInfo.black = StrSave("-");
16323 gameInfo.result = r;
16324 gameInfo.resultDetails = p;
16328 gameInfo.event = StrSave("Edited position");
16329 gameInfo.site = StrSave(HostName());
16330 gameInfo.date = PGNDate();
16331 gameInfo.round = StrSave("-");
16332 gameInfo.white = StrSave("-");
16333 gameInfo.black = StrSave("-");
16336 case IcsPlayingWhite:
16337 case IcsPlayingBlack:
16342 case PlayFromGameFile:
16343 gameInfo.event = StrSave("Game from non-PGN file");
16344 gameInfo.site = StrSave(HostName());
16345 gameInfo.date = PGNDate();
16346 gameInfo.round = StrSave("-");
16347 gameInfo.white = StrSave("?");
16348 gameInfo.black = StrSave("?");
16357 ReplaceComment (int index, char *text)
16363 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16364 pvInfoList[index-1].depth == len &&
16365 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16366 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16367 while (*text == '\n') text++;
16368 len = strlen(text);
16369 while (len > 0 && text[len - 1] == '\n') len--;
16371 if (commentList[index] != NULL)
16372 free(commentList[index]);
16375 commentList[index] = NULL;
16378 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16379 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16380 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16381 commentList[index] = (char *) malloc(len + 2);
16382 strncpy(commentList[index], text, len);
16383 commentList[index][len] = '\n';
16384 commentList[index][len + 1] = NULLCHAR;
16386 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16388 commentList[index] = (char *) malloc(len + 7);
16389 safeStrCpy(commentList[index], "{\n", 3);
16390 safeStrCpy(commentList[index]+2, text, len+1);
16391 commentList[index][len+2] = NULLCHAR;
16392 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16393 strcat(commentList[index], "\n}\n");
16398 CrushCRs (char *text)
16406 if (ch == '\r') continue;
16408 } while (ch != '\0');
16412 AppendComment (int index, char *text, Boolean addBraces)
16413 /* addBraces tells if we should add {} */
16418 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16419 if(addBraces == 3) addBraces = 0; else // force appending literally
16420 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16423 while (*text == '\n') text++;
16424 len = strlen(text);
16425 while (len > 0 && text[len - 1] == '\n') len--;
16426 text[len] = NULLCHAR;
16428 if (len == 0) return;
16430 if (commentList[index] != NULL) {
16431 Boolean addClosingBrace = addBraces;
16432 old = commentList[index];
16433 oldlen = strlen(old);
16434 while(commentList[index][oldlen-1] == '\n')
16435 commentList[index][--oldlen] = NULLCHAR;
16436 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16437 safeStrCpy(commentList[index], old, oldlen + len + 6);
16439 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16440 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16441 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16442 while (*text == '\n') { text++; len--; }
16443 commentList[index][--oldlen] = NULLCHAR;
16445 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16446 else strcat(commentList[index], "\n");
16447 strcat(commentList[index], text);
16448 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16449 else strcat(commentList[index], "\n");
16451 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16453 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16454 else commentList[index][0] = NULLCHAR;
16455 strcat(commentList[index], text);
16456 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16457 if(addBraces == TRUE) strcat(commentList[index], "}\n");
16462 FindStr (char * text, char * sub_text)
16464 char * result = strstr( text, sub_text );
16466 if( result != NULL ) {
16467 result += strlen( sub_text );
16473 /* [AS] Try to extract PV info from PGN comment */
16474 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16476 GetInfoFromComment (int index, char * text)
16478 char * sep = text, *p;
16480 if( text != NULL && index > 0 ) {
16483 int time = -1, sec = 0, deci;
16484 char * s_eval = FindStr( text, "[%eval " );
16485 char * s_emt = FindStr( text, "[%emt " );
16487 if( s_eval != NULL || s_emt != NULL ) {
16489 if(0) { // [HGM] this code is not finished, and could actually be detrimental
16494 if( s_eval != NULL ) {
16495 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16499 if( delim != ']' ) {
16504 if( s_emt != NULL ) {
16509 /* We expect something like: [+|-]nnn.nn/dd */
16512 if(*text != '{') return text; // [HGM] braces: must be normal comment
16514 sep = strchr( text, '/' );
16515 if( sep == NULL || sep < (text+4) ) {
16520 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16521 if(p[1] == '(') { // comment starts with PV
16522 p = strchr(p, ')'); // locate end of PV
16523 if(p == NULL || sep < p+5) return text;
16524 // at this point we have something like "{(.*) +0.23/6 ..."
16525 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16526 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16527 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16529 time = -1; sec = -1; deci = -1;
16530 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16531 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16532 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16533 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
16537 if( score_lo < 0 || score_lo >= 100 ) {
16541 if(sec >= 0) time = 600*time + 10*sec; else
16542 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16544 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16546 /* [HGM] PV time: now locate end of PV info */
16547 while( *++sep >= '0' && *sep <= '9'); // strip depth
16549 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16551 while( *++sep >= '0' && *sep <= '9'); // strip seconds
16553 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16554 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16565 pvInfoList[index-1].depth = depth;
16566 pvInfoList[index-1].score = score;
16567 pvInfoList[index-1].time = 10*time; // centi-sec
16568 if(*sep == '}') *sep = 0; else *--sep = '{';
16569 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16575 SendToProgram (char *message, ChessProgramState *cps)
16577 int count, outCount, error;
16580 if (cps->pr == NoProc) return;
16583 if (appData.debugMode) {
16586 fprintf(debugFP, "%ld >%-6s: %s",
16587 SubtractTimeMarks(&now, &programStartTime),
16588 cps->which, message);
16590 fprintf(serverFP, "%ld >%-6s: %s",
16591 SubtractTimeMarks(&now, &programStartTime),
16592 cps->which, message), fflush(serverFP);
16595 count = strlen(message);
16596 outCount = OutputToProcess(cps->pr, message, count, &error);
16597 if (outCount < count && !exiting
16598 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16599 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16600 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16601 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16602 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16603 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16604 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16605 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16607 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16608 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16609 gameInfo.result = res;
16611 gameInfo.resultDetails = StrSave(buf);
16613 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16614 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16619 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16623 ChessProgramState *cps = (ChessProgramState *)closure;
16625 if (isr != cps->isr) return; /* Killed intentionally */
16628 RemoveInputSource(cps->isr);
16629 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16630 _(cps->which), cps->program);
16631 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16632 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16633 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16634 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16635 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16636 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16638 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16639 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16640 gameInfo.result = res;
16642 gameInfo.resultDetails = StrSave(buf);
16644 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16645 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16647 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16648 _(cps->which), cps->program);
16649 RemoveInputSource(cps->isr);
16651 /* [AS] Program is misbehaving badly... kill it */
16652 if( count == -2 ) {
16653 DestroyChildProcess( cps->pr, 9 );
16657 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16662 if ((end_str = strchr(message, '\r')) != NULL)
16663 *end_str = NULLCHAR;
16664 if ((end_str = strchr(message, '\n')) != NULL)
16665 *end_str = NULLCHAR;
16667 if (appData.debugMode) {
16668 TimeMark now; int print = 1;
16669 char *quote = ""; char c; int i;
16671 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16672 char start = message[0];
16673 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16674 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16675 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
16676 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16677 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16678 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
16679 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16680 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
16681 sscanf(message, "hint: %c", &c)!=1 &&
16682 sscanf(message, "pong %c", &c)!=1 && start != '#') {
16683 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16684 print = (appData.engineComments >= 2);
16686 message[0] = start; // restore original message
16690 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16691 SubtractTimeMarks(&now, &programStartTime), cps->which,
16695 fprintf(serverFP, "%ld <%-6s: %s%s\n",
16696 SubtractTimeMarks(&now, &programStartTime), cps->which,
16698 message), fflush(serverFP);
16702 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16703 if (appData.icsEngineAnalyze) {
16704 if (strstr(message, "whisper") != NULL ||
16705 strstr(message, "kibitz") != NULL ||
16706 strstr(message, "tellics") != NULL) return;
16709 HandleMachineMove(message, cps);
16714 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16719 if( timeControl_2 > 0 ) {
16720 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16721 tc = timeControl_2;
16724 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16725 inc /= cps->timeOdds;
16726 st /= cps->timeOdds;
16728 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16731 /* Set exact time per move, normally using st command */
16732 if (cps->stKludge) {
16733 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16735 if (seconds == 0) {
16736 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16738 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16741 snprintf(buf, MSG_SIZ, "st %d\n", st);
16744 /* Set conventional or incremental time control, using level command */
16745 if (seconds == 0) {
16746 /* Note old gnuchess bug -- minutes:seconds used to not work.
16747 Fixed in later versions, but still avoid :seconds
16748 when seconds is 0. */
16749 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16751 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16752 seconds, inc/1000.);
16755 SendToProgram(buf, cps);
16757 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16758 /* Orthogonally, limit search to given depth */
16760 if (cps->sdKludge) {
16761 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16763 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16765 SendToProgram(buf, cps);
16768 if(cps->nps >= 0) { /* [HGM] nps */
16769 if(cps->supportsNPS == FALSE)
16770 cps->nps = -1; // don't use if engine explicitly says not supported!
16772 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16773 SendToProgram(buf, cps);
16778 ChessProgramState *
16780 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16782 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16783 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16789 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16791 char message[MSG_SIZ];
16794 /* Note: this routine must be called when the clocks are stopped
16795 or when they have *just* been set or switched; otherwise
16796 it will be off by the time since the current tick started.
16798 if (machineWhite) {
16799 time = whiteTimeRemaining / 10;
16800 otime = blackTimeRemaining / 10;
16802 time = blackTimeRemaining / 10;
16803 otime = whiteTimeRemaining / 10;
16805 /* [HGM] translate opponent's time by time-odds factor */
16806 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16808 if (time <= 0) time = 1;
16809 if (otime <= 0) otime = 1;
16811 snprintf(message, MSG_SIZ, "time %ld\n", time);
16812 SendToProgram(message, cps);
16814 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16815 SendToProgram(message, cps);
16819 EngineDefinedVariant (ChessProgramState *cps, int n)
16820 { // return name of n-th unknown variant that engine supports
16821 static char buf[MSG_SIZ];
16822 char *p, *s = cps->variants;
16823 if(!s) return NULL;
16824 do { // parse string from variants feature
16826 p = strchr(s, ',');
16827 if(p) *p = NULLCHAR;
16828 v = StringToVariant(s);
16829 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16830 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16831 if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16832 !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16833 !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16834 !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16835 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16838 if(n < 0) return buf;
16844 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16847 int len = strlen(name);
16850 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16852 sscanf(*p, "%d", &val);
16854 while (**p && **p != ' ')
16856 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16857 SendToProgram(buf, cps);
16864 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16867 int len = strlen(name);
16868 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16870 sscanf(*p, "%d", loc);
16871 while (**p && **p != ' ') (*p)++;
16872 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16873 SendToProgram(buf, cps);
16880 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16883 int len = strlen(name);
16884 if (strncmp((*p), name, len) == 0
16885 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16887 ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16888 sscanf(*p, "%[^\"]", *loc);
16889 while (**p && **p != '\"') (*p)++;
16890 if (**p == '\"') (*p)++;
16891 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16892 SendToProgram(buf, cps);
16899 ParseOption (Option *opt, ChessProgramState *cps)
16900 // [HGM] options: process the string that defines an engine option, and determine
16901 // name, type, default value, and allowed value range
16903 char *p, *q, buf[MSG_SIZ];
16904 int n, min = (-1)<<31, max = 1<<31, def;
16906 if(p = strstr(opt->name, " -spin ")) {
16907 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16908 if(max < min) max = min; // enforce consistency
16909 if(def < min) def = min;
16910 if(def > max) def = max;
16915 } else if((p = strstr(opt->name, " -slider "))) {
16916 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16917 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16918 if(max < min) max = min; // enforce consistency
16919 if(def < min) def = min;
16920 if(def > max) def = max;
16924 opt->type = Spin; // Slider;
16925 } else if((p = strstr(opt->name, " -string "))) {
16926 opt->textValue = p+9;
16927 opt->type = TextBox;
16928 } else if((p = strstr(opt->name, " -file "))) {
16929 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16930 opt->textValue = p+7;
16931 opt->type = FileName; // FileName;
16932 } else if((p = strstr(opt->name, " -path "))) {
16933 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16934 opt->textValue = p+7;
16935 opt->type = PathName; // PathName;
16936 } else if(p = strstr(opt->name, " -check ")) {
16937 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16938 opt->value = (def != 0);
16939 opt->type = CheckBox;
16940 } else if(p = strstr(opt->name, " -combo ")) {
16941 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16942 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16943 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16944 opt->value = n = 0;
16945 while(q = StrStr(q, " /// ")) {
16946 n++; *q = 0; // count choices, and null-terminate each of them
16948 if(*q == '*') { // remember default, which is marked with * prefix
16952 cps->comboList[cps->comboCnt++] = q;
16954 cps->comboList[cps->comboCnt++] = NULL;
16956 opt->type = ComboBox;
16957 } else if(p = strstr(opt->name, " -button")) {
16958 opt->type = Button;
16959 } else if(p = strstr(opt->name, " -save")) {
16960 opt->type = SaveButton;
16961 } else return FALSE;
16962 *p = 0; // terminate option name
16963 // now look if the command-line options define a setting for this engine option.
16964 if(cps->optionSettings && cps->optionSettings[0])
16965 p = strstr(cps->optionSettings, opt->name); else p = NULL;
16966 if(p && (p == cps->optionSettings || p[-1] == ',')) {
16967 snprintf(buf, MSG_SIZ, "option %s", p);
16968 if(p = strstr(buf, ",")) *p = 0;
16969 if(q = strchr(buf, '=')) switch(opt->type) {
16971 for(n=0; n<opt->max; n++)
16972 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16975 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16979 opt->value = atoi(q+1);
16984 SendToProgram(buf, cps);
16990 FeatureDone (ChessProgramState *cps, int val)
16992 DelayedEventCallback cb = GetDelayedEvent();
16993 if ((cb == InitBackEnd3 && cps == &first) ||
16994 (cb == SettingsMenuIfReady && cps == &second) ||
16995 (cb == LoadEngine) ||
16996 (cb == TwoMachinesEventIfReady)) {
16997 CancelDelayedEvent();
16998 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17000 cps->initDone = val;
17001 if(val) cps->reload = FALSE;
17004 /* Parse feature command from engine */
17006 ParseFeatures (char *args, ChessProgramState *cps)
17014 while (*p == ' ') p++;
17015 if (*p == NULLCHAR) return;
17017 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17018 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17019 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17020 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17021 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17022 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17023 if (BoolFeature(&p, "reuse", &val, cps)) {
17024 /* Engine can disable reuse, but can't enable it if user said no */
17025 if (!val) cps->reuse = FALSE;
17028 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17029 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17030 if (gameMode == TwoMachinesPlay) {
17031 DisplayTwoMachinesTitle();
17037 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17038 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17039 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17040 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17041 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17042 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17043 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17044 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17045 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17046 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17047 if (IntFeature(&p, "done", &val, cps)) {
17048 FeatureDone(cps, val);
17051 /* Added by Tord: */
17052 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17053 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17054 /* End of additions by Tord */
17056 /* [HGM] added features: */
17057 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17058 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17059 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17060 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17061 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17062 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17063 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17064 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17065 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17066 FREE(cps->option[cps->nrOptions].name);
17067 cps->option[cps->nrOptions].name = q; q = NULL;
17068 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17069 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17070 SendToProgram(buf, cps);
17073 if(cps->nrOptions >= MAX_OPTIONS) {
17075 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17076 DisplayError(buf, 0);
17080 /* End of additions by HGM */
17082 /* unknown feature: complain and skip */
17084 while (*q && *q != '=') q++;
17085 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17086 SendToProgram(buf, cps);
17092 while (*p && *p != '\"') p++;
17093 if (*p == '\"') p++;
17095 while (*p && *p != ' ') p++;
17103 PeriodicUpdatesEvent (int newState)
17105 if (newState == appData.periodicUpdates)
17108 appData.periodicUpdates=newState;
17110 /* Display type changes, so update it now */
17111 // DisplayAnalysis();
17113 /* Get the ball rolling again... */
17115 AnalysisPeriodicEvent(1);
17116 StartAnalysisClock();
17121 PonderNextMoveEvent (int newState)
17123 if (newState == appData.ponderNextMove) return;
17124 if (gameMode == EditPosition) EditPositionDone(TRUE);
17126 SendToProgram("hard\n", &first);
17127 if (gameMode == TwoMachinesPlay) {
17128 SendToProgram("hard\n", &second);
17131 SendToProgram("easy\n", &first);
17132 thinkOutput[0] = NULLCHAR;
17133 if (gameMode == TwoMachinesPlay) {
17134 SendToProgram("easy\n", &second);
17137 appData.ponderNextMove = newState;
17141 NewSettingEvent (int option, int *feature, char *command, int value)
17145 if (gameMode == EditPosition) EditPositionDone(TRUE);
17146 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17147 if(feature == NULL || *feature) SendToProgram(buf, &first);
17148 if (gameMode == TwoMachinesPlay) {
17149 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17154 ShowThinkingEvent ()
17155 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17157 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17158 int newState = appData.showThinking
17159 // [HGM] thinking: other features now need thinking output as well
17160 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17162 if (oldState == newState) return;
17163 oldState = newState;
17164 if (gameMode == EditPosition) EditPositionDone(TRUE);
17166 SendToProgram("post\n", &first);
17167 if (gameMode == TwoMachinesPlay) {
17168 SendToProgram("post\n", &second);
17171 SendToProgram("nopost\n", &first);
17172 thinkOutput[0] = NULLCHAR;
17173 if (gameMode == TwoMachinesPlay) {
17174 SendToProgram("nopost\n", &second);
17177 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17181 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17183 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17184 if (pr == NoProc) return;
17185 AskQuestion(title, question, replyPrefix, pr);
17189 TypeInEvent (char firstChar)
17191 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17192 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17193 gameMode == AnalyzeMode || gameMode == EditGame ||
17194 gameMode == EditPosition || gameMode == IcsExamining ||
17195 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17196 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17197 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17198 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
17199 gameMode == Training) PopUpMoveDialog(firstChar);
17203 TypeInDoneEvent (char *move)
17206 int n, fromX, fromY, toX, toY;
17208 ChessMove moveType;
17211 if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17212 EditPositionPasteFEN(move);
17215 // [HGM] movenum: allow move number to be typed in any mode
17216 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17220 // undocumented kludge: allow command-line option to be typed in!
17221 // (potentially fatal, and does not implement the effect of the option.)
17222 // should only be used for options that are values on which future decisions will be made,
17223 // and definitely not on options that would be used during initialization.
17224 if(strstr(move, "!!! -") == move) {
17225 ParseArgsFromString(move+4);
17229 if (gameMode != EditGame && currentMove != forwardMostMove &&
17230 gameMode != Training) {
17231 DisplayMoveError(_("Displayed move is not current"));
17233 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17234 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17235 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17236 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17237 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17238 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17240 DisplayMoveError(_("Could not parse move"));
17246 DisplayMove (int moveNumber)
17248 char message[MSG_SIZ];
17250 char cpThinkOutput[MSG_SIZ];
17252 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17254 if (moveNumber == forwardMostMove - 1 ||
17255 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17257 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17259 if (strchr(cpThinkOutput, '\n')) {
17260 *strchr(cpThinkOutput, '\n') = NULLCHAR;
17263 *cpThinkOutput = NULLCHAR;
17266 /* [AS] Hide thinking from human user */
17267 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17268 *cpThinkOutput = NULLCHAR;
17269 if( thinkOutput[0] != NULLCHAR ) {
17272 for( i=0; i<=hiddenThinkOutputState; i++ ) {
17273 cpThinkOutput[i] = '.';
17275 cpThinkOutput[i] = NULLCHAR;
17276 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17280 if (moveNumber == forwardMostMove - 1 &&
17281 gameInfo.resultDetails != NULL) {
17282 if (gameInfo.resultDetails[0] == NULLCHAR) {
17283 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17285 snprintf(res, MSG_SIZ, " {%s} %s",
17286 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17292 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17293 DisplayMessage(res, cpThinkOutput);
17295 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17296 WhiteOnMove(moveNumber) ? " " : ".. ",
17297 parseList[moveNumber], res);
17298 DisplayMessage(message, cpThinkOutput);
17303 DisplayComment (int moveNumber, char *text)
17305 char title[MSG_SIZ];
17307 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17308 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17310 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17311 WhiteOnMove(moveNumber) ? " " : ".. ",
17312 parseList[moveNumber]);
17314 if (text != NULL && (appData.autoDisplayComment || commentUp))
17315 CommentPopUp(title, text);
17318 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17319 * might be busy thinking or pondering. It can be omitted if your
17320 * gnuchess is configured to stop thinking immediately on any user
17321 * input. However, that gnuchess feature depends on the FIONREAD
17322 * ioctl, which does not work properly on some flavors of Unix.
17325 Attention (ChessProgramState *cps)
17328 if (!cps->useSigint) return;
17329 if (appData.noChessProgram || (cps->pr == NoProc)) return;
17330 switch (gameMode) {
17331 case MachinePlaysWhite:
17332 case MachinePlaysBlack:
17333 case TwoMachinesPlay:
17334 case IcsPlayingWhite:
17335 case IcsPlayingBlack:
17338 /* Skip if we know it isn't thinking */
17339 if (!cps->maybeThinking) return;
17340 if (appData.debugMode)
17341 fprintf(debugFP, "Interrupting %s\n", cps->which);
17342 InterruptChildProcess(cps->pr);
17343 cps->maybeThinking = FALSE;
17348 #endif /*ATTENTION*/
17354 if (whiteTimeRemaining <= 0) {
17357 if (appData.icsActive) {
17358 if (appData.autoCallFlag &&
17359 gameMode == IcsPlayingBlack && !blackFlag) {
17360 SendToICS(ics_prefix);
17361 SendToICS("flag\n");
17365 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17367 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17368 if (appData.autoCallFlag) {
17369 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17376 if (blackTimeRemaining <= 0) {
17379 if (appData.icsActive) {
17380 if (appData.autoCallFlag &&
17381 gameMode == IcsPlayingWhite && !whiteFlag) {
17382 SendToICS(ics_prefix);
17383 SendToICS("flag\n");
17387 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17389 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17390 if (appData.autoCallFlag) {
17391 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17402 CheckTimeControl ()
17404 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17405 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17408 * add time to clocks when time control is achieved ([HGM] now also used for increment)
17410 if ( !WhiteOnMove(forwardMostMove) ) {
17411 /* White made time control */
17412 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17413 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17414 /* [HGM] time odds: correct new time quota for time odds! */
17415 / WhitePlayer()->timeOdds;
17416 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17418 lastBlack -= blackTimeRemaining;
17419 /* Black made time control */
17420 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17421 / WhitePlayer()->other->timeOdds;
17422 lastWhite = whiteTimeRemaining;
17427 DisplayBothClocks ()
17429 int wom = gameMode == EditPosition ?
17430 !blackPlaysFirst : WhiteOnMove(currentMove);
17431 DisplayWhiteClock(whiteTimeRemaining, wom);
17432 DisplayBlackClock(blackTimeRemaining, !wom);
17436 /* Timekeeping seems to be a portability nightmare. I think everyone
17437 has ftime(), but I'm really not sure, so I'm including some ifdefs
17438 to use other calls if you don't. Clocks will be less accurate if
17439 you have neither ftime nor gettimeofday.
17442 /* VS 2008 requires the #include outside of the function */
17443 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17444 #include <sys/timeb.h>
17447 /* Get the current time as a TimeMark */
17449 GetTimeMark (TimeMark *tm)
17451 #if HAVE_GETTIMEOFDAY
17453 struct timeval timeVal;
17454 struct timezone timeZone;
17456 gettimeofday(&timeVal, &timeZone);
17457 tm->sec = (long) timeVal.tv_sec;
17458 tm->ms = (int) (timeVal.tv_usec / 1000L);
17460 #else /*!HAVE_GETTIMEOFDAY*/
17463 // include <sys/timeb.h> / moved to just above start of function
17464 struct timeb timeB;
17467 tm->sec = (long) timeB.time;
17468 tm->ms = (int) timeB.millitm;
17470 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17471 tm->sec = (long) time(NULL);
17477 /* Return the difference in milliseconds between two
17478 time marks. We assume the difference will fit in a long!
17481 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17483 return 1000L*(tm2->sec - tm1->sec) +
17484 (long) (tm2->ms - tm1->ms);
17489 * Code to manage the game clocks.
17491 * In tournament play, black starts the clock and then white makes a move.
17492 * We give the human user a slight advantage if he is playing white---the
17493 * clocks don't run until he makes his first move, so it takes zero time.
17494 * Also, we don't account for network lag, so we could get out of sync
17495 * with GNU Chess's clock -- but then, referees are always right.
17498 static TimeMark tickStartTM;
17499 static long intendedTickLength;
17502 NextTickLength (long timeRemaining)
17504 long nominalTickLength, nextTickLength;
17506 if (timeRemaining > 0L && timeRemaining <= 10000L)
17507 nominalTickLength = 100L;
17509 nominalTickLength = 1000L;
17510 nextTickLength = timeRemaining % nominalTickLength;
17511 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17513 return nextTickLength;
17516 /* Adjust clock one minute up or down */
17518 AdjustClock (Boolean which, int dir)
17520 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17521 if(which) blackTimeRemaining += 60000*dir;
17522 else whiteTimeRemaining += 60000*dir;
17523 DisplayBothClocks();
17524 adjustedClock = TRUE;
17527 /* Stop clocks and reset to a fresh time control */
17531 (void) StopClockTimer();
17532 if (appData.icsActive) {
17533 whiteTimeRemaining = blackTimeRemaining = 0;
17534 } else if (searchTime) {
17535 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17536 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17537 } else { /* [HGM] correct new time quote for time odds */
17538 whiteTC = blackTC = fullTimeControlString;
17539 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17540 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17542 if (whiteFlag || blackFlag) {
17544 whiteFlag = blackFlag = FALSE;
17546 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17547 DisplayBothClocks();
17548 adjustedClock = FALSE;
17551 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17553 /* Decrement running clock by amount of time that has passed */
17557 long timeRemaining;
17558 long lastTickLength, fudge;
17561 if (!appData.clockMode) return;
17562 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17566 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17568 /* Fudge if we woke up a little too soon */
17569 fudge = intendedTickLength - lastTickLength;
17570 if (fudge < 0 || fudge > FUDGE) fudge = 0;
17572 if (WhiteOnMove(forwardMostMove)) {
17573 if(whiteNPS >= 0) lastTickLength = 0;
17574 timeRemaining = whiteTimeRemaining -= lastTickLength;
17575 if(timeRemaining < 0 && !appData.icsActive) {
17576 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17577 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17578 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17579 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17582 DisplayWhiteClock(whiteTimeRemaining - fudge,
17583 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17585 if(blackNPS >= 0) lastTickLength = 0;
17586 timeRemaining = blackTimeRemaining -= lastTickLength;
17587 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17588 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17590 blackStartMove = forwardMostMove;
17591 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17594 DisplayBlackClock(blackTimeRemaining - fudge,
17595 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17597 if (CheckFlags()) return;
17599 if(twoBoards) { // count down secondary board's clocks as well
17600 activePartnerTime -= lastTickLength;
17602 if(activePartner == 'W')
17603 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17605 DisplayBlackClock(activePartnerTime, TRUE);
17610 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17611 StartClockTimer(intendedTickLength);
17613 /* if the time remaining has fallen below the alarm threshold, sound the
17614 * alarm. if the alarm has sounded and (due to a takeback or time control
17615 * with increment) the time remaining has increased to a level above the
17616 * threshold, reset the alarm so it can sound again.
17619 if (appData.icsActive && appData.icsAlarm) {
17621 /* make sure we are dealing with the user's clock */
17622 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17623 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17626 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17627 alarmSounded = FALSE;
17628 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17630 alarmSounded = TRUE;
17636 /* A player has just moved, so stop the previously running
17637 clock and (if in clock mode) start the other one.
17638 We redisplay both clocks in case we're in ICS mode, because
17639 ICS gives us an update to both clocks after every move.
17640 Note that this routine is called *after* forwardMostMove
17641 is updated, so the last fractional tick must be subtracted
17642 from the color that is *not* on move now.
17645 SwitchClocks (int newMoveNr)
17647 long lastTickLength;
17649 int flagged = FALSE;
17653 if (StopClockTimer() && appData.clockMode) {
17654 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17655 if (!WhiteOnMove(forwardMostMove)) {
17656 if(blackNPS >= 0) lastTickLength = 0;
17657 blackTimeRemaining -= lastTickLength;
17658 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17659 // if(pvInfoList[forwardMostMove].time == -1)
17660 pvInfoList[forwardMostMove].time = // use GUI time
17661 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17663 if(whiteNPS >= 0) lastTickLength = 0;
17664 whiteTimeRemaining -= lastTickLength;
17665 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17666 // if(pvInfoList[forwardMostMove].time == -1)
17667 pvInfoList[forwardMostMove].time =
17668 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17670 flagged = CheckFlags();
17672 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17673 CheckTimeControl();
17675 if (flagged || !appData.clockMode) return;
17677 switch (gameMode) {
17678 case MachinePlaysBlack:
17679 case MachinePlaysWhite:
17680 case BeginningOfGame:
17681 if (pausing) return;
17685 case PlayFromGameFile:
17693 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17694 if(WhiteOnMove(forwardMostMove))
17695 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17696 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17700 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17701 whiteTimeRemaining : blackTimeRemaining);
17702 StartClockTimer(intendedTickLength);
17706 /* Stop both clocks */
17710 long lastTickLength;
17713 if (!StopClockTimer()) return;
17714 if (!appData.clockMode) return;
17718 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17719 if (WhiteOnMove(forwardMostMove)) {
17720 if(whiteNPS >= 0) lastTickLength = 0;
17721 whiteTimeRemaining -= lastTickLength;
17722 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17724 if(blackNPS >= 0) lastTickLength = 0;
17725 blackTimeRemaining -= lastTickLength;
17726 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17731 /* Start clock of player on move. Time may have been reset, so
17732 if clock is already running, stop and restart it. */
17736 (void) StopClockTimer(); /* in case it was running already */
17737 DisplayBothClocks();
17738 if (CheckFlags()) return;
17740 if (!appData.clockMode) return;
17741 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17743 GetTimeMark(&tickStartTM);
17744 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17745 whiteTimeRemaining : blackTimeRemaining);
17747 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17748 whiteNPS = blackNPS = -1;
17749 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17750 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17751 whiteNPS = first.nps;
17752 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17753 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17754 blackNPS = first.nps;
17755 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17756 whiteNPS = second.nps;
17757 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17758 blackNPS = second.nps;
17759 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17761 StartClockTimer(intendedTickLength);
17765 TimeString (long ms)
17767 long second, minute, hour, day;
17769 static char buf[32];
17771 if (ms > 0 && ms <= 9900) {
17772 /* convert milliseconds to tenths, rounding up */
17773 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17775 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17779 /* convert milliseconds to seconds, rounding up */
17780 /* use floating point to avoid strangeness of integer division
17781 with negative dividends on many machines */
17782 second = (long) floor(((double) (ms + 999L)) / 1000.0);
17789 day = second / (60 * 60 * 24);
17790 second = second % (60 * 60 * 24);
17791 hour = second / (60 * 60);
17792 second = second % (60 * 60);
17793 minute = second / 60;
17794 second = second % 60;
17797 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17798 sign, day, hour, minute, second);
17800 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17802 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17809 * This is necessary because some C libraries aren't ANSI C compliant yet.
17812 StrStr (char *string, char *match)
17816 length = strlen(match);
17818 for (i = strlen(string) - length; i >= 0; i--, string++)
17819 if (!strncmp(match, string, length))
17826 StrCaseStr (char *string, char *match)
17830 length = strlen(match);
17832 for (i = strlen(string) - length; i >= 0; i--, string++) {
17833 for (j = 0; j < length; j++) {
17834 if (ToLower(match[j]) != ToLower(string[j]))
17837 if (j == length) return string;
17845 StrCaseCmp (char *s1, char *s2)
17850 c1 = ToLower(*s1++);
17851 c2 = ToLower(*s2++);
17852 if (c1 > c2) return 1;
17853 if (c1 < c2) return -1;
17854 if (c1 == NULLCHAR) return 0;
17862 return isupper(c) ? tolower(c) : c;
17869 return islower(c) ? toupper(c) : c;
17871 #endif /* !_amigados */
17878 if ((ret = (char *) malloc(strlen(s) + 1)))
17880 safeStrCpy(ret, s, strlen(s)+1);
17886 StrSavePtr (char *s, char **savePtr)
17891 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17892 safeStrCpy(*savePtr, s, strlen(s)+1);
17904 clock = time((time_t *)NULL);
17905 tm = localtime(&clock);
17906 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17907 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17908 return StrSave(buf);
17913 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17915 int i, j, fromX, fromY, toX, toY;
17916 int whiteToPlay, haveRights = nrCastlingRights;
17922 whiteToPlay = (gameMode == EditPosition) ?
17923 !blackPlaysFirst : (move % 2 == 0);
17926 /* Piece placement data */
17927 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17928 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17930 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17931 if (boards[move][i][j] == EmptySquare) {
17933 } else { ChessSquare piece = boards[move][i][j];
17934 if (emptycount > 0) {
17935 if(emptycount<10) /* [HGM] can be >= 10 */
17936 *p++ = '0' + emptycount;
17937 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17940 if(PieceToChar(piece) == '+') {
17941 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17943 piece = (ChessSquare)(CHUDEMOTED piece);
17945 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17946 if(*p = PieceSuffix(piece)) p++;
17948 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17949 p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17954 if (emptycount > 0) {
17955 if(emptycount<10) /* [HGM] can be >= 10 */
17956 *p++ = '0' + emptycount;
17957 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17964 /* [HGM] print Crazyhouse or Shogi holdings */
17965 if( gameInfo.holdingsWidth ) {
17966 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17968 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17969 piece = boards[move][i][BOARD_WIDTH-1];
17970 if( piece != EmptySquare )
17971 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17972 *p++ = PieceToChar(piece);
17974 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17975 piece = boards[move][BOARD_HEIGHT-i-1][0];
17976 if( piece != EmptySquare )
17977 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17978 *p++ = PieceToChar(piece);
17981 if( q == p ) *p++ = '-';
17987 *p++ = whiteToPlay ? 'w' : 'b';
17990 if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
17991 haveRights = 0; q = p;
17992 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
17993 piece = boards[move][0][i];
17994 if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
17995 if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
17998 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
17999 piece = boards[move][BOARD_HEIGHT-1][i];
18000 if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18001 if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18004 if(p == q) *p++ = '-';
18008 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18009 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18012 int handW=0, handB=0;
18013 if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18014 for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18015 for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18018 if(appData.fischerCastling) {
18019 if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18020 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18021 if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18023 /* [HGM] write directly from rights */
18024 if(boards[move][CASTLING][2] != NoRights &&
18025 boards[move][CASTLING][0] != NoRights )
18026 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18027 if(boards[move][CASTLING][2] != NoRights &&
18028 boards[move][CASTLING][1] != NoRights )
18029 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18032 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18033 if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18035 if(boards[move][CASTLING][5] != NoRights &&
18036 boards[move][CASTLING][3] != NoRights )
18037 *p++ = boards[move][CASTLING][3] + AAA;
18038 if(boards[move][CASTLING][5] != NoRights &&
18039 boards[move][CASTLING][4] != NoRights )
18040 *p++ = boards[move][CASTLING][4] + AAA;
18044 /* [HGM] write true castling rights */
18045 if( nrCastlingRights == 6 ) {
18047 if(boards[move][CASTLING][0] != NoRights &&
18048 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
18049 q = (boards[move][CASTLING][1] != NoRights &&
18050 boards[move][CASTLING][2] != NoRights );
18051 if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18052 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18053 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18054 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18058 if(boards[move][CASTLING][3] != NoRights &&
18059 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
18060 q = (boards[move][CASTLING][4] != NoRights &&
18061 boards[move][CASTLING][5] != NoRights );
18063 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18064 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18065 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18070 if (q == p) *p++ = '-'; /* No castling rights */
18074 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
18075 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18076 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18077 /* En passant target square */
18078 if (move > backwardMostMove) {
18079 fromX = moveList[move - 1][0] - AAA;
18080 fromY = moveList[move - 1][1] - ONE;
18081 toX = moveList[move - 1][2] - AAA;
18082 toY = moveList[move - 1][3] - ONE;
18083 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18084 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18085 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18087 /* 2-square pawn move just happened */
18089 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18093 } else if(move == backwardMostMove) {
18094 // [HGM] perhaps we should always do it like this, and forget the above?
18095 if((signed char)boards[move][EP_STATUS] >= 0) {
18096 *p++ = boards[move][EP_STATUS] + AAA;
18097 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18109 { int i = 0, j=move;
18111 /* [HGM] find reversible plies */
18112 if (appData.debugMode) { int k;
18113 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18114 for(k=backwardMostMove; k<=forwardMostMove; k++)
18115 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18119 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18120 if( j == backwardMostMove ) i += initialRulePlies;
18121 sprintf(p, "%d ", i);
18122 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18124 /* Fullmove number */
18125 sprintf(p, "%d", (move / 2) + 1);
18126 } else *--p = NULLCHAR;
18128 return StrSave(buf);
18132 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18134 int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18136 int emptycount, virgin[BOARD_FILES];
18137 ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18141 /* Piece placement data */
18142 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18145 if (*p == '/' || *p == ' ' || *p == '[' ) {
18147 emptycount = gameInfo.boardWidth - j;
18148 while (emptycount--)
18149 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18150 if (*p == '/') p++;
18151 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18152 for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18153 for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18155 appData.NrRanks = gameInfo.boardHeight - i; i=0;
18158 #if(BOARD_FILES >= 10)*0
18159 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18160 p++; emptycount=10;
18161 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18162 while (emptycount--)
18163 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18165 } else if (*p == '*') {
18166 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18167 } else if (isdigit(*p)) {
18168 emptycount = *p++ - '0';
18169 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18170 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18171 while (emptycount--)
18172 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18173 } else if (*p == '<') {
18174 if(i == BOARD_HEIGHT-1) shuffle = 1;
18175 else if (i != 0 || !shuffle) return FALSE;
18177 } else if (shuffle && *p == '>') {
18178 p++; // for now ignore closing shuffle range, and assume rank-end
18179 } else if (*p == '?') {
18180 if (j >= gameInfo.boardWidth) return FALSE;
18181 if (i != 0 && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18182 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18183 } else if (*p == '+' || isalpha(*p)) {
18184 char *q, *s = SUFFIXES;
18185 if (j >= gameInfo.boardWidth) return FALSE;
18188 if(q = strchr(s, p[1])) p++;
18189 piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18190 if(piece == EmptySquare) return FALSE; /* unknown piece */
18191 piece = (ChessSquare) (CHUPROMOTED piece ); p++;
18192 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18195 if(q = strchr(s, *p)) p++;
18196 piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18199 if(piece==EmptySquare) return FALSE; /* unknown piece */
18200 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18201 piece = (ChessSquare) (PROMOTED piece);
18202 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18205 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18206 if(piece == king) wKingRank = i;
18207 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18213 while (*p == '/' || *p == ' ') p++;
18215 if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18217 /* [HGM] by default clear Crazyhouse holdings, if present */
18218 if(gameInfo.holdingsWidth) {
18219 for(i=0; i<BOARD_HEIGHT; i++) {
18220 board[i][0] = EmptySquare; /* black holdings */
18221 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18222 board[i][1] = (ChessSquare) 0; /* black counts */
18223 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18227 /* [HGM] look for Crazyhouse holdings here */
18228 while(*p==' ') p++;
18229 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18230 int swap=0, wcnt=0, bcnt=0;
18232 if(*p == '<') swap++, p++;
18233 if(*p == '-' ) p++; /* empty holdings */ else {
18234 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18235 /* if we would allow FEN reading to set board size, we would */
18236 /* have to add holdings and shift the board read so far here */
18237 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18239 if((int) piece >= (int) BlackPawn ) {
18240 i = (int)piece - (int)BlackPawn;
18241 i = PieceToNumber((ChessSquare)i);
18242 if( i >= gameInfo.holdingsSize ) return FALSE;
18243 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18244 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
18247 i = (int)piece - (int)WhitePawn;
18248 i = PieceToNumber((ChessSquare)i);
18249 if( i >= gameInfo.holdingsSize ) return FALSE;
18250 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
18251 board[i][BOARD_WIDTH-2]++; /* black holdings */
18255 if(subst) { // substitute back-rank question marks by holdings pieces
18256 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18257 int k, m, n = bcnt + 1;
18258 if(board[0][j] == ClearBoard) {
18259 if(!wcnt) return FALSE;
18261 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18262 board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18263 if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18267 if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18268 if(!bcnt) return FALSE;
18269 if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18270 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18271 board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18272 if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18283 if(subst) return FALSE; // substitution requested, but no holdings
18285 while(*p == ' ') p++;
18289 if(appData.colorNickNames) {
18290 if( c == appData.colorNickNames[0] ) c = 'w'; else
18291 if( c == appData.colorNickNames[1] ) c = 'b';
18295 *blackPlaysFirst = FALSE;
18298 *blackPlaysFirst = TRUE;
18304 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18305 /* return the extra info in global variiables */
18307 while(*p==' ') p++;
18309 if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18310 if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18311 if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18314 /* set defaults in case FEN is incomplete */
18315 board[EP_STATUS] = EP_UNKNOWN;
18316 board[TOUCHED_W] = board[TOUCHED_B] = 0;
18317 for(i=0; i<nrCastlingRights; i++ ) {
18318 board[CASTLING][i] =
18319 appData.fischerCastling ? NoRights : initialRights[i];
18320 } /* assume possible unless obviously impossible */
18321 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18322 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18323 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18324 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18325 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18326 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18327 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18328 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18331 if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18334 while(isalpha(*p)) {
18335 if(isupper(*p)) w |= 1 << (*p++ - 'A');
18336 if(islower(*p)) b |= 1 << (*p++ - 'a');
18340 board[TOUCHED_W] = ~w;
18341 board[TOUCHED_B] = ~b;
18342 while(*p == ' ') p++;
18346 if(nrCastlingRights) {
18348 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18349 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18350 /* castling indicator present, so default becomes no castlings */
18351 for(i=0; i<nrCastlingRights; i++ ) {
18352 board[CASTLING][i] = NoRights;
18355 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18356 (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18357 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18358 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
18359 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18361 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18362 if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18363 if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18365 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18366 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18367 if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18368 && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18369 if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18370 && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18373 for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18374 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18375 board[CASTLING][2] = whiteKingFile;
18376 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18377 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18378 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18381 for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18382 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18383 board[CASTLING][2] = whiteKingFile;
18384 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18385 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18386 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18389 for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18390 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18391 board[CASTLING][5] = blackKingFile;
18392 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18393 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18394 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18397 for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18398 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18399 board[CASTLING][5] = blackKingFile;
18400 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18401 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18402 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18405 default: /* FRC castlings */
18406 if(c >= 'a') { /* black rights */
18407 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18408 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18409 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18410 if(i == BOARD_RGHT) break;
18411 board[CASTLING][5] = i;
18413 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
18414 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
18416 board[CASTLING][3] = c;
18418 board[CASTLING][4] = c;
18419 } else { /* white rights */
18420 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18421 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18422 if(board[0][i] == WhiteKing) break;
18423 if(i == BOARD_RGHT) break;
18424 board[CASTLING][2] = i;
18425 c -= AAA - 'a' + 'A';
18426 if(board[0][c] >= WhiteKing) break;
18428 board[CASTLING][0] = c;
18430 board[CASTLING][1] = c;
18434 for(i=0; i<nrCastlingRights; i++)
18435 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18436 if(gameInfo.variant == VariantSChess)
18437 for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18438 if(fischer && shuffle) appData.fischerCastling = TRUE;
18439 if (appData.debugMode) {
18440 fprintf(debugFP, "FEN castling rights:");
18441 for(i=0; i<nrCastlingRights; i++)
18442 fprintf(debugFP, " %d", board[CASTLING][i]);
18443 fprintf(debugFP, "\n");
18446 while(*p==' ') p++;
18449 if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18451 /* read e.p. field in games that know e.p. capture */
18452 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
18453 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18454 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18456 p++; board[EP_STATUS] = EP_NONE;
18458 char c = *p++ - AAA;
18460 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18461 if(*p >= '0' && *p <='9') p++;
18462 board[EP_STATUS] = c;
18467 if(sscanf(p, "%d", &i) == 1) {
18468 FENrulePlies = i; /* 50-move ply counter */
18469 /* (The move number is still ignored) */
18476 EditPositionPasteFEN (char *fen)
18479 Board initial_position;
18481 if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18482 DisplayError(_("Bad FEN position in clipboard"), 0);
18485 int savedBlackPlaysFirst = blackPlaysFirst;
18486 EditPositionEvent();
18487 blackPlaysFirst = savedBlackPlaysFirst;
18488 CopyBoard(boards[0], initial_position);
18489 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18490 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18491 DisplayBothClocks();
18492 DrawPosition(FALSE, boards[currentMove]);
18497 static char cseq[12] = "\\ ";
18500 set_cont_sequence (char *new_seq)
18505 // handle bad attempts to set the sequence
18507 return 0; // acceptable error - no debug
18509 len = strlen(new_seq);
18510 ret = (len > 0) && (len < sizeof(cseq));
18512 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18513 else if (appData.debugMode)
18514 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18519 reformat a source message so words don't cross the width boundary. internal
18520 newlines are not removed. returns the wrapped size (no null character unless
18521 included in source message). If dest is NULL, only calculate the size required
18522 for the dest buffer. lp argument indicats line position upon entry, and it's
18523 passed back upon exit.
18526 wrap (char *dest, char *src, int count, int width, int *lp)
18528 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18530 cseq_len = strlen(cseq);
18531 old_line = line = *lp;
18532 ansi = len = clen = 0;
18534 for (i=0; i < count; i++)
18536 if (src[i] == '\033')
18539 // if we hit the width, back up
18540 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18542 // store i & len in case the word is too long
18543 old_i = i, old_len = len;
18545 // find the end of the last word
18546 while (i && src[i] != ' ' && src[i] != '\n')
18552 // word too long? restore i & len before splitting it
18553 if ((old_i-i+clen) >= width)
18560 if (i && src[i-1] == ' ')
18563 if (src[i] != ' ' && src[i] != '\n')
18570 // now append the newline and continuation sequence
18575 strncpy(dest+len, cseq, cseq_len);
18583 dest[len] = src[i];
18587 if (src[i] == '\n')
18592 if (dest && appData.debugMode)
18594 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18595 count, width, line, len, *lp);
18596 show_bytes(debugFP, src, count);
18597 fprintf(debugFP, "\ndest: ");
18598 show_bytes(debugFP, dest, len);
18599 fprintf(debugFP, "\n");
18601 *lp = dest ? line : old_line;
18606 // [HGM] vari: routines for shelving variations
18607 Boolean modeRestore = FALSE;
18610 PushInner (int firstMove, int lastMove)
18612 int i, j, nrMoves = lastMove - firstMove;
18614 // push current tail of game on stack
18615 savedResult[storedGames] = gameInfo.result;
18616 savedDetails[storedGames] = gameInfo.resultDetails;
18617 gameInfo.resultDetails = NULL;
18618 savedFirst[storedGames] = firstMove;
18619 savedLast [storedGames] = lastMove;
18620 savedFramePtr[storedGames] = framePtr;
18621 framePtr -= nrMoves; // reserve space for the boards
18622 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18623 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18624 for(j=0; j<MOVE_LEN; j++)
18625 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18626 for(j=0; j<2*MOVE_LEN; j++)
18627 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18628 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18629 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18630 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18631 pvInfoList[firstMove+i-1].depth = 0;
18632 commentList[framePtr+i] = commentList[firstMove+i];
18633 commentList[firstMove+i] = NULL;
18637 forwardMostMove = firstMove; // truncate game so we can start variation
18641 PushTail (int firstMove, int lastMove)
18643 if(appData.icsActive) { // only in local mode
18644 forwardMostMove = currentMove; // mimic old ICS behavior
18647 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18649 PushInner(firstMove, lastMove);
18650 if(storedGames == 1) GreyRevert(FALSE);
18651 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18655 PopInner (Boolean annotate)
18658 char buf[8000], moveBuf[20];
18660 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18661 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18662 nrMoves = savedLast[storedGames] - currentMove;
18665 if(!WhiteOnMove(currentMove))
18666 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18667 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18668 for(i=currentMove; i<forwardMostMove; i++) {
18670 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18671 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18672 strcat(buf, moveBuf);
18673 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18674 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18678 for(i=1; i<=nrMoves; i++) { // copy last variation back
18679 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18680 for(j=0; j<MOVE_LEN; j++)
18681 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18682 for(j=0; j<2*MOVE_LEN; j++)
18683 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18684 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18685 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18686 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18687 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18688 commentList[currentMove+i] = commentList[framePtr+i];
18689 commentList[framePtr+i] = NULL;
18691 if(annotate) AppendComment(currentMove+1, buf, FALSE);
18692 framePtr = savedFramePtr[storedGames];
18693 gameInfo.result = savedResult[storedGames];
18694 if(gameInfo.resultDetails != NULL) {
18695 free(gameInfo.resultDetails);
18697 gameInfo.resultDetails = savedDetails[storedGames];
18698 forwardMostMove = currentMove + nrMoves;
18702 PopTail (Boolean annotate)
18704 if(appData.icsActive) return FALSE; // only in local mode
18705 if(!storedGames) return FALSE; // sanity
18706 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18708 PopInner(annotate);
18709 if(currentMove < forwardMostMove) ForwardEvent(); else
18710 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18712 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18718 { // remove all shelved variations
18720 for(i=0; i<storedGames; i++) {
18721 if(savedDetails[i])
18722 free(savedDetails[i]);
18723 savedDetails[i] = NULL;
18725 for(i=framePtr; i<MAX_MOVES; i++) {
18726 if(commentList[i]) free(commentList[i]);
18727 commentList[i] = NULL;
18729 framePtr = MAX_MOVES-1;
18734 LoadVariation (int index, char *text)
18735 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18736 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18737 int level = 0, move;
18739 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18740 // first find outermost bracketing variation
18741 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18742 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18743 if(*p == '{') wait = '}'; else
18744 if(*p == '[') wait = ']'; else
18745 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18746 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18748 if(*p == wait) wait = NULLCHAR; // closing ]} found
18751 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18752 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18753 end[1] = NULLCHAR; // clip off comment beyond variation
18754 ToNrEvent(currentMove-1);
18755 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18756 // kludge: use ParsePV() to append variation to game
18757 move = currentMove;
18758 ParsePV(start, TRUE, TRUE);
18759 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18760 ClearPremoveHighlights();
18762 ToNrEvent(currentMove+1);
18768 char *p, *q, buf[MSG_SIZ];
18769 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18770 snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18771 ParseArgsFromString(buf);
18772 ActivateTheme(TRUE); // also redo colors
18776 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18779 q = appData.themeNames;
18780 snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18781 if(appData.useBitmaps) {
18782 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18783 appData.liteBackTextureFile, appData.darkBackTextureFile,
18784 appData.liteBackTextureMode,
18785 appData.darkBackTextureMode );
18787 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18788 Col2Text(2), // lightSquareColor
18789 Col2Text(3) ); // darkSquareColor
18791 if(appData.useBorder) {
18792 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18795 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18797 if(appData.useFont) {
18798 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18799 appData.renderPiecesWithFont,
18800 appData.fontToPieceTable,
18801 Col2Text(9), // appData.fontBackColorWhite
18802 Col2Text(10) ); // appData.fontForeColorBlack
18804 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18805 appData.pieceDirectory);
18806 if(!appData.pieceDirectory[0])
18807 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18808 Col2Text(0), // whitePieceColor
18809 Col2Text(1) ); // blackPieceColor
18811 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18812 Col2Text(4), // highlightSquareColor
18813 Col2Text(5) ); // premoveHighlightColor
18814 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18815 if(insert != q) insert[-1] = NULLCHAR;
18816 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18819 ActivateTheme(FALSE);