2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free
9 * Software Foundation, Inc.
11 * Enhancements Copyright 2005 Alessandro Scotti
13 * The following terms apply to Digital Equipment Corporation's copyright
15 * ------------------------------------------------------------------------
18 * Permission to use, copy, modify, and distribute this software and its
19 * documentation for any purpose and without fee is hereby granted,
20 * provided that the above copyright notice appear in all copies and that
21 * both that copyright notice and this permission notice appear in
22 * supporting documentation, and that the name of Digital not be
23 * used in advertising or publicity pertaining to distribution of the
24 * software without specific, written prior permission.
26 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
27 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
28 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
29 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
30 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
31 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
33 * ------------------------------------------------------------------------
35 * The following terms apply to the enhanced version of XBoard
36 * distributed by the Free Software Foundation:
37 * ------------------------------------------------------------------------
39 * GNU XBoard is free software: you can redistribute it and/or modify
40 * it under the terms of the GNU General Public License as published by
41 * the Free Software Foundation, either version 3 of the License, or (at
42 * your option) any later version.
44 * GNU XBoard is distributed in the hope that it will be useful, but
45 * WITHOUT ANY WARRANTY; without even the implied warranty of
46 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
47 * General Public License for more details.
49 * You should have received a copy of the GNU General Public License
50 * along with this program. If not, see http://www.gnu.org/licenses/. *
52 *------------------------------------------------------------------------
53 ** See the file ChangeLog for a revision history. */
55 /* [AS] Also useful here for debugging */
59 int flock(int f, int code);
64 # define EGBB_NAME "egbbdll64.dll"
66 # define EGBB_NAME "egbbdll.dll"
71 # include <sys/file.h>
76 # define EGBB_NAME "egbbso64.so"
78 # define EGBB_NAME "egbbso.so"
80 // kludge to allow Windows code in back-end by converting it to corresponding Linux code
82 # define HMODULE void *
83 # define LoadLibrary(x) dlopen(x, RTLD_LAZY)
84 # define GetProcAddress dlsym
94 #include <sys/types.h>
103 #else /* not STDC_HEADERS */
106 # else /* not HAVE_STRING_H */
107 # include <strings.h>
108 # endif /* not HAVE_STRING_H */
109 #endif /* not STDC_HEADERS */
112 # include <sys/fcntl.h>
113 #else /* not HAVE_SYS_FCNTL_H */
116 # endif /* HAVE_FCNTL_H */
117 #endif /* not HAVE_SYS_FCNTL_H */
119 #if TIME_WITH_SYS_TIME
120 # include <sys/time.h>
124 # include <sys/time.h>
130 #if defined(_amigados) && !defined(__GNUC__)
135 extern int gettimeofday(struct timeval *, struct timezone *);
143 #include "frontend.h"
150 #include "backendz.h"
151 #include "evalgraph.h"
152 #include "engineoutput.h"
156 # define _(s) gettext (s)
157 # define N_(s) gettext_noop (s)
158 # define T_(s) gettext(s)
171 int establish P((void));
172 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
173 char *buf, int count, int error));
174 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
175 char *buf, int count, int error));
176 void SendToICS P((char *s));
177 void SendToICSDelayed P((char *s, long msdelay));
178 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
179 void HandleMachineMove P((char *message, ChessProgramState *cps));
180 int AutoPlayOneMove P((void));
181 int LoadGameOneMove P((ChessMove readAhead));
182 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
183 int LoadPositionFromFile P((char *filename, int n, char *title));
184 int SavePositionToFile P((char *filename));
185 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
186 void ShowMove P((int fromX, int fromY, int toX, int toY));
187 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
188 /*char*/int promoChar));
189 void BackwardInner P((int target));
190 void ForwardInner P((int target));
191 int Adjudicate P((ChessProgramState *cps));
192 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
193 void EditPositionDone P((Boolean fakeRights));
194 void PrintOpponents P((FILE *fp));
195 void PrintPosition P((FILE *fp, int move));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199 char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201 int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
250 extern void ConsoleCreate();
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
266 extern int tinyLayout, smallLayout;
267 ChessProgramStats programStats;
268 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
270 static int exiting = 0; /* [HGM] moved to top */
271 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
272 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
273 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
274 int partnerHighlight[2];
275 Boolean partnerBoardValid = 0;
276 char partnerStatus[MSG_SIZ];
278 Boolean originalFlip;
279 Boolean twoBoards = 0;
280 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
281 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
282 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
283 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
284 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
285 int opponentKibitzes;
286 int lastSavedGame; /* [HGM] save: ID of game */
287 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
288 extern int chatCount;
290 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
291 char legal[BOARD_RANKS][BOARD_FILES]; /* [HGM] legal target squares */
292 char lastMsg[MSG_SIZ];
293 char lastTalker[MSG_SIZ];
294 ChessSquare pieceSweep = EmptySquare;
295 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
296 int promoDefaultAltered;
297 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
298 static int initPing = -1;
299 int border; /* [HGM] width of board rim, needed to size seek graph */
300 char bestMove[MSG_SIZ], avoidMove[MSG_SIZ];
301 int solvingTime, totalTime;
303 /* States for ics_getting_history */
305 #define H_REQUESTED 1
306 #define H_GOT_REQ_HEADER 2
307 #define H_GOT_UNREQ_HEADER 3
308 #define H_GETTING_MOVES 4
309 #define H_GOT_UNWANTED_HEADER 5
311 /* whosays values for GameEnds */
320 /* Maximum number of games in a cmail message */
321 #define CMAIL_MAX_GAMES 20
323 /* Different types of move when calling RegisterMove */
325 #define CMAIL_RESIGN 1
327 #define CMAIL_ACCEPT 3
329 /* Different types of result to remember for each game */
330 #define CMAIL_NOT_RESULT 0
331 #define CMAIL_OLD_RESULT 1
332 #define CMAIL_NEW_RESULT 2
334 /* Telnet protocol constants */
345 safeStrCpy (char *dst, const char *src, size_t count)
348 assert( dst != NULL );
349 assert( src != NULL );
352 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
353 if( i == count && dst[count-1] != NULLCHAR)
355 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
356 if(appData.debugMode)
357 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
363 /* Some compiler can't cast u64 to double
364 * This function do the job for us:
366 * We use the highest bit for cast, this only
367 * works if the highest bit is not
368 * in use (This should not happen)
370 * We used this for all compiler
373 u64ToDouble (u64 value)
376 u64 tmp = value & u64Const(0x7fffffffffffffff);
377 r = (double)(s64)tmp;
378 if (value & u64Const(0x8000000000000000))
379 r += 9.2233720368547758080e18; /* 2^63 */
383 /* Fake up flags for now, as we aren't keeping track of castling
384 availability yet. [HGM] Change of logic: the flag now only
385 indicates the type of castlings allowed by the rule of the game.
386 The actual rights themselves are maintained in the array
387 castlingRights, as part of the game history, and are not probed
393 int flags = F_ALL_CASTLE_OK;
394 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
395 switch (gameInfo.variant) {
397 flags &= ~F_ALL_CASTLE_OK;
398 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
399 flags |= F_IGNORE_CHECK;
401 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
404 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
406 case VariantKriegspiel:
407 flags |= F_KRIEGSPIEL_CAPTURE;
409 case VariantCapaRandom:
410 case VariantFischeRandom:
411 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
412 case VariantNoCastle:
413 case VariantShatranj:
418 flags &= ~F_ALL_CASTLE_OK;
421 case VariantChuChess:
423 flags |= F_NULL_MOVE;
428 if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
432 FILE *gameFileFP, *debugFP, *serverFP;
433 char *currentDebugFile; // [HGM] debug split: to remember name
436 [AS] Note: sometimes, the sscanf() function is used to parse the input
437 into a fixed-size buffer. Because of this, we must be prepared to
438 receive strings as long as the size of the input buffer, which is currently
439 set to 4K for Windows and 8K for the rest.
440 So, we must either allocate sufficiently large buffers here, or
441 reduce the size of the input buffer in the input reading part.
444 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
445 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
446 char thinkOutput1[MSG_SIZ*10];
447 char promoRestrict[MSG_SIZ];
449 ChessProgramState first, second, pairing;
451 /* premove variables */
454 int premoveFromX = 0;
455 int premoveFromY = 0;
456 int premovePromoChar = 0;
458 Boolean alarmSounded;
459 /* end premove variables */
461 char *ics_prefix = "$";
462 enum ICS_TYPE ics_type = ICS_GENERIC;
464 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
465 int pauseExamForwardMostMove = 0;
466 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
467 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
468 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
469 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
470 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
471 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
472 int whiteFlag = FALSE, blackFlag = FALSE;
473 int userOfferedDraw = FALSE;
474 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
475 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
476 int cmailMoveType[CMAIL_MAX_GAMES];
477 long ics_clock_paused = 0;
478 ProcRef icsPR = NoProc, cmailPR = NoProc;
479 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
480 GameMode gameMode = BeginningOfGame;
481 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
482 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
483 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
484 int hiddenThinkOutputState = 0; /* [AS] */
485 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
486 int adjudicateLossPlies = 6;
487 char white_holding[64], black_holding[64];
488 TimeMark lastNodeCountTime;
489 long lastNodeCount=0;
490 int shiftKey, controlKey; // [HGM] set by mouse handler
492 int have_sent_ICS_logon = 0;
494 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
495 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
496 Boolean adjustedClock;
497 long timeControl_2; /* [AS] Allow separate time controls */
498 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
499 long timeRemaining[2][MAX_MOVES];
500 int matchGame = 0, nextGame = 0, roundNr = 0;
501 Boolean waitingForGame = FALSE, startingEngine = FALSE;
502 TimeMark programStartTime, pauseStart;
503 char ics_handle[MSG_SIZ];
504 int have_set_title = 0;
506 /* animateTraining preserves the state of appData.animate
507 * when Training mode is activated. This allows the
508 * response to be animated when appData.animate == TRUE and
509 * appData.animateDragging == TRUE.
511 Boolean animateTraining;
517 Board boards[MAX_MOVES];
518 /* [HGM] Following 7 needed for accurate legality tests: */
519 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
520 unsigned char initialRights[BOARD_FILES];
521 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
522 int initialRulePlies, FENrulePlies;
523 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
525 Boolean shuffleOpenings;
526 int mute; // mute all sounds
528 // [HGM] vari: next 12 to save and restore variations
529 #define MAX_VARIATIONS 10
530 int framePtr = MAX_MOVES-1; // points to free stack entry
532 int savedFirst[MAX_VARIATIONS];
533 int savedLast[MAX_VARIATIONS];
534 int savedFramePtr[MAX_VARIATIONS];
535 char *savedDetails[MAX_VARIATIONS];
536 ChessMove savedResult[MAX_VARIATIONS];
538 void PushTail P((int firstMove, int lastMove));
539 Boolean PopTail P((Boolean annotate));
540 void PushInner P((int firstMove, int lastMove));
541 void PopInner P((Boolean annotate));
542 void CleanupTail P((void));
544 ChessSquare FIDEArray[2][BOARD_FILES] = {
545 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
546 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
547 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
548 BlackKing, BlackBishop, BlackKnight, BlackRook }
551 ChessSquare twoKingsArray[2][BOARD_FILES] = {
552 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
553 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
554 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
555 BlackKing, BlackKing, BlackKnight, BlackRook }
558 ChessSquare KnightmateArray[2][BOARD_FILES] = {
559 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
560 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
561 { BlackRook, BlackMan, BlackBishop, BlackQueen,
562 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
565 ChessSquare SpartanArray[2][BOARD_FILES] = {
566 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
567 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
568 { BlackAlfil, BlackDragon, BlackKing, BlackTower,
569 BlackTower, BlackKing, BlackAngel, BlackAlfil }
572 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
573 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
574 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
575 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
576 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
579 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
580 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
581 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
582 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
583 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
586 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
587 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
588 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
589 { BlackRook, BlackKnight, BlackMan, BlackFerz,
590 BlackKing, BlackMan, BlackKnight, BlackRook }
593 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
594 { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
595 WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
596 { BlackRook, BlackKnight, BlackMan, BlackFerz,
597 BlackKing, BlackMan, BlackKnight, BlackRook }
600 ChessSquare lionArray[2][BOARD_FILES] = {
601 { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
602 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
603 { BlackRook, BlackLion, BlackBishop, BlackQueen,
604 BlackKing, BlackBishop, BlackKnight, BlackRook }
608 #if (BOARD_FILES>=10)
609 ChessSquare ShogiArray[2][BOARD_FILES] = {
610 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
611 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
612 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
613 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
616 ChessSquare XiangqiArray[2][BOARD_FILES] = {
617 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
618 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
619 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
620 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
623 ChessSquare CapablancaArray[2][BOARD_FILES] = {
624 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
625 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
626 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
627 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
630 ChessSquare GreatArray[2][BOARD_FILES] = {
631 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
632 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
633 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
634 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
637 ChessSquare JanusArray[2][BOARD_FILES] = {
638 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
639 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
640 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
641 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
644 ChessSquare GrandArray[2][BOARD_FILES] = {
645 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
646 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
647 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
648 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
651 ChessSquare ChuChessArray[2][BOARD_FILES] = {
652 { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
653 WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
654 { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
655 BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
659 ChessSquare GothicArray[2][BOARD_FILES] = {
660 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
661 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
662 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
663 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
666 #define GothicArray CapablancaArray
670 ChessSquare FalconArray[2][BOARD_FILES] = {
671 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
672 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
673 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
674 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
677 #define FalconArray CapablancaArray
680 #else // !(BOARD_FILES>=10)
681 #define XiangqiPosition FIDEArray
682 #define CapablancaArray FIDEArray
683 #define GothicArray FIDEArray
684 #define GreatArray FIDEArray
685 #endif // !(BOARD_FILES>=10)
687 #if (BOARD_FILES>=12)
688 ChessSquare CourierArray[2][BOARD_FILES] = {
689 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
690 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
691 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
692 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
694 ChessSquare ChuArray[6][BOARD_FILES] = {
695 { WhiteLance, WhiteCat, WhiteCopper, WhiteFerz, WhiteWazir, WhiteKing,
696 WhiteAlfil, WhiteWazir, WhiteFerz, WhiteCopper, WhiteCat, WhiteLance },
697 { BlackLance, BlackCat, BlackCopper, BlackFerz, BlackWazir, BlackAlfil,
698 BlackKing, BlackWazir, BlackFerz, BlackCopper, BlackCat, BlackLance },
699 { WhiteAxe, EmptySquare, WhiteBishop, EmptySquare, WhiteClaw, WhiteMarshall,
700 WhiteAngel, WhiteClaw, EmptySquare, WhiteBishop, EmptySquare, WhiteAxe },
701 { BlackAxe, EmptySquare, BlackBishop, EmptySquare, BlackClaw, BlackAngel,
702 BlackMarshall, BlackClaw, EmptySquare, BlackBishop, EmptySquare, BlackAxe },
703 { WhiteDagger, WhiteSword, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
704 WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSword, WhiteDagger },
705 { BlackDagger, BlackSword, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
706 BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSword, BlackDagger }
708 #else // !(BOARD_FILES>=12)
709 #define CourierArray CapablancaArray
710 #define ChuArray CapablancaArray
711 #endif // !(BOARD_FILES>=12)
714 Board initialPosition;
717 /* Convert str to a rating. Checks for special cases of "----",
719 "++++", etc. Also strips ()'s */
721 string_to_rating (char *str)
723 while(*str && !isdigit(*str)) ++str;
725 return 0; /* One of the special "no rating" cases */
733 /* Init programStats */
734 programStats.movelist[0] = 0;
735 programStats.depth = 0;
736 programStats.nr_moves = 0;
737 programStats.moves_left = 0;
738 programStats.nodes = 0;
739 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
740 programStats.score = 0;
741 programStats.got_only_move = 0;
742 programStats.got_fail = 0;
743 programStats.line_is_book = 0;
748 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
749 if (appData.firstPlaysBlack) {
750 first.twoMachinesColor = "black\n";
751 second.twoMachinesColor = "white\n";
753 first.twoMachinesColor = "white\n";
754 second.twoMachinesColor = "black\n";
757 first.other = &second;
758 second.other = &first;
761 if(appData.timeOddsMode) {
762 norm = appData.timeOdds[0];
763 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
765 first.timeOdds = appData.timeOdds[0]/norm;
766 second.timeOdds = appData.timeOdds[1]/norm;
769 if(programVersion) free(programVersion);
770 if (appData.noChessProgram) {
771 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
772 sprintf(programVersion, "%s", PACKAGE_STRING);
774 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
775 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
776 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
781 UnloadEngine (ChessProgramState *cps)
783 /* Kill off first chess program */
784 if (cps->isr != NULL)
785 RemoveInputSource(cps->isr);
788 if (cps->pr != NoProc) {
790 DoSleep( appData.delayBeforeQuit );
791 SendToProgram("quit\n", cps);
792 DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
795 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
799 ClearOptions (ChessProgramState *cps)
802 cps->nrOptions = cps->comboCnt = 0;
803 for(i=0; i<MAX_OPTIONS; i++) {
804 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
805 cps->option[i].textValue = 0;
809 char *engineNames[] = {
810 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
811 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
813 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
814 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
819 InitEngine (ChessProgramState *cps, int n)
820 { // [HGM] all engine initialiation put in a function that does one engine
824 cps->which = engineNames[n];
825 cps->maybeThinking = FALSE;
829 cps->sendDrawOffers = 1;
831 cps->program = appData.chessProgram[n];
832 cps->host = appData.host[n];
833 cps->dir = appData.directory[n];
834 cps->initString = appData.engInitString[n];
835 cps->computerString = appData.computerString[n];
836 cps->useSigint = TRUE;
837 cps->useSigterm = TRUE;
838 cps->reuse = appData.reuse[n];
839 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
840 cps->useSetboard = FALSE;
842 cps->usePing = FALSE;
845 cps->usePlayother = FALSE;
846 cps->useColors = TRUE;
847 cps->useUsermove = FALSE;
848 cps->sendICS = FALSE;
849 cps->sendName = appData.icsActive;
850 cps->sdKludge = FALSE;
851 cps->stKludge = FALSE;
852 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
853 TidyProgramName(cps->program, cps->host, cps->tidy);
855 ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
856 cps->analysisSupport = 2; /* detect */
857 cps->analyzing = FALSE;
858 cps->initDone = FALSE;
860 cps->pseudo = appData.pseudo[n];
862 /* New features added by Tord: */
863 cps->useFEN960 = FALSE;
864 cps->useOOCastle = TRUE;
865 /* End of new features added by Tord. */
866 cps->fenOverride = appData.fenOverride[n];
868 /* [HGM] time odds: set factor for each machine */
869 cps->timeOdds = appData.timeOdds[n];
871 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
872 cps->accumulateTC = appData.accumulateTC[n];
873 cps->maxNrOfSessions = 1;
878 cps->drawDepth = appData.drawDepth[n];
879 cps->supportsNPS = UNKNOWN;
880 cps->memSize = FALSE;
881 cps->maxCores = FALSE;
882 ASSIGN(cps->egtFormats, "");
885 cps->optionSettings = appData.engOptions[n];
887 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
888 cps->isUCI = appData.isUCI[n]; /* [AS] */
889 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
892 if (appData.protocolVersion[n] > PROTOVER
893 || appData.protocolVersion[n] < 1)
898 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
899 appData.protocolVersion[n]);
900 if( (len >= MSG_SIZ) && appData.debugMode )
901 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
903 DisplayFatalError(buf, 0, 2);
907 cps->protocolVersion = appData.protocolVersion[n];
910 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
911 ParseFeatures(appData.featureDefaults, cps);
914 ChessProgramState *savCps;
922 if(WaitForEngine(savCps, LoadEngine)) return;
923 CommonEngineInit(); // recalculate time odds
924 if(gameInfo.variant != StringToVariant(appData.variant)) {
925 // we changed variant when loading the engine; this forces us to reset
926 Reset(TRUE, savCps != &first);
927 oldMode = BeginningOfGame; // to prevent restoring old mode
929 InitChessProgram(savCps, FALSE);
930 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
931 DisplayMessage("", "");
932 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
933 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
936 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
940 ReplaceEngine (ChessProgramState *cps, int n)
942 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
944 if(oldMode != BeginningOfGame) EditGameEvent();
947 appData.noChessProgram = FALSE;
948 appData.clockMode = TRUE;
951 if(n) return; // only startup first engine immediately; second can wait
952 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
956 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
957 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
959 static char resetOptions[] =
960 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
961 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
962 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
963 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
966 FloatToFront(char **list, char *engineLine)
968 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
970 if(appData.recentEngines <= 0) return;
971 TidyProgramName(engineLine, "localhost", tidy+1);
972 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
973 strncpy(buf+1, *list, MSG_SIZ-50);
974 if(p = strstr(buf, tidy)) { // tidy name appears in list
975 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
976 while(*p++ = *++q); // squeeze out
978 strcat(tidy, buf+1); // put list behind tidy name
979 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
980 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
981 ASSIGN(*list, tidy+1);
984 char *insert, *wbOptions, *currentEngine[2]; // point in ChessProgramNames were we should insert new engine
987 Load (ChessProgramState *cps, int i)
989 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
990 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
991 ASSIGN(currentEngine[i], engineLine);
992 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
993 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
994 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
995 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
996 appData.firstProtocolVersion = PROTOVER;
997 ParseArgsFromString(buf);
999 ReplaceEngine(cps, i);
1000 FloatToFront(&appData.recentEngineList, engineLine);
1001 if(gameMode == BeginningOfGame) Reset(TRUE, TRUE);
1005 while(q = strchr(p, SLASH)) p = q+1;
1006 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1007 if(engineDir[0] != NULLCHAR) {
1008 ASSIGN(appData.directory[i], engineDir); p = engineName;
1009 } else if(p != engineName) { // derive directory from engine path, when not given
1011 ASSIGN(appData.directory[i], engineName);
1013 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1014 } else { ASSIGN(appData.directory[i], "."); }
1015 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1017 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1018 snprintf(command, MSG_SIZ, "%s %s", p, params);
1021 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1022 ASSIGN(appData.chessProgram[i], p);
1023 appData.isUCI[i] = isUCI;
1024 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1025 appData.hasOwnBookUCI[i] = hasBook;
1026 if(!nickName[0]) useNick = FALSE;
1027 if(useNick) ASSIGN(appData.pgnName[i], nickName);
1031 q = firstChessProgramNames;
1032 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1033 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1034 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s",
1035 quote, p, quote, appData.directory[i],
1036 useNick ? " -fn \"" : "",
1037 useNick ? nickName : "",
1038 useNick ? "\"" : "",
1039 v1 ? " -firstProtocolVersion 1" : "",
1040 hasBook ? "" : " -fNoOwnBookUCI",
1041 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1042 storeVariant ? " -variant " : "",
1043 storeVariant ? VariantName(gameInfo.variant) : "");
1044 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " %s", wbOptions);
1045 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 2);
1046 if(insert != q) insert[-1] = NULLCHAR;
1047 snprintf(firstChessProgramNames, len, "%s\n%s\n%s", q, buf, insert);
1049 FloatToFront(&appData.recentEngineList, buf);
1050 ASSIGN(currentEngine[i], buf);
1052 ReplaceEngine(cps, i);
1058 int matched, min, sec;
1060 * Parse timeControl resource
1062 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1063 appData.movesPerSession)) {
1065 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1066 DisplayFatalError(buf, 0, 2);
1070 * Parse searchTime resource
1072 if (*appData.searchTime != NULLCHAR) {
1073 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1075 searchTime = min * 60;
1076 } else if (matched == 2) {
1077 searchTime = min * 60 + sec;
1080 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1081 DisplayFatalError(buf, 0, 2);
1090 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1091 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1093 GetTimeMark(&programStartTime);
1094 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1095 appData.seedBase = random() + (random()<<15);
1096 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1098 ClearProgramStats();
1099 programStats.ok_to_send = 1;
1100 programStats.seen_stat = 0;
1103 * Initialize game list
1109 * Internet chess server status
1111 if (appData.icsActive) {
1112 appData.matchMode = FALSE;
1113 appData.matchGames = 0;
1115 appData.noChessProgram = !appData.zippyPlay;
1117 appData.zippyPlay = FALSE;
1118 appData.zippyTalk = FALSE;
1119 appData.noChessProgram = TRUE;
1121 if (*appData.icsHelper != NULLCHAR) {
1122 appData.useTelnet = TRUE;
1123 appData.telnetProgram = appData.icsHelper;
1126 appData.zippyTalk = appData.zippyPlay = FALSE;
1129 /* [AS] Initialize pv info list [HGM] and game state */
1133 for( i=0; i<=framePtr; i++ ) {
1134 pvInfoList[i].depth = -1;
1135 boards[i][EP_STATUS] = EP_NONE;
1136 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1142 /* [AS] Adjudication threshold */
1143 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1145 InitEngine(&first, 0);
1146 InitEngine(&second, 1);
1149 pairing.which = "pairing"; // pairing engine
1150 pairing.pr = NoProc;
1152 pairing.program = appData.pairingEngine;
1153 pairing.host = "localhost";
1156 if (appData.icsActive) {
1157 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1158 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1159 appData.clockMode = FALSE;
1160 first.sendTime = second.sendTime = 0;
1164 /* Override some settings from environment variables, for backward
1165 compatibility. Unfortunately it's not feasible to have the env
1166 vars just set defaults, at least in xboard. Ugh.
1168 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1173 if (!appData.icsActive) {
1177 /* Check for variants that are supported only in ICS mode,
1178 or not at all. Some that are accepted here nevertheless
1179 have bugs; see comments below.
1181 VariantClass variant = StringToVariant(appData.variant);
1183 case VariantBughouse: /* need four players and two boards */
1184 case VariantKriegspiel: /* need to hide pieces and move details */
1185 /* case VariantFischeRandom: (Fabien: moved below) */
1186 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1187 if( (len >= MSG_SIZ) && appData.debugMode )
1188 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1190 DisplayFatalError(buf, 0, 2);
1193 case VariantUnknown:
1194 case VariantLoadable:
1204 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1205 if( (len >= MSG_SIZ) && appData.debugMode )
1206 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1208 DisplayFatalError(buf, 0, 2);
1211 case VariantNormal: /* definitely works! */
1212 if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1213 safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1216 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1217 case VariantFairy: /* [HGM] TestLegality definitely off! */
1218 case VariantGothic: /* [HGM] should work */
1219 case VariantCapablanca: /* [HGM] should work */
1220 case VariantCourier: /* [HGM] initial forced moves not implemented */
1221 case VariantShogi: /* [HGM] could still mate with pawn drop */
1222 case VariantChu: /* [HGM] experimental */
1223 case VariantKnightmate: /* [HGM] should work */
1224 case VariantCylinder: /* [HGM] untested */
1225 case VariantFalcon: /* [HGM] untested */
1226 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1227 offboard interposition not understood */
1228 case VariantWildCastle: /* pieces not automatically shuffled */
1229 case VariantNoCastle: /* pieces not automatically shuffled */
1230 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1231 case VariantLosers: /* should work except for win condition,
1232 and doesn't know captures are mandatory */
1233 case VariantSuicide: /* should work except for win condition,
1234 and doesn't know captures are mandatory */
1235 case VariantGiveaway: /* should work except for win condition,
1236 and doesn't know captures are mandatory */
1237 case VariantTwoKings: /* should work */
1238 case VariantAtomic: /* should work except for win condition */
1239 case Variant3Check: /* should work except for win condition */
1240 case VariantShatranj: /* should work except for all win conditions */
1241 case VariantMakruk: /* should work except for draw countdown */
1242 case VariantASEAN : /* should work except for draw countdown */
1243 case VariantBerolina: /* might work if TestLegality is off */
1244 case VariantCapaRandom: /* should work */
1245 case VariantJanus: /* should work */
1246 case VariantSuper: /* experimental */
1247 case VariantGreat: /* experimental, requires legality testing to be off */
1248 case VariantSChess: /* S-Chess, should work */
1249 case VariantGrand: /* should work */
1250 case VariantSpartan: /* should work */
1251 case VariantLion: /* should work */
1252 case VariantChuChess: /* should work */
1260 NextIntegerFromString (char ** str, long * value)
1265 while( *s == ' ' || *s == '\t' ) {
1271 if( *s >= '0' && *s <= '9' ) {
1272 while( *s >= '0' && *s <= '9' ) {
1273 *value = *value * 10 + (*s - '0');
1286 NextTimeControlFromString (char ** str, long * value)
1289 int result = NextIntegerFromString( str, &temp );
1292 *value = temp * 60; /* Minutes */
1293 if( **str == ':' ) {
1295 result = NextIntegerFromString( str, &temp );
1296 *value += temp; /* Seconds */
1304 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1305 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1306 int result = -1, type = 0; long temp, temp2;
1308 if(**str != ':') return -1; // old params remain in force!
1310 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1311 if( NextIntegerFromString( str, &temp ) ) return -1;
1312 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1315 /* time only: incremental or sudden-death time control */
1316 if(**str == '+') { /* increment follows; read it */
1318 if(**str == '!') type = *(*str)++; // Bronstein TC
1319 if(result = NextIntegerFromString( str, &temp2)) return -1;
1320 *inc = temp2 * 1000;
1321 if(**str == '.') { // read fraction of increment
1322 char *start = ++(*str);
1323 if(result = NextIntegerFromString( str, &temp2)) return -1;
1325 while(start++ < *str) temp2 /= 10;
1329 *moves = 0; *tc = temp * 1000; *incType = type;
1333 (*str)++; /* classical time control */
1334 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1346 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1347 { /* [HGM] get time to add from the multi-session time-control string */
1348 int incType, moves=1; /* kludge to force reading of first session */
1349 long time, increment;
1352 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1354 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1355 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1356 if(movenr == -1) return time; /* last move before new session */
1357 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1358 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1359 if(!moves) return increment; /* current session is incremental */
1360 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1361 } while(movenr >= -1); /* try again for next session */
1363 return 0; // no new time quota on this move
1367 ParseTimeControl (char *tc, float ti, int mps)
1371 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1374 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1375 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1376 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1380 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1382 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1385 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1387 snprintf(buf, MSG_SIZ, ":%s", mytc);
1389 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1391 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1396 /* Parse second time control */
1399 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1407 timeControl_2 = tc2 * 1000;
1417 timeControl = tc1 * 1000;
1420 timeIncrement = ti * 1000; /* convert to ms */
1421 movesPerSession = 0;
1424 movesPerSession = mps;
1432 if (appData.debugMode) {
1433 # ifdef __GIT_VERSION
1434 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1436 fprintf(debugFP, "Version: %s\n", programVersion);
1439 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1441 set_cont_sequence(appData.wrapContSeq);
1442 if (appData.matchGames > 0) {
1443 appData.matchMode = TRUE;
1444 } else if (appData.matchMode) {
1445 appData.matchGames = 1;
1447 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1448 appData.matchGames = appData.sameColorGames;
1449 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1450 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1451 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1454 if (appData.noChessProgram || first.protocolVersion == 1) {
1457 /* kludge: allow timeout for initial "feature" commands */
1459 DisplayMessage("", _("Starting chess program"));
1460 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1465 CalculateIndex (int index, int gameNr)
1466 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1468 if(index > 0) return index; // fixed nmber
1469 if(index == 0) return 1;
1470 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1471 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1476 LoadGameOrPosition (int gameNr)
1477 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1478 if (*appData.loadGameFile != NULLCHAR) {
1479 if (!LoadGameFromFile(appData.loadGameFile,
1480 CalculateIndex(appData.loadGameIndex, gameNr),
1481 appData.loadGameFile, FALSE)) {
1482 DisplayFatalError(_("Bad game file"), 0, 1);
1485 } else if (*appData.loadPositionFile != NULLCHAR) {
1486 if (!LoadPositionFromFile(appData.loadPositionFile,
1487 CalculateIndex(appData.loadPositionIndex, gameNr),
1488 appData.loadPositionFile)) {
1489 DisplayFatalError(_("Bad position file"), 0, 1);
1497 ReserveGame (int gameNr, char resChar)
1499 FILE *tf = fopen(appData.tourneyFile, "r+");
1500 char *p, *q, c, buf[MSG_SIZ];
1501 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1502 safeStrCpy(buf, lastMsg, MSG_SIZ);
1503 DisplayMessage(_("Pick new game"), "");
1504 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1505 ParseArgsFromFile(tf);
1506 p = q = appData.results;
1507 if(appData.debugMode) {
1508 char *r = appData.participants;
1509 fprintf(debugFP, "results = '%s'\n", p);
1510 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1511 fprintf(debugFP, "\n");
1513 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1515 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1516 safeStrCpy(q, p, strlen(p) + 2);
1517 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1518 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1519 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1520 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1523 fseek(tf, -(strlen(p)+4), SEEK_END);
1525 if(c != '"') // depending on DOS or Unix line endings we can be one off
1526 fseek(tf, -(strlen(p)+2), SEEK_END);
1527 else fseek(tf, -(strlen(p)+3), SEEK_END);
1528 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1529 DisplayMessage(buf, "");
1530 free(p); appData.results = q;
1531 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1532 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1533 int round = appData.defaultMatchGames * appData.tourneyType;
1534 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1535 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1536 UnloadEngine(&first); // next game belongs to other pairing;
1537 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1539 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1543 MatchEvent (int mode)
1544 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1546 if(matchMode) { // already in match mode: switch it off
1548 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1551 // if(gameMode != BeginningOfGame) {
1552 // DisplayError(_("You can only start a match from the initial position."), 0);
1556 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1557 /* Set up machine vs. machine match */
1559 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1560 if(appData.tourneyFile[0]) {
1562 if(nextGame > appData.matchGames) {
1564 if(strchr(appData.results, '*') == NULL) {
1566 appData.tourneyCycles++;
1567 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1569 NextTourneyGame(-1, &dummy);
1571 if(nextGame <= appData.matchGames) {
1572 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1574 ScheduleDelayedEvent(NextMatchGame, 10000);
1579 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1580 DisplayError(buf, 0);
1581 appData.tourneyFile[0] = 0;
1585 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1586 DisplayFatalError(_("Can't have a match with no chess programs"),
1591 matchGame = roundNr = 1;
1592 first.matchWins = second.matchWins = totalTime = 0; // [HGM] match: needed in later matches
1596 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1599 InitBackEnd3 P((void))
1601 GameMode initialMode;
1605 ParseFeatures(appData.features[0], &first);
1606 if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode && // mode involves only first engine
1607 !strcmp(appData.variant, "normal") && // no explicit variant request
1608 appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 && // no size overrides requested
1609 !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") && // but 'normal' won't work with engine
1610 !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1611 char c, *q = first.variants, *p = strchr(q, ',');
1612 if(p) *p = NULLCHAR;
1613 if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1615 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1616 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1617 ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1618 Reset(TRUE, FALSE); // and re-initialize
1623 InitChessProgram(&first, startedFromSetupPosition);
1625 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1626 free(programVersion);
1627 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1628 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1629 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1632 if (appData.icsActive) {
1634 /* [DM] Make a console window if needed [HGM] merged ifs */
1640 if (*appData.icsCommPort != NULLCHAR)
1641 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1642 appData.icsCommPort);
1644 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1645 appData.icsHost, appData.icsPort);
1647 if( (len >= MSG_SIZ) && appData.debugMode )
1648 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1650 DisplayFatalError(buf, err, 1);
1655 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1657 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1658 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1659 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1660 } else if (appData.noChessProgram) {
1666 if (*appData.cmailGameName != NULLCHAR) {
1668 OpenLoopback(&cmailPR);
1670 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1674 DisplayMessage("", "");
1675 if (StrCaseCmp(appData.initialMode, "") == 0) {
1676 initialMode = BeginningOfGame;
1677 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1678 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1679 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1680 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1683 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1684 initialMode = TwoMachinesPlay;
1685 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1686 initialMode = AnalyzeFile;
1687 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1688 initialMode = AnalyzeMode;
1689 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1690 initialMode = MachinePlaysWhite;
1691 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1692 initialMode = MachinePlaysBlack;
1693 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1694 initialMode = EditGame;
1695 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1696 initialMode = EditPosition;
1697 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1698 initialMode = Training;
1700 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1701 if( (len >= MSG_SIZ) && appData.debugMode )
1702 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1704 DisplayFatalError(buf, 0, 2);
1708 if (appData.matchMode) {
1709 if(appData.tourneyFile[0]) { // start tourney from command line
1711 if(f = fopen(appData.tourneyFile, "r")) {
1712 ParseArgsFromFile(f); // make sure tourney parmeters re known
1714 appData.clockMode = TRUE;
1716 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1719 } else if (*appData.cmailGameName != NULLCHAR) {
1720 /* Set up cmail mode */
1721 ReloadCmailMsgEvent(TRUE);
1723 /* Set up other modes */
1724 if (initialMode == AnalyzeFile) {
1725 if (*appData.loadGameFile == NULLCHAR) {
1726 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1730 if (*appData.loadGameFile != NULLCHAR) {
1731 (void) LoadGameFromFile(appData.loadGameFile,
1732 appData.loadGameIndex,
1733 appData.loadGameFile, TRUE);
1734 } else if (*appData.loadPositionFile != NULLCHAR) {
1735 (void) LoadPositionFromFile(appData.loadPositionFile,
1736 appData.loadPositionIndex,
1737 appData.loadPositionFile);
1738 /* [HGM] try to make self-starting even after FEN load */
1739 /* to allow automatic setup of fairy variants with wtm */
1740 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1741 gameMode = BeginningOfGame;
1742 setboardSpoiledMachineBlack = 1;
1744 /* [HGM] loadPos: make that every new game uses the setup */
1745 /* from file as long as we do not switch variant */
1746 if(!blackPlaysFirst) {
1747 startedFromPositionFile = TRUE;
1748 CopyBoard(filePosition, boards[0]);
1749 CopyBoard(initialPosition, boards[0]);
1751 } else if(*appData.fen != NULLCHAR) {
1752 if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
1753 startedFromPositionFile = TRUE;
1757 if (initialMode == AnalyzeMode) {
1758 if (appData.noChessProgram) {
1759 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1762 if (appData.icsActive) {
1763 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1767 } else if (initialMode == AnalyzeFile) {
1768 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1769 ShowThinkingEvent();
1771 AnalysisPeriodicEvent(1);
1772 } else if (initialMode == MachinePlaysWhite) {
1773 if (appData.noChessProgram) {
1774 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1778 if (appData.icsActive) {
1779 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1783 MachineWhiteEvent();
1784 } else if (initialMode == MachinePlaysBlack) {
1785 if (appData.noChessProgram) {
1786 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1790 if (appData.icsActive) {
1791 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1795 MachineBlackEvent();
1796 } else if (initialMode == TwoMachinesPlay) {
1797 if (appData.noChessProgram) {
1798 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1802 if (appData.icsActive) {
1803 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1808 } else if (initialMode == EditGame) {
1810 } else if (initialMode == EditPosition) {
1811 EditPositionEvent();
1812 } else if (initialMode == Training) {
1813 if (*appData.loadGameFile == NULLCHAR) {
1814 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1823 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1825 DisplayBook(current+1);
1827 MoveHistorySet( movelist, first, last, current, pvInfoList );
1829 EvalGraphSet( first, last, current, pvInfoList );
1831 MakeEngineOutputTitle();
1835 * Establish will establish a contact to a remote host.port.
1836 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1837 * used to talk to the host.
1838 * Returns 0 if okay, error code if not.
1845 if (*appData.icsCommPort != NULLCHAR) {
1846 /* Talk to the host through a serial comm port */
1847 return OpenCommPort(appData.icsCommPort, &icsPR);
1849 } else if (*appData.gateway != NULLCHAR) {
1850 if (*appData.remoteShell == NULLCHAR) {
1851 /* Use the rcmd protocol to run telnet program on a gateway host */
1852 snprintf(buf, sizeof(buf), "%s %s %s",
1853 appData.telnetProgram, appData.icsHost, appData.icsPort);
1854 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1857 /* Use the rsh program to run telnet program on a gateway host */
1858 if (*appData.remoteUser == NULLCHAR) {
1859 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1860 appData.gateway, appData.telnetProgram,
1861 appData.icsHost, appData.icsPort);
1863 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1864 appData.remoteShell, appData.gateway,
1865 appData.remoteUser, appData.telnetProgram,
1866 appData.icsHost, appData.icsPort);
1868 return StartChildProcess(buf, "", &icsPR);
1871 } else if (appData.useTelnet) {
1872 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1875 /* TCP socket interface differs somewhat between
1876 Unix and NT; handle details in the front end.
1878 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1883 EscapeExpand (char *p, char *q)
1884 { // [HGM] initstring: routine to shape up string arguments
1885 while(*p++ = *q++) if(p[-1] == '\\')
1887 case 'n': p[-1] = '\n'; break;
1888 case 'r': p[-1] = '\r'; break;
1889 case 't': p[-1] = '\t'; break;
1890 case '\\': p[-1] = '\\'; break;
1891 case 0: *p = 0; return;
1892 default: p[-1] = q[-1]; break;
1897 show_bytes (FILE *fp, char *buf, int count)
1900 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1901 fprintf(fp, "\\%03o", *buf & 0xff);
1910 /* Returns an errno value */
1912 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1914 char buf[8192], *p, *q, *buflim;
1915 int left, newcount, outcount;
1917 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1918 *appData.gateway != NULLCHAR) {
1919 if (appData.debugMode) {
1920 fprintf(debugFP, ">ICS: ");
1921 show_bytes(debugFP, message, count);
1922 fprintf(debugFP, "\n");
1924 return OutputToProcess(pr, message, count, outError);
1927 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1934 if (appData.debugMode) {
1935 fprintf(debugFP, ">ICS: ");
1936 show_bytes(debugFP, buf, newcount);
1937 fprintf(debugFP, "\n");
1939 outcount = OutputToProcess(pr, buf, newcount, outError);
1940 if (outcount < newcount) return -1; /* to be sure */
1947 } else if (((unsigned char) *p) == TN_IAC) {
1948 *q++ = (char) TN_IAC;
1955 if (appData.debugMode) {
1956 fprintf(debugFP, ">ICS: ");
1957 show_bytes(debugFP, buf, newcount);
1958 fprintf(debugFP, "\n");
1960 outcount = OutputToProcess(pr, buf, newcount, outError);
1961 if (outcount < newcount) return -1; /* to be sure */
1966 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1968 int outError, outCount;
1969 static int gotEof = 0;
1972 /* Pass data read from player on to ICS */
1975 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1976 if (outCount < count) {
1977 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1979 if(have_sent_ICS_logon == 2) {
1980 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1981 fprintf(ini, "%s", message);
1982 have_sent_ICS_logon = 3;
1984 have_sent_ICS_logon = 1;
1985 } else if(have_sent_ICS_logon == 3) {
1986 fprintf(ini, "%s", message);
1988 have_sent_ICS_logon = 1;
1990 } else if (count < 0) {
1991 RemoveInputSource(isr);
1992 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1993 } else if (gotEof++ > 0) {
1994 RemoveInputSource(isr);
1995 DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
2001 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
2002 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
2003 connectionAlive = FALSE; // only sticks if no response to 'date' command.
2004 SendToICS("date\n");
2005 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
2008 /* added routine for printf style output to ics */
2010 ics_printf (char *format, ...)
2012 char buffer[MSG_SIZ];
2015 va_start(args, format);
2016 vsnprintf(buffer, sizeof(buffer), format, args);
2017 buffer[sizeof(buffer)-1] = '\0';
2025 int count, outCount, outError;
2027 if (icsPR == NoProc) return;
2030 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2031 if (outCount < count) {
2032 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2036 /* This is used for sending logon scripts to the ICS. Sending
2037 without a delay causes problems when using timestamp on ICC
2038 (at least on my machine). */
2040 SendToICSDelayed (char *s, long msdelay)
2042 int count, outCount, outError;
2044 if (icsPR == NoProc) return;
2047 if (appData.debugMode) {
2048 fprintf(debugFP, ">ICS: ");
2049 show_bytes(debugFP, s, count);
2050 fprintf(debugFP, "\n");
2052 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2054 if (outCount < count) {
2055 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2060 /* Remove all highlighting escape sequences in s
2061 Also deletes any suffix starting with '('
2064 StripHighlightAndTitle (char *s)
2066 static char retbuf[MSG_SIZ];
2069 while (*s != NULLCHAR) {
2070 while (*s == '\033') {
2071 while (*s != NULLCHAR && !isalpha(*s)) s++;
2072 if (*s != NULLCHAR) s++;
2074 while (*s != NULLCHAR && *s != '\033') {
2075 if (*s == '(' || *s == '[') {
2086 /* Remove all highlighting escape sequences in s */
2088 StripHighlight (char *s)
2090 static char retbuf[MSG_SIZ];
2093 while (*s != NULLCHAR) {
2094 while (*s == '\033') {
2095 while (*s != NULLCHAR && !isalpha(*s)) s++;
2096 if (*s != NULLCHAR) s++;
2098 while (*s != NULLCHAR && *s != '\033') {
2106 char engineVariant[MSG_SIZ];
2107 char *variantNames[] = VARIANT_NAMES;
2109 VariantName (VariantClass v)
2111 if(v == VariantUnknown || *engineVariant) return engineVariant;
2112 return variantNames[v];
2116 /* Identify a variant from the strings the chess servers use or the
2117 PGN Variant tag names we use. */
2119 StringToVariant (char *e)
2123 VariantClass v = VariantNormal;
2124 int i, found = FALSE;
2125 char buf[MSG_SIZ], c;
2130 /* [HGM] skip over optional board-size prefixes */
2131 if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2132 sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2133 while( *e++ != '_');
2136 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2140 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2141 if (p = StrCaseStr(e, variantNames[i])) {
2142 if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2143 v = (VariantClass) i;
2150 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2151 || StrCaseStr(e, "wild/fr")
2152 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2153 v = VariantFischeRandom;
2154 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2155 (i = 1, p = StrCaseStr(e, "w"))) {
2157 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2164 case 0: /* FICS only, actually */
2166 /* Castling legal even if K starts on d-file */
2167 v = VariantWildCastle;
2172 /* Castling illegal even if K & R happen to start in
2173 normal positions. */
2174 v = VariantNoCastle;
2187 /* Castling legal iff K & R start in normal positions */
2193 /* Special wilds for position setup; unclear what to do here */
2194 v = VariantLoadable;
2197 /* Bizarre ICC game */
2198 v = VariantTwoKings;
2201 v = VariantKriegspiel;
2207 v = VariantFischeRandom;
2210 v = VariantCrazyhouse;
2213 v = VariantBughouse;
2219 /* Not quite the same as FICS suicide! */
2220 v = VariantGiveaway;
2226 v = VariantShatranj;
2229 /* Temporary names for future ICC types. The name *will* change in
2230 the next xboard/WinBoard release after ICC defines it. */
2268 v = VariantCapablanca;
2271 v = VariantKnightmate;
2277 v = VariantCylinder;
2283 v = VariantCapaRandom;
2286 v = VariantBerolina;
2298 /* Found "wild" or "w" in the string but no number;
2299 must assume it's normal chess. */
2303 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2304 if( (len >= MSG_SIZ) && appData.debugMode )
2305 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2307 DisplayError(buf, 0);
2313 if (appData.debugMode) {
2314 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2315 e, wnum, VariantName(v));
2320 static int leftover_start = 0, leftover_len = 0;
2321 char star_match[STAR_MATCH_N][MSG_SIZ];
2323 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2324 advance *index beyond it, and set leftover_start to the new value of
2325 *index; else return FALSE. If pattern contains the character '*', it
2326 matches any sequence of characters not containing '\r', '\n', or the
2327 character following the '*' (if any), and the matched sequence(s) are
2328 copied into star_match.
2331 looking_at ( char *buf, int *index, char *pattern)
2333 char *bufp = &buf[*index], *patternp = pattern;
2335 char *matchp = star_match[0];
2338 if (*patternp == NULLCHAR) {
2339 *index = leftover_start = bufp - buf;
2343 if (*bufp == NULLCHAR) return FALSE;
2344 if (*patternp == '*') {
2345 if (*bufp == *(patternp + 1)) {
2347 matchp = star_match[++star_count];
2351 } else if (*bufp == '\n' || *bufp == '\r') {
2353 if (*patternp == NULLCHAR)
2358 *matchp++ = *bufp++;
2362 if (*patternp != *bufp) return FALSE;
2369 SendToPlayer (char *data, int length)
2371 int error, outCount;
2372 outCount = OutputToProcess(NoProc, data, length, &error);
2373 if (outCount < length) {
2374 DisplayFatalError(_("Error writing to display"), error, 1);
2379 PackHolding (char packed[], char *holding)
2389 switch (runlength) {
2400 sprintf(q, "%d", runlength);
2412 /* Telnet protocol requests from the front end */
2414 TelnetRequest (unsigned char ddww, unsigned char option)
2416 unsigned char msg[3];
2417 int outCount, outError;
2419 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2421 if (appData.debugMode) {
2422 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2438 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2447 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2450 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2455 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2457 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2464 if (!appData.icsActive) return;
2465 TelnetRequest(TN_DO, TN_ECHO);
2471 if (!appData.icsActive) return;
2472 TelnetRequest(TN_DONT, TN_ECHO);
2476 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2478 /* put the holdings sent to us by the server on the board holdings area */
2479 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2483 if(gameInfo.holdingsWidth < 2) return;
2484 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2485 return; // prevent overwriting by pre-board holdings
2487 if( (int)lowestPiece >= BlackPawn ) {
2490 holdingsStartRow = BOARD_HEIGHT-1;
2493 holdingsColumn = BOARD_WIDTH-1;
2494 countsColumn = BOARD_WIDTH-2;
2495 holdingsStartRow = 0;
2499 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2500 board[i][holdingsColumn] = EmptySquare;
2501 board[i][countsColumn] = (ChessSquare) 0;
2503 while( (p=*holdings++) != NULLCHAR ) {
2504 piece = CharToPiece( ToUpper(p) );
2505 if(piece == EmptySquare) continue;
2506 /*j = (int) piece - (int) WhitePawn;*/
2507 j = PieceToNumber(piece);
2508 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2509 if(j < 0) continue; /* should not happen */
2510 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2511 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2512 board[holdingsStartRow+j*direction][countsColumn]++;
2518 VariantSwitch (Board board, VariantClass newVariant)
2520 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2521 static Board oldBoard;
2523 startedFromPositionFile = FALSE;
2524 if(gameInfo.variant == newVariant) return;
2526 /* [HGM] This routine is called each time an assignment is made to
2527 * gameInfo.variant during a game, to make sure the board sizes
2528 * are set to match the new variant. If that means adding or deleting
2529 * holdings, we shift the playing board accordingly
2530 * This kludge is needed because in ICS observe mode, we get boards
2531 * of an ongoing game without knowing the variant, and learn about the
2532 * latter only later. This can be because of the move list we requested,
2533 * in which case the game history is refilled from the beginning anyway,
2534 * but also when receiving holdings of a crazyhouse game. In the latter
2535 * case we want to add those holdings to the already received position.
2539 if (appData.debugMode) {
2540 fprintf(debugFP, "Switch board from %s to %s\n",
2541 VariantName(gameInfo.variant), VariantName(newVariant));
2542 setbuf(debugFP, NULL);
2544 shuffleOpenings = 0; /* [HGM] shuffle */
2545 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2549 newWidth = 9; newHeight = 9;
2550 gameInfo.holdingsSize = 7;
2551 case VariantBughouse:
2552 case VariantCrazyhouse:
2553 newHoldingsWidth = 2; break;
2557 newHoldingsWidth = 2;
2558 gameInfo.holdingsSize = 8;
2561 case VariantCapablanca:
2562 case VariantCapaRandom:
2565 newHoldingsWidth = gameInfo.holdingsSize = 0;
2568 if(newWidth != gameInfo.boardWidth ||
2569 newHeight != gameInfo.boardHeight ||
2570 newHoldingsWidth != gameInfo.holdingsWidth ) {
2572 /* shift position to new playing area, if needed */
2573 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2574 for(i=0; i<BOARD_HEIGHT; i++)
2575 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2576 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2578 for(i=0; i<newHeight; i++) {
2579 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2580 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2582 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2583 for(i=0; i<BOARD_HEIGHT; i++)
2584 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2585 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2588 board[HOLDINGS_SET] = 0;
2589 gameInfo.boardWidth = newWidth;
2590 gameInfo.boardHeight = newHeight;
2591 gameInfo.holdingsWidth = newHoldingsWidth;
2592 gameInfo.variant = newVariant;
2593 InitDrawingSizes(-2, 0);
2594 } else gameInfo.variant = newVariant;
2595 CopyBoard(oldBoard, board); // remember correctly formatted board
2596 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2597 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2600 static int loggedOn = FALSE;
2602 /*-- Game start info cache: --*/
2604 char gs_kind[MSG_SIZ];
2605 static char player1Name[128] = "";
2606 static char player2Name[128] = "";
2607 static char cont_seq[] = "\n\\ ";
2608 static int player1Rating = -1;
2609 static int player2Rating = -1;
2610 /*----------------------------*/
2612 ColorClass curColor = ColorNormal;
2613 int suppressKibitz = 0;
2616 Boolean soughtPending = FALSE;
2617 Boolean seekGraphUp;
2618 #define MAX_SEEK_ADS 200
2620 char *seekAdList[MAX_SEEK_ADS];
2621 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2622 float tcList[MAX_SEEK_ADS];
2623 char colorList[MAX_SEEK_ADS];
2624 int nrOfSeekAds = 0;
2625 int minRating = 1010, maxRating = 2800;
2626 int hMargin = 10, vMargin = 20, h, w;
2627 extern int squareSize, lineGap;
2632 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2633 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2634 if(r < minRating+100 && r >=0 ) r = minRating+100;
2635 if(r > maxRating) r = maxRating;
2636 if(tc < 1.f) tc = 1.f;
2637 if(tc > 95.f) tc = 95.f;
2638 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2639 y = ((double)r - minRating)/(maxRating - minRating)
2640 * (h-vMargin-squareSize/8-1) + vMargin;
2641 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2642 if(strstr(seekAdList[i], " u ")) color = 1;
2643 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2644 !strstr(seekAdList[i], "bullet") &&
2645 !strstr(seekAdList[i], "blitz") &&
2646 !strstr(seekAdList[i], "standard") ) color = 2;
2647 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2648 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2652 PlotSingleSeekAd (int i)
2658 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2660 char buf[MSG_SIZ], *ext = "";
2661 VariantClass v = StringToVariant(type);
2662 if(strstr(type, "wild")) {
2663 ext = type + 4; // append wild number
2664 if(v == VariantFischeRandom) type = "chess960"; else
2665 if(v == VariantLoadable) type = "setup"; else
2666 type = VariantName(v);
2668 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2669 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2670 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2671 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2672 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2673 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2674 seekNrList[nrOfSeekAds] = nr;
2675 zList[nrOfSeekAds] = 0;
2676 seekAdList[nrOfSeekAds++] = StrSave(buf);
2677 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2682 EraseSeekDot (int i)
2684 int x = xList[i], y = yList[i], d=squareSize/4, k;
2685 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2686 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2687 // now replot every dot that overlapped
2688 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2689 int xx = xList[k], yy = yList[k];
2690 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2691 DrawSeekDot(xx, yy, colorList[k]);
2696 RemoveSeekAd (int nr)
2699 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2701 if(seekAdList[i]) free(seekAdList[i]);
2702 seekAdList[i] = seekAdList[--nrOfSeekAds];
2703 seekNrList[i] = seekNrList[nrOfSeekAds];
2704 ratingList[i] = ratingList[nrOfSeekAds];
2705 colorList[i] = colorList[nrOfSeekAds];
2706 tcList[i] = tcList[nrOfSeekAds];
2707 xList[i] = xList[nrOfSeekAds];
2708 yList[i] = yList[nrOfSeekAds];
2709 zList[i] = zList[nrOfSeekAds];
2710 seekAdList[nrOfSeekAds] = NULL;
2716 MatchSoughtLine (char *line)
2718 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2719 int nr, base, inc, u=0; char dummy;
2721 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2722 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2724 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2725 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2726 // match: compact and save the line
2727 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2737 if(!seekGraphUp) return FALSE;
2738 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2739 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap + 2*border;
2741 DrawSeekBackground(0, 0, w, h);
2742 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2743 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2744 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2745 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2747 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2750 snprintf(buf, MSG_SIZ, "%d", i);
2751 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2754 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2755 for(i=1; i<100; i+=(i<10?1:5)) {
2756 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2757 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2758 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2760 snprintf(buf, MSG_SIZ, "%d", i);
2761 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2764 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2769 SeekGraphClick (ClickType click, int x, int y, int moving)
2771 static int lastDown = 0, displayed = 0, lastSecond;
2772 if(y < 0) return FALSE;
2773 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2774 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2775 if(!seekGraphUp) return FALSE;
2776 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2777 DrawPosition(TRUE, NULL);
2780 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2781 if(click == Release || moving) return FALSE;
2783 soughtPending = TRUE;
2784 SendToICS(ics_prefix);
2785 SendToICS("sought\n"); // should this be "sought all"?
2786 } else { // issue challenge based on clicked ad
2787 int dist = 10000; int i, closest = 0, second = 0;
2788 for(i=0; i<nrOfSeekAds; i++) {
2789 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2790 if(d < dist) { dist = d; closest = i; }
2791 second += (d - zList[i] < 120); // count in-range ads
2792 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2796 second = (second > 1);
2797 if(displayed != closest || second != lastSecond) {
2798 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2799 lastSecond = second; displayed = closest;
2801 if(click == Press) {
2802 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2805 } // on press 'hit', only show info
2806 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2807 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2808 SendToICS(ics_prefix);
2810 return TRUE; // let incoming board of started game pop down the graph
2811 } else if(click == Release) { // release 'miss' is ignored
2812 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2813 if(moving == 2) { // right up-click
2814 nrOfSeekAds = 0; // refresh graph
2815 soughtPending = TRUE;
2816 SendToICS(ics_prefix);
2817 SendToICS("sought\n"); // should this be "sought all"?
2820 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2821 // press miss or release hit 'pop down' seek graph
2822 seekGraphUp = FALSE;
2823 DrawPosition(TRUE, NULL);
2829 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2831 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2832 #define STARTED_NONE 0
2833 #define STARTED_MOVES 1
2834 #define STARTED_BOARD 2
2835 #define STARTED_OBSERVE 3
2836 #define STARTED_HOLDINGS 4
2837 #define STARTED_CHATTER 5
2838 #define STARTED_COMMENT 6
2839 #define STARTED_MOVES_NOHIDE 7
2841 static int started = STARTED_NONE;
2842 static char parse[20000];
2843 static int parse_pos = 0;
2844 static char buf[BUF_SIZE + 1];
2845 static int firstTime = TRUE, intfSet = FALSE;
2846 static ColorClass prevColor = ColorNormal;
2847 static int savingComment = FALSE;
2848 static int cmatch = 0; // continuation sequence match
2855 int backup; /* [DM] For zippy color lines */
2857 char talker[MSG_SIZ]; // [HGM] chat
2858 int channel, collective=0;
2860 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2862 if (appData.debugMode) {
2864 fprintf(debugFP, "<ICS: ");
2865 show_bytes(debugFP, data, count);
2866 fprintf(debugFP, "\n");
2870 if (appData.debugMode) { int f = forwardMostMove;
2871 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2872 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2873 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2876 /* If last read ended with a partial line that we couldn't parse,
2877 prepend it to the new read and try again. */
2878 if (leftover_len > 0) {
2879 for (i=0; i<leftover_len; i++)
2880 buf[i] = buf[leftover_start + i];
2883 /* copy new characters into the buffer */
2884 bp = buf + leftover_len;
2885 buf_len=leftover_len;
2886 for (i=0; i<count; i++)
2889 if (data[i] == '\r')
2892 // join lines split by ICS?
2893 if (!appData.noJoin)
2896 Joining just consists of finding matches against the
2897 continuation sequence, and discarding that sequence
2898 if found instead of copying it. So, until a match
2899 fails, there's nothing to do since it might be the
2900 complete sequence, and thus, something we don't want
2903 if (data[i] == cont_seq[cmatch])
2906 if (cmatch == strlen(cont_seq))
2908 cmatch = 0; // complete match. just reset the counter
2911 it's possible for the ICS to not include the space
2912 at the end of the last word, making our [correct]
2913 join operation fuse two separate words. the server
2914 does this when the space occurs at the width setting.
2916 if (!buf_len || buf[buf_len-1] != ' ')
2927 match failed, so we have to copy what matched before
2928 falling through and copying this character. In reality,
2929 this will only ever be just the newline character, but
2930 it doesn't hurt to be precise.
2932 strncpy(bp, cont_seq, cmatch);
2944 buf[buf_len] = NULLCHAR;
2945 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2950 while (i < buf_len) {
2951 /* Deal with part of the TELNET option negotiation
2952 protocol. We refuse to do anything beyond the
2953 defaults, except that we allow the WILL ECHO option,
2954 which ICS uses to turn off password echoing when we are
2955 directly connected to it. We reject this option
2956 if localLineEditing mode is on (always on in xboard)
2957 and we are talking to port 23, which might be a real
2958 telnet server that will try to keep WILL ECHO on permanently.
2960 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2961 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2962 unsigned char option;
2964 switch ((unsigned char) buf[++i]) {
2966 if (appData.debugMode)
2967 fprintf(debugFP, "\n<WILL ");
2968 switch (option = (unsigned char) buf[++i]) {
2970 if (appData.debugMode)
2971 fprintf(debugFP, "ECHO ");
2972 /* Reply only if this is a change, according
2973 to the protocol rules. */
2974 if (remoteEchoOption) break;
2975 if (appData.localLineEditing &&
2976 atoi(appData.icsPort) == TN_PORT) {
2977 TelnetRequest(TN_DONT, TN_ECHO);
2980 TelnetRequest(TN_DO, TN_ECHO);
2981 remoteEchoOption = TRUE;
2985 if (appData.debugMode)
2986 fprintf(debugFP, "%d ", option);
2987 /* Whatever this is, we don't want it. */
2988 TelnetRequest(TN_DONT, option);
2993 if (appData.debugMode)
2994 fprintf(debugFP, "\n<WONT ");
2995 switch (option = (unsigned char) buf[++i]) {
2997 if (appData.debugMode)
2998 fprintf(debugFP, "ECHO ");
2999 /* Reply only if this is a change, according
3000 to the protocol rules. */
3001 if (!remoteEchoOption) break;
3003 TelnetRequest(TN_DONT, TN_ECHO);
3004 remoteEchoOption = FALSE;
3007 if (appData.debugMode)
3008 fprintf(debugFP, "%d ", (unsigned char) option);
3009 /* Whatever this is, it must already be turned
3010 off, because we never agree to turn on
3011 anything non-default, so according to the
3012 protocol rules, we don't reply. */
3017 if (appData.debugMode)
3018 fprintf(debugFP, "\n<DO ");
3019 switch (option = (unsigned char) buf[++i]) {
3021 /* Whatever this is, we refuse to do it. */
3022 if (appData.debugMode)
3023 fprintf(debugFP, "%d ", option);
3024 TelnetRequest(TN_WONT, option);
3029 if (appData.debugMode)
3030 fprintf(debugFP, "\n<DONT ");
3031 switch (option = (unsigned char) buf[++i]) {
3033 if (appData.debugMode)
3034 fprintf(debugFP, "%d ", option);
3035 /* Whatever this is, we are already not doing
3036 it, because we never agree to do anything
3037 non-default, so according to the protocol
3038 rules, we don't reply. */
3043 if (appData.debugMode)
3044 fprintf(debugFP, "\n<IAC ");
3045 /* Doubled IAC; pass it through */
3049 if (appData.debugMode)
3050 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3051 /* Drop all other telnet commands on the floor */
3054 if (oldi > next_out)
3055 SendToPlayer(&buf[next_out], oldi - next_out);
3061 /* OK, this at least will *usually* work */
3062 if (!loggedOn && looking_at(buf, &i, "ics%")) {
3066 if (loggedOn && !intfSet) {
3067 if (ics_type == ICS_ICC) {
3068 snprintf(str, MSG_SIZ,
3069 "/set-quietly interface %s\n/set-quietly style 12\n",
3071 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3072 strcat(str, "/set-2 51 1\n/set seek 1\n");
3073 } else if (ics_type == ICS_CHESSNET) {
3074 snprintf(str, MSG_SIZ, "/style 12\n");
3076 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3077 strcat(str, programVersion);
3078 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3079 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3080 strcat(str, "$iset seekremove 1\n$set seek 1\n");
3082 strcat(str, "$iset nohighlight 1\n");
3084 strcat(str, "$iset lock 1\n$style 12\n");
3087 NotifyFrontendLogin();
3091 if (started == STARTED_COMMENT) {
3092 /* Accumulate characters in comment */
3093 parse[parse_pos++] = buf[i];
3094 if (buf[i] == '\n') {
3095 parse[parse_pos] = NULLCHAR;
3096 if(chattingPartner>=0) {
3098 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3099 OutputChatMessage(chattingPartner, mess);
3100 if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3102 talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3103 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3104 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3105 OutputChatMessage(p, mess);
3109 chattingPartner = -1;
3110 if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3113 if(!suppressKibitz) // [HGM] kibitz
3114 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3115 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3116 int nrDigit = 0, nrAlph = 0, j;
3117 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3118 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3119 parse[parse_pos] = NULLCHAR;
3120 // try to be smart: if it does not look like search info, it should go to
3121 // ICS interaction window after all, not to engine-output window.
3122 for(j=0; j<parse_pos; j++) { // count letters and digits
3123 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3124 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3125 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3127 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3128 int depth=0; float score;
3129 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3130 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3131 pvInfoList[forwardMostMove-1].depth = depth;
3132 pvInfoList[forwardMostMove-1].score = 100*score;
3134 OutputKibitz(suppressKibitz, parse);
3137 if(gameMode == IcsObserving) // restore original ICS messages
3138 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3139 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3141 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3142 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3143 SendToPlayer(tmp, strlen(tmp));
3145 next_out = i+1; // [HGM] suppress printing in ICS window
3147 started = STARTED_NONE;
3149 /* Don't match patterns against characters in comment */
3154 if (started == STARTED_CHATTER) {
3155 if (buf[i] != '\n') {
3156 /* Don't match patterns against characters in chatter */
3160 started = STARTED_NONE;
3161 if(suppressKibitz) next_out = i+1;
3164 /* Kludge to deal with rcmd protocol */
3165 if (firstTime && looking_at(buf, &i, "\001*")) {
3166 DisplayFatalError(&buf[1], 0, 1);
3172 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3175 if (appData.debugMode)
3176 fprintf(debugFP, "ics_type %d\n", ics_type);
3179 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3180 ics_type = ICS_FICS;
3182 if (appData.debugMode)
3183 fprintf(debugFP, "ics_type %d\n", ics_type);
3186 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3187 ics_type = ICS_CHESSNET;
3189 if (appData.debugMode)
3190 fprintf(debugFP, "ics_type %d\n", ics_type);
3195 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3196 looking_at(buf, &i, "Logging you in as \"*\"") ||
3197 looking_at(buf, &i, "will be \"*\""))) {
3198 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3202 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3204 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3205 DisplayIcsInteractionTitle(buf);
3206 have_set_title = TRUE;
3209 /* skip finger notes */
3210 if (started == STARTED_NONE &&
3211 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3212 (buf[i] == '1' && buf[i+1] == '0')) &&
3213 buf[i+2] == ':' && buf[i+3] == ' ') {
3214 started = STARTED_CHATTER;
3220 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3221 if(appData.seekGraph) {
3222 if(soughtPending && MatchSoughtLine(buf+i)) {
3223 i = strstr(buf+i, "rated") - buf;
3224 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3225 next_out = leftover_start = i;
3226 started = STARTED_CHATTER;
3227 suppressKibitz = TRUE;
3230 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3231 && looking_at(buf, &i, "* ads displayed")) {
3232 soughtPending = FALSE;
3237 if(appData.autoRefresh) {
3238 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3239 int s = (ics_type == ICS_ICC); // ICC format differs
3241 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3242 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3243 looking_at(buf, &i, "*% "); // eat prompt
3244 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3245 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3246 next_out = i; // suppress
3249 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3250 char *p = star_match[0];
3252 if(seekGraphUp) RemoveSeekAd(atoi(p));
3253 while(*p && *p++ != ' '); // next
3255 looking_at(buf, &i, "*% "); // eat prompt
3256 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3263 /* skip formula vars */
3264 if (started == STARTED_NONE &&
3265 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3266 started = STARTED_CHATTER;
3271 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3272 if (appData.autoKibitz && started == STARTED_NONE &&
3273 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3274 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3275 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3276 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3277 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3278 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3279 suppressKibitz = TRUE;
3280 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3282 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3283 && (gameMode == IcsPlayingWhite)) ||
3284 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3285 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3286 started = STARTED_CHATTER; // own kibitz we simply discard
3288 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3289 parse_pos = 0; parse[0] = NULLCHAR;
3290 savingComment = TRUE;
3291 suppressKibitz = gameMode != IcsObserving ? 2 :
3292 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3296 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3297 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3298 && atoi(star_match[0])) {
3299 // suppress the acknowledgements of our own autoKibitz
3301 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3302 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3303 SendToPlayer(star_match[0], strlen(star_match[0]));
3304 if(looking_at(buf, &i, "*% ")) // eat prompt
3305 suppressKibitz = FALSE;
3309 } // [HGM] kibitz: end of patch
3311 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3313 // [HGM] chat: intercept tells by users for which we have an open chat window
3315 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3316 looking_at(buf, &i, "* whispers:") ||
3317 looking_at(buf, &i, "* kibitzes:") ||
3318 looking_at(buf, &i, "* shouts:") ||
3319 looking_at(buf, &i, "* c-shouts:") ||
3320 looking_at(buf, &i, "--> * ") ||
3321 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3322 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3323 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3324 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3326 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3327 chattingPartner = -1; collective = 0;
3329 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3330 for(p=0; p<MAX_CHAT; p++) {
3332 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3333 talker[0] = '['; strcat(talker, "] ");
3334 Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3335 chattingPartner = p; break;
3338 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3339 for(p=0; p<MAX_CHAT; p++) {
3341 if(!strcmp("kibitzes", chatPartner[p])) {
3342 talker[0] = '['; strcat(talker, "] ");
3343 chattingPartner = p; break;
3346 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3347 for(p=0; p<MAX_CHAT; p++) {
3349 if(!strcmp("whispers", chatPartner[p])) {
3350 talker[0] = '['; strcat(talker, "] ");
3351 chattingPartner = p; break;
3354 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3355 if(buf[i-8] == '-' && buf[i-3] == 't')
3356 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3358 if(!strcmp("c-shouts", chatPartner[p])) {
3359 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3360 chattingPartner = p; break;
3363 if(chattingPartner < 0)
3364 for(p=0; p<MAX_CHAT; p++) {
3366 if(!strcmp("shouts", chatPartner[p])) {
3367 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3368 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3369 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3370 chattingPartner = p; break;
3374 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3375 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3377 Colorize(ColorTell, FALSE);
3378 if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3380 chattingPartner = p; break;
3382 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3383 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3384 started = STARTED_COMMENT;
3385 parse_pos = 0; parse[0] = NULLCHAR;
3386 savingComment = 3 + chattingPartner; // counts as TRUE
3387 if(collective == 3) i = oldi; else {
3388 suppressKibitz = TRUE;
3389 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3390 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3394 } // [HGM] chat: end of patch
3397 if (appData.zippyTalk || appData.zippyPlay) {
3398 /* [DM] Backup address for color zippy lines */
3400 if (loggedOn == TRUE)
3401 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3402 (appData.zippyPlay && ZippyMatch(buf, &backup)))
3405 } // [DM] 'else { ' deleted
3407 /* Regular tells and says */
3408 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3409 looking_at(buf, &i, "* (your partner) tells you: ") ||
3410 looking_at(buf, &i, "* says: ") ||
3411 /* Don't color "message" or "messages" output */
3412 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3413 looking_at(buf, &i, "*. * at *:*: ") ||
3414 looking_at(buf, &i, "--* (*:*): ") ||
3415 /* Message notifications (same color as tells) */
3416 looking_at(buf, &i, "* has left a message ") ||
3417 looking_at(buf, &i, "* just sent you a message:\n") ||
3418 /* Whispers and kibitzes */
3419 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3420 looking_at(buf, &i, "* kibitzes: ") ||
3422 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3424 if (tkind == 1 && strchr(star_match[0], ':')) {
3425 /* Avoid "tells you:" spoofs in channels */
3428 if (star_match[0][0] == NULLCHAR ||
3429 strchr(star_match[0], ' ') ||
3430 (tkind == 3 && strchr(star_match[1], ' '))) {
3431 /* Reject bogus matches */
3434 if (appData.colorize) {
3435 if (oldi > next_out) {
3436 SendToPlayer(&buf[next_out], oldi - next_out);
3441 Colorize(ColorTell, FALSE);
3442 curColor = ColorTell;
3445 Colorize(ColorKibitz, FALSE);
3446 curColor = ColorKibitz;
3449 p = strrchr(star_match[1], '(');
3456 Colorize(ColorChannel1, FALSE);
3457 curColor = ColorChannel1;
3459 Colorize(ColorChannel, FALSE);
3460 curColor = ColorChannel;
3464 curColor = ColorNormal;
3468 if (started == STARTED_NONE && appData.autoComment &&
3469 (gameMode == IcsObserving ||
3470 gameMode == IcsPlayingWhite ||
3471 gameMode == IcsPlayingBlack)) {
3472 parse_pos = i - oldi;
3473 memcpy(parse, &buf[oldi], parse_pos);
3474 parse[parse_pos] = NULLCHAR;
3475 started = STARTED_COMMENT;
3476 savingComment = TRUE;
3477 } else if(collective != 3) {
3478 started = STARTED_CHATTER;
3479 savingComment = FALSE;
3486 if (looking_at(buf, &i, "* s-shouts: ") ||
3487 looking_at(buf, &i, "* c-shouts: ")) {
3488 if (appData.colorize) {
3489 if (oldi > next_out) {
3490 SendToPlayer(&buf[next_out], oldi - next_out);
3493 Colorize(ColorSShout, FALSE);
3494 curColor = ColorSShout;
3497 started = STARTED_CHATTER;
3501 if (looking_at(buf, &i, "--->")) {
3506 if (looking_at(buf, &i, "* shouts: ") ||
3507 looking_at(buf, &i, "--> ")) {
3508 if (appData.colorize) {
3509 if (oldi > next_out) {
3510 SendToPlayer(&buf[next_out], oldi - next_out);
3513 Colorize(ColorShout, FALSE);
3514 curColor = ColorShout;
3517 started = STARTED_CHATTER;
3521 if (looking_at( buf, &i, "Challenge:")) {
3522 if (appData.colorize) {
3523 if (oldi > next_out) {
3524 SendToPlayer(&buf[next_out], oldi - next_out);
3527 Colorize(ColorChallenge, FALSE);
3528 curColor = ColorChallenge;
3534 if (looking_at(buf, &i, "* offers you") ||
3535 looking_at(buf, &i, "* offers to be") ||
3536 looking_at(buf, &i, "* would like to") ||
3537 looking_at(buf, &i, "* requests to") ||
3538 looking_at(buf, &i, "Your opponent offers") ||
3539 looking_at(buf, &i, "Your opponent requests")) {
3541 if (appData.colorize) {
3542 if (oldi > next_out) {
3543 SendToPlayer(&buf[next_out], oldi - next_out);
3546 Colorize(ColorRequest, FALSE);
3547 curColor = ColorRequest;
3552 if (looking_at(buf, &i, "* (*) seeking")) {
3553 if (appData.colorize) {
3554 if (oldi > next_out) {
3555 SendToPlayer(&buf[next_out], oldi - next_out);
3558 Colorize(ColorSeek, FALSE);
3559 curColor = ColorSeek;
3564 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3566 if (looking_at(buf, &i, "\\ ")) {
3567 if (prevColor != ColorNormal) {
3568 if (oldi > next_out) {
3569 SendToPlayer(&buf[next_out], oldi - next_out);
3572 Colorize(prevColor, TRUE);
3573 curColor = prevColor;
3575 if (savingComment) {
3576 parse_pos = i - oldi;
3577 memcpy(parse, &buf[oldi], parse_pos);
3578 parse[parse_pos] = NULLCHAR;
3579 started = STARTED_COMMENT;
3580 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3581 chattingPartner = savingComment - 3; // kludge to remember the box
3583 started = STARTED_CHATTER;
3588 if (looking_at(buf, &i, "Black Strength :") ||
3589 looking_at(buf, &i, "<<< style 10 board >>>") ||
3590 looking_at(buf, &i, "<10>") ||
3591 looking_at(buf, &i, "#@#")) {
3592 /* Wrong board style */
3594 SendToICS(ics_prefix);
3595 SendToICS("set style 12\n");
3596 SendToICS(ics_prefix);
3597 SendToICS("refresh\n");
3601 if (looking_at(buf, &i, "login:")) {
3602 if (!have_sent_ICS_logon) {
3604 have_sent_ICS_logon = 1;
3605 else // no init script was found
3606 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3607 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3608 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3613 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3614 (looking_at(buf, &i, "\n<12> ") ||
3615 looking_at(buf, &i, "<12> "))) {
3617 if (oldi > next_out) {
3618 SendToPlayer(&buf[next_out], oldi - next_out);
3621 started = STARTED_BOARD;
3626 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3627 looking_at(buf, &i, "<b1> ")) {
3628 if (oldi > next_out) {
3629 SendToPlayer(&buf[next_out], oldi - next_out);
3632 started = STARTED_HOLDINGS;
3637 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3639 /* Header for a move list -- first line */
3641 switch (ics_getting_history) {
3645 case BeginningOfGame:
3646 /* User typed "moves" or "oldmoves" while we
3647 were idle. Pretend we asked for these
3648 moves and soak them up so user can step
3649 through them and/or save them.
3652 gameMode = IcsObserving;
3655 ics_getting_history = H_GOT_UNREQ_HEADER;
3657 case EditGame: /*?*/
3658 case EditPosition: /*?*/
3659 /* Should above feature work in these modes too? */
3660 /* For now it doesn't */
3661 ics_getting_history = H_GOT_UNWANTED_HEADER;
3664 ics_getting_history = H_GOT_UNWANTED_HEADER;
3669 /* Is this the right one? */
3670 if (gameInfo.white && gameInfo.black &&
3671 strcmp(gameInfo.white, star_match[0]) == 0 &&
3672 strcmp(gameInfo.black, star_match[2]) == 0) {
3674 ics_getting_history = H_GOT_REQ_HEADER;
3677 case H_GOT_REQ_HEADER:
3678 case H_GOT_UNREQ_HEADER:
3679 case H_GOT_UNWANTED_HEADER:
3680 case H_GETTING_MOVES:
3681 /* Should not happen */
3682 DisplayError(_("Error gathering move list: two headers"), 0);
3683 ics_getting_history = H_FALSE;
3687 /* Save player ratings into gameInfo if needed */
3688 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3689 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3690 (gameInfo.whiteRating == -1 ||
3691 gameInfo.blackRating == -1)) {
3693 gameInfo.whiteRating = string_to_rating(star_match[1]);
3694 gameInfo.blackRating = string_to_rating(star_match[3]);
3695 if (appData.debugMode)
3696 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3697 gameInfo.whiteRating, gameInfo.blackRating);
3702 if (looking_at(buf, &i,
3703 "* * match, initial time: * minute*, increment: * second")) {
3704 /* Header for a move list -- second line */
3705 /* Initial board will follow if this is a wild game */
3706 if (gameInfo.event != NULL) free(gameInfo.event);
3707 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3708 gameInfo.event = StrSave(str);
3709 /* [HGM] we switched variant. Translate boards if needed. */
3710 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3714 if (looking_at(buf, &i, "Move ")) {
3715 /* Beginning of a move list */
3716 switch (ics_getting_history) {
3718 /* Normally should not happen */
3719 /* Maybe user hit reset while we were parsing */
3722 /* Happens if we are ignoring a move list that is not
3723 * the one we just requested. Common if the user
3724 * tries to observe two games without turning off
3727 case H_GETTING_MOVES:
3728 /* Should not happen */
3729 DisplayError(_("Error gathering move list: nested"), 0);
3730 ics_getting_history = H_FALSE;
3732 case H_GOT_REQ_HEADER:
3733 ics_getting_history = H_GETTING_MOVES;
3734 started = STARTED_MOVES;
3736 if (oldi > next_out) {
3737 SendToPlayer(&buf[next_out], oldi - next_out);
3740 case H_GOT_UNREQ_HEADER:
3741 ics_getting_history = H_GETTING_MOVES;
3742 started = STARTED_MOVES_NOHIDE;
3745 case H_GOT_UNWANTED_HEADER:
3746 ics_getting_history = H_FALSE;
3752 if (looking_at(buf, &i, "% ") ||
3753 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3754 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3755 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3756 soughtPending = FALSE;
3760 if(suppressKibitz) next_out = i;
3761 savingComment = FALSE;
3765 case STARTED_MOVES_NOHIDE:
3766 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3767 parse[parse_pos + i - oldi] = NULLCHAR;
3768 ParseGameHistory(parse);
3770 if (appData.zippyPlay && first.initDone) {
3771 FeedMovesToProgram(&first, forwardMostMove);
3772 if (gameMode == IcsPlayingWhite) {
3773 if (WhiteOnMove(forwardMostMove)) {
3774 if (first.sendTime) {
3775 if (first.useColors) {
3776 SendToProgram("black\n", &first);
3778 SendTimeRemaining(&first, TRUE);
3780 if (first.useColors) {
3781 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3783 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3784 first.maybeThinking = TRUE;
3786 if (first.usePlayother) {
3787 if (first.sendTime) {
3788 SendTimeRemaining(&first, TRUE);
3790 SendToProgram("playother\n", &first);
3796 } else if (gameMode == IcsPlayingBlack) {
3797 if (!WhiteOnMove(forwardMostMove)) {
3798 if (first.sendTime) {
3799 if (first.useColors) {
3800 SendToProgram("white\n", &first);
3802 SendTimeRemaining(&first, FALSE);
3804 if (first.useColors) {
3805 SendToProgram("black\n", &first);
3807 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3808 first.maybeThinking = TRUE;
3810 if (first.usePlayother) {
3811 if (first.sendTime) {
3812 SendTimeRemaining(&first, FALSE);
3814 SendToProgram("playother\n", &first);
3823 if (gameMode == IcsObserving && ics_gamenum == -1) {
3824 /* Moves came from oldmoves or moves command
3825 while we weren't doing anything else.
3827 currentMove = forwardMostMove;
3828 ClearHighlights();/*!!could figure this out*/
3829 flipView = appData.flipView;
3830 DrawPosition(TRUE, boards[currentMove]);
3831 DisplayBothClocks();
3832 snprintf(str, MSG_SIZ, "%s %s %s",
3833 gameInfo.white, _("vs."), gameInfo.black);
3837 /* Moves were history of an active game */
3838 if (gameInfo.resultDetails != NULL) {
3839 free(gameInfo.resultDetails);
3840 gameInfo.resultDetails = NULL;
3843 HistorySet(parseList, backwardMostMove,
3844 forwardMostMove, currentMove-1);
3845 DisplayMove(currentMove - 1);
3846 if (started == STARTED_MOVES) next_out = i;
3847 started = STARTED_NONE;
3848 ics_getting_history = H_FALSE;
3851 case STARTED_OBSERVE:
3852 started = STARTED_NONE;
3853 SendToICS(ics_prefix);
3854 SendToICS("refresh\n");
3860 if(bookHit) { // [HGM] book: simulate book reply
3861 static char bookMove[MSG_SIZ]; // a bit generous?
3863 programStats.nodes = programStats.depth = programStats.time =
3864 programStats.score = programStats.got_only_move = 0;
3865 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3867 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3868 strcat(bookMove, bookHit);
3869 HandleMachineMove(bookMove, &first);
3874 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3875 started == STARTED_HOLDINGS ||
3876 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3877 /* Accumulate characters in move list or board */
3878 parse[parse_pos++] = buf[i];
3881 /* Start of game messages. Mostly we detect start of game
3882 when the first board image arrives. On some versions
3883 of the ICS, though, we need to do a "refresh" after starting
3884 to observe in order to get the current board right away. */
3885 if (looking_at(buf, &i, "Adding game * to observation list")) {
3886 started = STARTED_OBSERVE;
3890 /* Handle auto-observe */
3891 if (appData.autoObserve &&
3892 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3893 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3895 /* Choose the player that was highlighted, if any. */
3896 if (star_match[0][0] == '\033' ||
3897 star_match[1][0] != '\033') {
3898 player = star_match[0];
3900 player = star_match[2];
3902 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3903 ics_prefix, StripHighlightAndTitle(player));
3906 /* Save ratings from notify string */
3907 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3908 player1Rating = string_to_rating(star_match[1]);
3909 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3910 player2Rating = string_to_rating(star_match[3]);
3912 if (appData.debugMode)
3914 "Ratings from 'Game notification:' %s %d, %s %d\n",
3915 player1Name, player1Rating,
3916 player2Name, player2Rating);
3921 /* Deal with automatic examine mode after a game,
3922 and with IcsObserving -> IcsExamining transition */
3923 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3924 looking_at(buf, &i, "has made you an examiner of game *")) {
3926 int gamenum = atoi(star_match[0]);
3927 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3928 gamenum == ics_gamenum) {
3929 /* We were already playing or observing this game;
3930 no need to refetch history */
3931 gameMode = IcsExamining;
3933 pauseExamForwardMostMove = forwardMostMove;
3934 } else if (currentMove < forwardMostMove) {
3935 ForwardInner(forwardMostMove);
3938 /* I don't think this case really can happen */
3939 SendToICS(ics_prefix);
3940 SendToICS("refresh\n");
3945 /* Error messages */
3946 // if (ics_user_moved) {
3947 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3948 if (looking_at(buf, &i, "Illegal move") ||
3949 looking_at(buf, &i, "Not a legal move") ||
3950 looking_at(buf, &i, "Your king is in check") ||
3951 looking_at(buf, &i, "It isn't your turn") ||
3952 looking_at(buf, &i, "It is not your move")) {
3954 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3955 currentMove = forwardMostMove-1;
3956 DisplayMove(currentMove - 1); /* before DMError */
3957 DrawPosition(FALSE, boards[currentMove]);
3958 SwitchClocks(forwardMostMove-1); // [HGM] race
3959 DisplayBothClocks();
3961 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3967 if (looking_at(buf, &i, "still have time") ||
3968 looking_at(buf, &i, "not out of time") ||
3969 looking_at(buf, &i, "either player is out of time") ||
3970 looking_at(buf, &i, "has timeseal; checking")) {
3971 /* We must have called his flag a little too soon */
3972 whiteFlag = blackFlag = FALSE;
3976 if (looking_at(buf, &i, "added * seconds to") ||
3977 looking_at(buf, &i, "seconds were added to")) {
3978 /* Update the clocks */
3979 SendToICS(ics_prefix);
3980 SendToICS("refresh\n");
3984 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3985 ics_clock_paused = TRUE;
3990 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3991 ics_clock_paused = FALSE;
3996 /* Grab player ratings from the Creating: message.
3997 Note we have to check for the special case when
3998 the ICS inserts things like [white] or [black]. */
3999 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
4000 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
4002 0 player 1 name (not necessarily white)
4004 2 empty, white, or black (IGNORED)
4005 3 player 2 name (not necessarily black)
4008 The names/ratings are sorted out when the game
4009 actually starts (below).
4011 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4012 player1Rating = string_to_rating(star_match[1]);
4013 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4014 player2Rating = string_to_rating(star_match[4]);
4016 if (appData.debugMode)
4018 "Ratings from 'Creating:' %s %d, %s %d\n",
4019 player1Name, player1Rating,
4020 player2Name, player2Rating);
4025 /* Improved generic start/end-of-game messages */
4026 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4027 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4028 /* If tkind == 0: */
4029 /* star_match[0] is the game number */
4030 /* [1] is the white player's name */
4031 /* [2] is the black player's name */
4032 /* For end-of-game: */
4033 /* [3] is the reason for the game end */
4034 /* [4] is a PGN end game-token, preceded by " " */
4035 /* For start-of-game: */
4036 /* [3] begins with "Creating" or "Continuing" */
4037 /* [4] is " *" or empty (don't care). */
4038 int gamenum = atoi(star_match[0]);
4039 char *whitename, *blackname, *why, *endtoken;
4040 ChessMove endtype = EndOfFile;
4043 whitename = star_match[1];
4044 blackname = star_match[2];
4045 why = star_match[3];
4046 endtoken = star_match[4];
4048 whitename = star_match[1];
4049 blackname = star_match[3];
4050 why = star_match[5];
4051 endtoken = star_match[6];
4054 /* Game start messages */
4055 if (strncmp(why, "Creating ", 9) == 0 ||
4056 strncmp(why, "Continuing ", 11) == 0) {
4057 gs_gamenum = gamenum;
4058 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4059 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4060 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4062 if (appData.zippyPlay) {
4063 ZippyGameStart(whitename, blackname);
4066 partnerBoardValid = FALSE; // [HGM] bughouse
4070 /* Game end messages */
4071 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4072 ics_gamenum != gamenum) {
4075 while (endtoken[0] == ' ') endtoken++;
4076 switch (endtoken[0]) {
4079 endtype = GameUnfinished;
4082 endtype = BlackWins;
4085 if (endtoken[1] == '/')
4086 endtype = GameIsDrawn;
4088 endtype = WhiteWins;
4091 GameEnds(endtype, why, GE_ICS);
4093 if (appData.zippyPlay && first.initDone) {
4094 ZippyGameEnd(endtype, why);
4095 if (first.pr == NoProc) {
4096 /* Start the next process early so that we'll
4097 be ready for the next challenge */
4098 StartChessProgram(&first);
4100 /* Send "new" early, in case this command takes
4101 a long time to finish, so that we'll be ready
4102 for the next challenge. */
4103 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4107 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4111 if (looking_at(buf, &i, "Removing game * from observation") ||
4112 looking_at(buf, &i, "no longer observing game *") ||
4113 looking_at(buf, &i, "Game * (*) has no examiners")) {
4114 if (gameMode == IcsObserving &&
4115 atoi(star_match[0]) == ics_gamenum)
4117 /* icsEngineAnalyze */
4118 if (appData.icsEngineAnalyze) {
4125 ics_user_moved = FALSE;
4130 if (looking_at(buf, &i, "no longer examining game *")) {
4131 if (gameMode == IcsExamining &&
4132 atoi(star_match[0]) == ics_gamenum)
4136 ics_user_moved = FALSE;
4141 /* Advance leftover_start past any newlines we find,
4142 so only partial lines can get reparsed */
4143 if (looking_at(buf, &i, "\n")) {
4144 prevColor = curColor;
4145 if (curColor != ColorNormal) {
4146 if (oldi > next_out) {
4147 SendToPlayer(&buf[next_out], oldi - next_out);
4150 Colorize(ColorNormal, FALSE);
4151 curColor = ColorNormal;
4153 if (started == STARTED_BOARD) {
4154 started = STARTED_NONE;
4155 parse[parse_pos] = NULLCHAR;
4156 ParseBoard12(parse);
4159 /* Send premove here */
4160 if (appData.premove) {
4162 if (currentMove == 0 &&
4163 gameMode == IcsPlayingWhite &&
4164 appData.premoveWhite) {
4165 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4166 if (appData.debugMode)
4167 fprintf(debugFP, "Sending premove:\n");
4169 } else if (currentMove == 1 &&
4170 gameMode == IcsPlayingBlack &&
4171 appData.premoveBlack) {
4172 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4173 if (appData.debugMode)
4174 fprintf(debugFP, "Sending premove:\n");
4176 } else if (gotPremove) {
4177 int oldFMM = forwardMostMove;
4179 ClearPremoveHighlights();
4180 if (appData.debugMode)
4181 fprintf(debugFP, "Sending premove:\n");
4182 UserMoveEvent(premoveFromX, premoveFromY,
4183 premoveToX, premoveToY,
4185 if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4186 if(moveList[oldFMM-1][1] != '@')
4187 SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4188 moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4190 SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4195 /* Usually suppress following prompt */
4196 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4197 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4198 if (looking_at(buf, &i, "*% ")) {
4199 savingComment = FALSE;
4204 } else if (started == STARTED_HOLDINGS) {
4206 char new_piece[MSG_SIZ];
4207 started = STARTED_NONE;
4208 parse[parse_pos] = NULLCHAR;
4209 if (appData.debugMode)
4210 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4211 parse, currentMove);
4212 if (sscanf(parse, " game %d", &gamenum) == 1) {
4213 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4214 if (gameInfo.variant == VariantNormal) {
4215 /* [HGM] We seem to switch variant during a game!
4216 * Presumably no holdings were displayed, so we have
4217 * to move the position two files to the right to
4218 * create room for them!
4220 VariantClass newVariant;
4221 switch(gameInfo.boardWidth) { // base guess on board width
4222 case 9: newVariant = VariantShogi; break;
4223 case 10: newVariant = VariantGreat; break;
4224 default: newVariant = VariantCrazyhouse; break;
4226 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4227 /* Get a move list just to see the header, which
4228 will tell us whether this is really bug or zh */
4229 if (ics_getting_history == H_FALSE) {
4230 ics_getting_history = H_REQUESTED;
4231 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4235 new_piece[0] = NULLCHAR;
4236 sscanf(parse, "game %d white [%s black [%s <- %s",
4237 &gamenum, white_holding, black_holding,
4239 white_holding[strlen(white_holding)-1] = NULLCHAR;
4240 black_holding[strlen(black_holding)-1] = NULLCHAR;
4241 /* [HGM] copy holdings to board holdings area */
4242 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4243 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4244 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4246 if (appData.zippyPlay && first.initDone) {
4247 ZippyHoldings(white_holding, black_holding,
4251 if (tinyLayout || smallLayout) {
4252 char wh[16], bh[16];
4253 PackHolding(wh, white_holding);
4254 PackHolding(bh, black_holding);
4255 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4256 gameInfo.white, gameInfo.black);
4258 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4259 gameInfo.white, white_holding, _("vs."),
4260 gameInfo.black, black_holding);
4262 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4263 DrawPosition(FALSE, boards[currentMove]);
4265 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4266 sscanf(parse, "game %d white [%s black [%s <- %s",
4267 &gamenum, white_holding, black_holding,
4269 white_holding[strlen(white_holding)-1] = NULLCHAR;
4270 black_holding[strlen(black_holding)-1] = NULLCHAR;
4271 /* [HGM] copy holdings to partner-board holdings area */
4272 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4273 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4274 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4275 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4276 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4279 /* Suppress following prompt */
4280 if (looking_at(buf, &i, "*% ")) {
4281 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4282 savingComment = FALSE;
4290 i++; /* skip unparsed character and loop back */
4293 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4294 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4295 // SendToPlayer(&buf[next_out], i - next_out);
4296 started != STARTED_HOLDINGS && leftover_start > next_out) {
4297 SendToPlayer(&buf[next_out], leftover_start - next_out);
4301 leftover_len = buf_len - leftover_start;
4302 /* if buffer ends with something we couldn't parse,
4303 reparse it after appending the next read */
4305 } else if (count == 0) {
4306 RemoveInputSource(isr);
4307 DisplayFatalError(_("Connection closed by ICS"), 0, 6666);
4309 DisplayFatalError(_("Error reading from ICS"), error, 1);
4314 /* Board style 12 looks like this:
4316 <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
4318 * The "<12> " is stripped before it gets to this routine. The two
4319 * trailing 0's (flip state and clock ticking) are later addition, and
4320 * some chess servers may not have them, or may have only the first.
4321 * Additional trailing fields may be added in the future.
4324 #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"
4326 #define RELATION_OBSERVING_PLAYED 0
4327 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4328 #define RELATION_PLAYING_MYMOVE 1
4329 #define RELATION_PLAYING_NOTMYMOVE -1
4330 #define RELATION_EXAMINING 2
4331 #define RELATION_ISOLATED_BOARD -3
4332 #define RELATION_STARTING_POSITION -4 /* FICS only */
4335 ParseBoard12 (char *string)
4339 char *bookHit = NULL; // [HGM] book
4341 GameMode newGameMode;
4342 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4343 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4344 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4345 char to_play, board_chars[200];
4346 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4347 char black[32], white[32];
4349 int prevMove = currentMove;
4352 int fromX, fromY, toX, toY;
4354 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4355 Boolean weird = FALSE, reqFlag = FALSE;
4357 fromX = fromY = toX = toY = -1;
4361 if (appData.debugMode)
4362 fprintf(debugFP, "Parsing board: %s\n", string);
4364 move_str[0] = NULLCHAR;
4365 elapsed_time[0] = NULLCHAR;
4366 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4368 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4369 if(string[i] == ' ') { ranks++; files = 0; }
4371 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4374 for(j = 0; j <i; j++) board_chars[j] = string[j];
4375 board_chars[i] = '\0';
4378 n = sscanf(string, PATTERN, &to_play, &double_push,
4379 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4380 &gamenum, white, black, &relation, &basetime, &increment,
4381 &white_stren, &black_stren, &white_time, &black_time,
4382 &moveNum, str, elapsed_time, move_str, &ics_flip,
4386 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4387 DisplayError(str, 0);
4391 /* Convert the move number to internal form */
4392 moveNum = (moveNum - 1) * 2;
4393 if (to_play == 'B') moveNum++;
4394 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4395 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4401 case RELATION_OBSERVING_PLAYED:
4402 case RELATION_OBSERVING_STATIC:
4403 if (gamenum == -1) {
4404 /* Old ICC buglet */
4405 relation = RELATION_OBSERVING_STATIC;
4407 newGameMode = IcsObserving;
4409 case RELATION_PLAYING_MYMOVE:
4410 case RELATION_PLAYING_NOTMYMOVE:
4412 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4413 IcsPlayingWhite : IcsPlayingBlack;
4414 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4416 case RELATION_EXAMINING:
4417 newGameMode = IcsExamining;
4419 case RELATION_ISOLATED_BOARD:
4421 /* Just display this board. If user was doing something else,
4422 we will forget about it until the next board comes. */
4423 newGameMode = IcsIdle;
4425 case RELATION_STARTING_POSITION:
4426 newGameMode = gameMode;
4430 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4431 gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4432 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4433 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4434 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4435 static int lastBgGame = -1;
4437 for (k = 0; k < ranks; k++) {
4438 for (j = 0; j < files; j++)
4439 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4440 if(gameInfo.holdingsWidth > 1) {
4441 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4442 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4445 CopyBoard(partnerBoard, board);
4446 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4447 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4448 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4449 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4450 if(toSqr = strchr(str, '-')) {
4451 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4452 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4453 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4454 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4455 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4456 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4458 DisplayWhiteClock(white_time*fac, to_play == 'W');
4459 DisplayBlackClock(black_time*fac, to_play != 'W');
4460 activePartner = to_play;
4461 if(gamenum != lastBgGame) {
4463 snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4466 lastBgGame = gamenum;
4467 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4468 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4469 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4470 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4471 if(!twoBoards) DisplayMessage(partnerStatus, "");
4472 partnerBoardValid = TRUE;
4476 if(appData.dualBoard && appData.bgObserve) {
4477 if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4478 SendToICS(ics_prefix), SendToICS("pobserve\n");
4479 else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4481 snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4486 /* Modify behavior for initial board display on move listing
4489 switch (ics_getting_history) {
4493 case H_GOT_REQ_HEADER:
4494 case H_GOT_UNREQ_HEADER:
4495 /* This is the initial position of the current game */
4496 gamenum = ics_gamenum;
4497 moveNum = 0; /* old ICS bug workaround */
4498 if (to_play == 'B') {
4499 startedFromSetupPosition = TRUE;
4500 blackPlaysFirst = TRUE;
4502 if (forwardMostMove == 0) forwardMostMove = 1;
4503 if (backwardMostMove == 0) backwardMostMove = 1;
4504 if (currentMove == 0) currentMove = 1;
4506 newGameMode = gameMode;
4507 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4509 case H_GOT_UNWANTED_HEADER:
4510 /* This is an initial board that we don't want */
4512 case H_GETTING_MOVES:
4513 /* Should not happen */
4514 DisplayError(_("Error gathering move list: extra board"), 0);
4515 ics_getting_history = H_FALSE;
4519 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4520 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4521 weird && (int)gameInfo.variant < (int)VariantShogi) {
4522 /* [HGM] We seem to have switched variant unexpectedly
4523 * Try to guess new variant from board size
4525 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4526 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4527 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4528 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4529 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4530 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4531 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4532 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4533 /* Get a move list just to see the header, which
4534 will tell us whether this is really bug or zh */
4535 if (ics_getting_history == H_FALSE) {
4536 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4537 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4542 /* Take action if this is the first board of a new game, or of a
4543 different game than is currently being displayed. */
4544 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4545 relation == RELATION_ISOLATED_BOARD) {
4547 /* Forget the old game and get the history (if any) of the new one */
4548 if (gameMode != BeginningOfGame) {
4552 if (appData.autoRaiseBoard) BoardToTop();
4554 if (gamenum == -1) {
4555 newGameMode = IcsIdle;
4556 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4557 appData.getMoveList && !reqFlag) {
4558 /* Need to get game history */
4559 ics_getting_history = H_REQUESTED;
4560 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4564 /* Initially flip the board to have black on the bottom if playing
4565 black or if the ICS flip flag is set, but let the user change
4566 it with the Flip View button. */
4567 flipView = appData.autoFlipView ?
4568 (newGameMode == IcsPlayingBlack) || ics_flip :
4571 /* Done with values from previous mode; copy in new ones */
4572 gameMode = newGameMode;
4574 ics_gamenum = gamenum;
4575 if (gamenum == gs_gamenum) {
4576 int klen = strlen(gs_kind);
4577 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4578 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4579 gameInfo.event = StrSave(str);
4581 gameInfo.event = StrSave("ICS game");
4583 gameInfo.site = StrSave(appData.icsHost);
4584 gameInfo.date = PGNDate();
4585 gameInfo.round = StrSave("-");
4586 gameInfo.white = StrSave(white);
4587 gameInfo.black = StrSave(black);
4588 timeControl = basetime * 60 * 1000;
4590 timeIncrement = increment * 1000;
4591 movesPerSession = 0;
4592 gameInfo.timeControl = TimeControlTagValue();
4593 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4594 if (appData.debugMode) {
4595 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4596 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4597 setbuf(debugFP, NULL);
4600 gameInfo.outOfBook = NULL;
4602 /* Do we have the ratings? */
4603 if (strcmp(player1Name, white) == 0 &&
4604 strcmp(player2Name, black) == 0) {
4605 if (appData.debugMode)
4606 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4607 player1Rating, player2Rating);
4608 gameInfo.whiteRating = player1Rating;
4609 gameInfo.blackRating = player2Rating;
4610 } else if (strcmp(player2Name, white) == 0 &&
4611 strcmp(player1Name, black) == 0) {
4612 if (appData.debugMode)
4613 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4614 player2Rating, player1Rating);
4615 gameInfo.whiteRating = player2Rating;
4616 gameInfo.blackRating = player1Rating;
4618 player1Name[0] = player2Name[0] = NULLCHAR;
4620 /* Silence shouts if requested */
4621 if (appData.quietPlay &&
4622 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4623 SendToICS(ics_prefix);
4624 SendToICS("set shout 0\n");
4628 /* Deal with midgame name changes */
4630 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4631 if (gameInfo.white) free(gameInfo.white);
4632 gameInfo.white = StrSave(white);
4634 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4635 if (gameInfo.black) free(gameInfo.black);
4636 gameInfo.black = StrSave(black);
4640 /* Throw away game result if anything actually changes in examine mode */
4641 if (gameMode == IcsExamining && !newGame) {
4642 gameInfo.result = GameUnfinished;
4643 if (gameInfo.resultDetails != NULL) {
4644 free(gameInfo.resultDetails);
4645 gameInfo.resultDetails = NULL;
4649 /* In pausing && IcsExamining mode, we ignore boards coming
4650 in if they are in a different variation than we are. */
4651 if (pauseExamInvalid) return;
4652 if (pausing && gameMode == IcsExamining) {
4653 if (moveNum <= pauseExamForwardMostMove) {
4654 pauseExamInvalid = TRUE;
4655 forwardMostMove = pauseExamForwardMostMove;
4660 if (appData.debugMode) {
4661 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4663 /* Parse the board */
4664 for (k = 0; k < ranks; k++) {
4665 for (j = 0; j < files; j++)
4666 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4667 if(gameInfo.holdingsWidth > 1) {
4668 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4669 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4672 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4673 board[5][BOARD_RGHT+1] = WhiteAngel;
4674 board[6][BOARD_RGHT+1] = WhiteMarshall;
4675 board[1][0] = BlackMarshall;
4676 board[2][0] = BlackAngel;
4677 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4679 CopyBoard(boards[moveNum], board);
4680 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4682 startedFromSetupPosition =
4683 !CompareBoards(board, initialPosition);
4684 if(startedFromSetupPosition)
4685 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4688 /* [HGM] Set castling rights. Take the outermost Rooks,
4689 to make it also work for FRC opening positions. Note that board12
4690 is really defective for later FRC positions, as it has no way to
4691 indicate which Rook can castle if they are on the same side of King.
4692 For the initial position we grant rights to the outermost Rooks,
4693 and remember thos rights, and we then copy them on positions
4694 later in an FRC game. This means WB might not recognize castlings with
4695 Rooks that have moved back to their original position as illegal,
4696 but in ICS mode that is not its job anyway.
4698 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4699 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4701 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4702 if(board[0][i] == WhiteRook) j = i;
4703 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4704 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4705 if(board[0][i] == WhiteRook) j = i;
4706 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4707 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4708 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4709 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4710 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4711 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4712 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4714 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4715 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4716 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4717 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4718 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4719 if(board[BOARD_HEIGHT-1][k] == bKing)
4720 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4721 if(gameInfo.variant == VariantTwoKings) {
4722 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4723 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4724 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4727 r = boards[moveNum][CASTLING][0] = initialRights[0];
4728 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4729 r = boards[moveNum][CASTLING][1] = initialRights[1];
4730 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4731 r = boards[moveNum][CASTLING][3] = initialRights[3];
4732 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4733 r = boards[moveNum][CASTLING][4] = initialRights[4];
4734 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4735 /* wildcastle kludge: always assume King has rights */
4736 r = boards[moveNum][CASTLING][2] = initialRights[2];
4737 r = boards[moveNum][CASTLING][5] = initialRights[5];
4739 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4740 boards[moveNum][EP_STATUS] = EP_NONE;
4741 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4742 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4743 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4746 if (ics_getting_history == H_GOT_REQ_HEADER ||
4747 ics_getting_history == H_GOT_UNREQ_HEADER) {
4748 /* This was an initial position from a move list, not
4749 the current position */
4753 /* Update currentMove and known move number limits */
4754 newMove = newGame || moveNum > forwardMostMove;
4757 forwardMostMove = backwardMostMove = currentMove = moveNum;
4758 if (gameMode == IcsExamining && moveNum == 0) {
4759 /* Workaround for ICS limitation: we are not told the wild
4760 type when starting to examine a game. But if we ask for
4761 the move list, the move list header will tell us */
4762 ics_getting_history = H_REQUESTED;
4763 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4766 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4767 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4769 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4770 /* [HGM] applied this also to an engine that is silently watching */
4771 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4772 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4773 gameInfo.variant == currentlyInitializedVariant) {
4774 takeback = forwardMostMove - moveNum;
4775 for (i = 0; i < takeback; i++) {
4776 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4777 SendToProgram("undo\n", &first);
4782 forwardMostMove = moveNum;
4783 if (!pausing || currentMove > forwardMostMove)
4784 currentMove = forwardMostMove;
4786 /* New part of history that is not contiguous with old part */
4787 if (pausing && gameMode == IcsExamining) {
4788 pauseExamInvalid = TRUE;
4789 forwardMostMove = pauseExamForwardMostMove;
4792 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4794 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4795 // [HGM] when we will receive the move list we now request, it will be
4796 // fed to the engine from the first move on. So if the engine is not
4797 // in the initial position now, bring it there.
4798 InitChessProgram(&first, 0);
4801 ics_getting_history = H_REQUESTED;
4802 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4805 forwardMostMove = backwardMostMove = currentMove = moveNum;
4808 /* Update the clocks */
4809 if (strchr(elapsed_time, '.')) {
4811 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4812 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4814 /* Time is in seconds */
4815 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4816 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4821 if (appData.zippyPlay && newGame &&
4822 gameMode != IcsObserving && gameMode != IcsIdle &&
4823 gameMode != IcsExamining)
4824 ZippyFirstBoard(moveNum, basetime, increment);
4827 /* Put the move on the move list, first converting
4828 to canonical algebraic form. */
4830 if (appData.debugMode) {
4831 int f = forwardMostMove;
4832 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4833 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4834 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4835 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4836 fprintf(debugFP, "moveNum = %d\n", moveNum);
4837 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4838 setbuf(debugFP, NULL);
4840 if (moveNum <= backwardMostMove) {
4841 /* We don't know what the board looked like before
4843 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4844 strcat(parseList[moveNum - 1], " ");
4845 strcat(parseList[moveNum - 1], elapsed_time);
4846 moveList[moveNum - 1][0] = NULLCHAR;
4847 } else if (strcmp(move_str, "none") == 0) {
4848 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4849 /* Again, we don't know what the board looked like;
4850 this is really the start of the game. */
4851 parseList[moveNum - 1][0] = NULLCHAR;
4852 moveList[moveNum - 1][0] = NULLCHAR;
4853 backwardMostMove = moveNum;
4854 startedFromSetupPosition = TRUE;
4855 fromX = fromY = toX = toY = -1;
4857 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4858 // So we parse the long-algebraic move string in stead of the SAN move
4859 int valid; char buf[MSG_SIZ], *prom;
4861 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4862 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4863 // str looks something like "Q/a1-a2"; kill the slash
4865 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4866 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4867 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4868 strcat(buf, prom); // long move lacks promo specification!
4869 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4870 if(appData.debugMode)
4871 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4872 safeStrCpy(move_str, buf, MSG_SIZ);
4874 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4875 &fromX, &fromY, &toX, &toY, &promoChar)
4876 || ParseOneMove(buf, moveNum - 1, &moveType,
4877 &fromX, &fromY, &toX, &toY, &promoChar);
4878 // end of long SAN patch
4880 (void) CoordsToAlgebraic(boards[moveNum - 1],
4881 PosFlags(moveNum - 1),
4882 fromY, fromX, toY, toX, promoChar,
4883 parseList[moveNum-1]);
4884 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4890 if(!IS_SHOGI(gameInfo.variant))
4891 strcat(parseList[moveNum - 1], "+");
4894 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4895 strcat(parseList[moveNum - 1], "#");
4898 strcat(parseList[moveNum - 1], " ");
4899 strcat(parseList[moveNum - 1], elapsed_time);
4900 /* currentMoveString is set as a side-effect of ParseOneMove */
4901 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4902 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4903 strcat(moveList[moveNum - 1], "\n");
4905 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4906 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4907 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4908 ChessSquare old, new = boards[moveNum][k][j];
4909 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4910 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4911 if(old == new) continue;
4912 if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4913 else if(new == WhiteWazir || new == BlackWazir) {
4914 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4915 boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4916 else boards[moveNum][k][j] = old; // preserve type of Gold
4917 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4918 boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4921 /* Move from ICS was illegal!? Punt. */
4922 if (appData.debugMode) {
4923 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4924 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4926 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4927 strcat(parseList[moveNum - 1], " ");
4928 strcat(parseList[moveNum - 1], elapsed_time);
4929 moveList[moveNum - 1][0] = NULLCHAR;
4930 fromX = fromY = toX = toY = -1;
4933 if (appData.debugMode) {
4934 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4935 setbuf(debugFP, NULL);
4939 /* Send move to chess program (BEFORE animating it). */
4940 if (appData.zippyPlay && !newGame && newMove &&
4941 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4943 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4944 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4945 if (moveList[moveNum - 1][0] == NULLCHAR) {
4946 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4948 DisplayError(str, 0);
4950 if (first.sendTime) {
4951 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4953 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4954 if (firstMove && !bookHit) {
4956 if (first.useColors) {
4957 SendToProgram(gameMode == IcsPlayingWhite ?
4959 "black\ngo\n", &first);
4961 SendToProgram("go\n", &first);
4963 first.maybeThinking = TRUE;
4966 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4967 if (moveList[moveNum - 1][0] == NULLCHAR) {
4968 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4969 DisplayError(str, 0);
4971 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4972 SendMoveToProgram(moveNum - 1, &first);
4979 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4980 /* If move comes from a remote source, animate it. If it
4981 isn't remote, it will have already been animated. */
4982 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4983 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4985 if (!pausing && appData.highlightLastMove) {
4986 SetHighlights(fromX, fromY, toX, toY);
4990 /* Start the clocks */
4991 whiteFlag = blackFlag = FALSE;
4992 appData.clockMode = !(basetime == 0 && increment == 0);
4994 ics_clock_paused = TRUE;
4996 } else if (ticking == 1) {
4997 ics_clock_paused = FALSE;
4999 if (gameMode == IcsIdle ||
5000 relation == RELATION_OBSERVING_STATIC ||
5001 relation == RELATION_EXAMINING ||
5003 DisplayBothClocks();
5007 /* Display opponents and material strengths */
5008 if (gameInfo.variant != VariantBughouse &&
5009 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
5010 if (tinyLayout || smallLayout) {
5011 if(gameInfo.variant == VariantNormal)
5012 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5013 gameInfo.white, white_stren, gameInfo.black, black_stren,
5014 basetime, increment);
5016 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5017 gameInfo.white, white_stren, gameInfo.black, black_stren,
5018 basetime, increment, (int) gameInfo.variant);
5020 if(gameInfo.variant == VariantNormal)
5021 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5022 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5023 basetime, increment);
5025 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5026 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5027 basetime, increment, VariantName(gameInfo.variant));
5030 if (appData.debugMode) {
5031 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5036 /* Display the board */
5037 if (!pausing && !appData.noGUI) {
5039 if (appData.premove)
5041 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5042 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5043 ClearPremoveHighlights();
5045 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5046 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5047 DrawPosition(j, boards[currentMove]);
5049 DisplayMove(moveNum - 1);
5050 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5051 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5052 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
5053 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5057 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5059 if(bookHit) { // [HGM] book: simulate book reply
5060 static char bookMove[MSG_SIZ]; // a bit generous?
5062 programStats.nodes = programStats.depth = programStats.time =
5063 programStats.score = programStats.got_only_move = 0;
5064 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5066 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5067 strcat(bookMove, bookHit);
5068 HandleMachineMove(bookMove, &first);
5077 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5078 ics_getting_history = H_REQUESTED;
5079 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5085 SendToBoth (char *msg)
5086 { // to make it easy to keep two engines in step in dual analysis
5087 SendToProgram(msg, &first);
5088 if(second.analyzing) SendToProgram(msg, &second);
5092 AnalysisPeriodicEvent (int force)
5094 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5095 && !force) || !appData.periodicUpdates)
5098 /* Send . command to Crafty to collect stats */
5101 /* Don't send another until we get a response (this makes
5102 us stop sending to old Crafty's which don't understand
5103 the "." command (sending illegal cmds resets node count & time,
5104 which looks bad)) */
5105 programStats.ok_to_send = 0;
5109 ics_update_width (int new_width)
5111 ics_printf("set width %d\n", new_width);
5115 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5119 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5120 if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5121 sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5122 SendToProgram(buf, cps);
5125 // null move in variant where engine does not understand it (for analysis purposes)
5126 SendBoard(cps, moveNum + 1); // send position after move in stead.
5129 if (cps->useUsermove) {
5130 SendToProgram("usermove ", cps);
5134 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5135 int len = space - parseList[moveNum];
5136 memcpy(buf, parseList[moveNum], len);
5138 buf[len] = NULLCHAR;
5140 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5142 SendToProgram(buf, cps);
5144 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5145 AlphaRank(moveList[moveNum], 4);
5146 SendToProgram(moveList[moveNum], cps);
5147 AlphaRank(moveList[moveNum], 4); // and back
5149 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5150 * the engine. It would be nice to have a better way to identify castle
5152 if(appData.fischerCastling && cps->useOOCastle) {
5153 int fromX = moveList[moveNum][0] - AAA;
5154 int fromY = moveList[moveNum][1] - ONE;
5155 int toX = moveList[moveNum][2] - AAA;
5156 int toY = moveList[moveNum][3] - ONE;
5157 if((boards[moveNum][fromY][fromX] == WhiteKing
5158 && boards[moveNum][toY][toX] == WhiteRook)
5159 || (boards[moveNum][fromY][fromX] == BlackKing
5160 && boards[moveNum][toY][toX] == BlackRook)) {
5161 if(toX > fromX) SendToProgram("O-O\n", cps);
5162 else SendToProgram("O-O-O\n", cps);
5164 else SendToProgram(moveList[moveNum], cps);
5166 if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5167 char *m = moveList[moveNum];
5169 *c = m[7]; if(*c == '\n') *c = NULLCHAR; // promoChar
5170 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
5171 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5174 m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5175 else if(*c && m[8] != '\n') { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5176 *c = m[9]; if(*c == '\n') *c = NULLCHAR;
5177 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to three moves
5182 m[2], m[3] - '0', c);
5184 snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5187 m[2], m[3] - '0', c);
5188 SendToProgram(buf, cps);
5190 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5191 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5192 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5193 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5194 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5196 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5197 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5198 SendToProgram(buf, cps);
5200 else SendToProgram(moveList[moveNum], cps);
5201 /* End of additions by Tord */
5204 /* [HGM] setting up the opening has brought engine in force mode! */
5205 /* Send 'go' if we are in a mode where machine should play. */
5206 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5207 (gameMode == TwoMachinesPlay ||
5209 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
5211 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5212 SendToProgram("go\n", cps);
5213 if (appData.debugMode) {
5214 fprintf(debugFP, "(extra)\n");
5217 setboardSpoiledMachineBlack = 0;
5221 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5223 char user_move[MSG_SIZ];
5226 if(gameInfo.variant == VariantSChess && promoChar) {
5227 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5228 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5229 } else suffix[0] = NULLCHAR;
5233 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5234 (int)moveType, fromX, fromY, toX, toY);
5235 DisplayError(user_move + strlen("say "), 0);
5237 case WhiteKingSideCastle:
5238 case BlackKingSideCastle:
5239 case WhiteQueenSideCastleWild:
5240 case BlackQueenSideCastleWild:
5242 case WhiteHSideCastleFR:
5243 case BlackHSideCastleFR:
5245 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5247 case WhiteQueenSideCastle:
5248 case BlackQueenSideCastle:
5249 case WhiteKingSideCastleWild:
5250 case BlackKingSideCastleWild:
5252 case WhiteASideCastleFR:
5253 case BlackASideCastleFR:
5255 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5257 case WhiteNonPromotion:
5258 case BlackNonPromotion:
5259 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5261 case WhitePromotion:
5262 case BlackPromotion:
5263 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5264 gameInfo.variant == VariantMakruk)
5265 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5266 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5267 PieceToChar(WhiteFerz));
5268 else if(gameInfo.variant == VariantGreat)
5269 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5270 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5271 PieceToChar(WhiteMan));
5273 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5274 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5280 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5281 ToUpper(PieceToChar((ChessSquare) fromX)),
5282 AAA + toX, ONE + toY);
5284 case IllegalMove: /* could be a variant we don't quite understand */
5285 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5287 case WhiteCapturesEnPassant:
5288 case BlackCapturesEnPassant:
5289 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5290 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5293 SendToICS(user_move);
5294 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5295 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5300 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5301 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5302 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5303 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5304 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5307 if(gameMode != IcsExamining) { // is this ever not the case?
5308 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5310 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5311 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5312 } else { // on FICS we must first go to general examine mode
5313 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5315 if(gameInfo.variant != VariantNormal) {
5316 // try figure out wild number, as xboard names are not always valid on ICS
5317 for(i=1; i<=36; i++) {
5318 snprintf(buf, MSG_SIZ, "wild/%d", i);
5319 if(StringToVariant(buf) == gameInfo.variant) break;
5321 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5322 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5323 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5324 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5325 SendToICS(ics_prefix);
5327 if(startedFromSetupPosition || backwardMostMove != 0) {
5328 fen = PositionToFEN(backwardMostMove, NULL, 1);
5329 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5330 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5332 } else { // FICS: everything has to set by separate bsetup commands
5333 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5334 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5336 if(!WhiteOnMove(backwardMostMove)) {
5337 SendToICS("bsetup tomove black\n");
5339 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5340 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5342 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5343 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5345 i = boards[backwardMostMove][EP_STATUS];
5346 if(i >= 0) { // set e.p.
5347 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5353 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5354 SendToICS("bsetup done\n"); // switch to normal examining.
5356 for(i = backwardMostMove; i<last; i++) {
5358 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5359 if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5360 int len = strlen(moveList[i]);
5361 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5362 if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5366 SendToICS(ics_prefix);
5367 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5370 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5374 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5376 if (rf == DROP_RANK) {
5377 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5378 sprintf(move, "%c@%c%c\n",
5379 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5381 if (promoChar == 'x' || promoChar == NULLCHAR) {
5382 sprintf(move, "%c%c%c%c\n",
5383 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5384 if(killX >= 0 && killY >= 0) {
5385 sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5386 if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5389 sprintf(move, "%c%c%c%c%c\n",
5390 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5391 if(killX >= 0 && killY >= 0) {
5392 sprintf(move+4, ";%c%c%c\n", AAA + killX, ONE + killY, promoChar);
5393 if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5400 ProcessICSInitScript (FILE *f)
5404 while (fgets(buf, MSG_SIZ, f)) {
5405 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5412 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5414 static ClickType lastClickType;
5417 PieceInString (char *s, ChessSquare piece)
5419 char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5420 while((p = strchr(s, ID))) {
5421 if(!suffix || p[1] == suffix) return TRUE;
5428 Partner (ChessSquare *p)
5429 { // change piece into promotion partner if one shogi-promotes to the other
5430 ChessSquare partner = promoPartner[*p];
5431 if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5432 if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5440 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5441 static int toggleFlag;
5442 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5443 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5444 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5445 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5446 if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5447 if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5449 if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5450 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5451 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5452 else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5453 else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5454 if(!step) step = -1;
5455 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5456 !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5457 promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it
5458 promoSweep == pawn ||
5459 appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5460 (promoSweep == WhiteLion || promoSweep == BlackLion)));
5462 int victim = boards[currentMove][toY][toX];
5463 boards[currentMove][toY][toX] = promoSweep;
5464 DrawPosition(FALSE, boards[currentMove]);
5465 boards[currentMove][toY][toX] = victim;
5467 ChangeDragPiece(promoSweep);
5471 PromoScroll (int x, int y)
5475 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5476 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5477 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5478 if(!step) return FALSE;
5479 lastX = x; lastY = y;
5480 if((promoSweep < BlackPawn) == flipView) step = -step;
5481 if(step > 0) selectFlag = 1;
5482 if(!selectFlag) Sweep(step);
5487 NextPiece (int step)
5489 ChessSquare piece = boards[currentMove][toY][toX];
5492 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5493 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5494 if(!step) step = -1;
5495 } while(PieceToChar(pieceSweep) == '.');
5496 boards[currentMove][toY][toX] = pieceSweep;
5497 DrawPosition(FALSE, boards[currentMove]);
5498 boards[currentMove][toY][toX] = piece;
5500 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5502 AlphaRank (char *move, int n)
5504 // char *p = move, c; int x, y;
5506 if (appData.debugMode) {
5507 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5511 move[2]>='0' && move[2]<='9' &&
5512 move[3]>='a' && move[3]<='x' ) {
5514 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5515 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5517 if(move[0]>='0' && move[0]<='9' &&
5518 move[1]>='a' && move[1]<='x' &&
5519 move[2]>='0' && move[2]<='9' &&
5520 move[3]>='a' && move[3]<='x' ) {
5521 /* input move, Shogi -> normal */
5522 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5523 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5524 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5525 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5528 move[3]>='0' && move[3]<='9' &&
5529 move[2]>='a' && move[2]<='x' ) {
5531 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5532 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5535 move[0]>='a' && move[0]<='x' &&
5536 move[3]>='0' && move[3]<='9' &&
5537 move[2]>='a' && move[2]<='x' ) {
5538 /* output move, normal -> Shogi */
5539 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5540 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5541 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5542 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5543 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5545 if (appData.debugMode) {
5546 fprintf(debugFP, " out = '%s'\n", move);
5550 char yy_textstr[8000];
5552 /* Parser for moves from gnuchess, ICS, or user typein box */
5554 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5556 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5558 switch (*moveType) {
5559 case WhitePromotion:
5560 case BlackPromotion:
5561 case WhiteNonPromotion:
5562 case BlackNonPromotion:
5565 case WhiteCapturesEnPassant:
5566 case BlackCapturesEnPassant:
5567 case WhiteKingSideCastle:
5568 case WhiteQueenSideCastle:
5569 case BlackKingSideCastle:
5570 case BlackQueenSideCastle:
5571 case WhiteKingSideCastleWild:
5572 case WhiteQueenSideCastleWild:
5573 case BlackKingSideCastleWild:
5574 case BlackQueenSideCastleWild:
5575 /* Code added by Tord: */
5576 case WhiteHSideCastleFR:
5577 case WhiteASideCastleFR:
5578 case BlackHSideCastleFR:
5579 case BlackASideCastleFR:
5580 /* End of code added by Tord */
5581 case IllegalMove: /* bug or odd chess variant */
5582 if(currentMoveString[1] == '@') { // illegal drop
5583 *fromX = WhiteOnMove(moveNum) ?
5584 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5585 (int) CharToPiece(ToLower(currentMoveString[0]));
5588 *fromX = currentMoveString[0] - AAA;
5589 *fromY = currentMoveString[1] - ONE;
5590 *toX = currentMoveString[2] - AAA;
5591 *toY = currentMoveString[3] - ONE;
5592 *promoChar = currentMoveString[4];
5593 if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5594 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5595 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5596 if (appData.debugMode) {
5597 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5599 *fromX = *fromY = *toX = *toY = 0;
5602 if (appData.testLegality) {
5603 return (*moveType != IllegalMove);
5605 return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5606 // [HGM] lion: if this is a double move we are less critical
5607 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5612 *fromX = *moveType == WhiteDrop ?
5613 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5614 (int) CharToPiece(ToLower(currentMoveString[0]));
5617 *toX = currentMoveString[2] - AAA;
5618 *toY = currentMoveString[3] - ONE;
5619 *promoChar = NULLCHAR;
5623 case ImpossibleMove:
5633 if (appData.debugMode) {
5634 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5637 *fromX = *fromY = *toX = *toY = 0;
5638 *promoChar = NULLCHAR;
5643 Boolean pushed = FALSE;
5644 char *lastParseAttempt;
5647 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5648 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5649 int fromX, fromY, toX, toY; char promoChar;
5654 lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV
5655 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5656 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5659 endPV = forwardMostMove;
5661 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5662 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5663 lastParseAttempt = pv;
5664 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5665 if(!valid && nr == 0 &&
5666 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5667 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5668 // Hande case where played move is different from leading PV move
5669 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5670 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5671 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5672 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5673 endPV += 2; // if position different, keep this
5674 moveList[endPV-1][0] = fromX + AAA;
5675 moveList[endPV-1][1] = fromY + ONE;
5676 moveList[endPV-1][2] = toX + AAA;
5677 moveList[endPV-1][3] = toY + ONE;
5678 parseList[endPV-1][0] = NULLCHAR;
5679 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5682 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5683 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5684 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5685 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5686 valid++; // allow comments in PV
5690 if(endPV+1 > framePtr) break; // no space, truncate
5693 CopyBoard(boards[endPV], boards[endPV-1]);
5694 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5695 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5696 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5697 CoordsToAlgebraic(boards[endPV - 1],
5698 PosFlags(endPV - 1),
5699 fromY, fromX, toY, toX, promoChar,
5700 parseList[endPV - 1]);
5702 if(atEnd == 2) return; // used hidden, for PV conversion
5703 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5704 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5705 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5706 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5707 DrawPosition(TRUE, boards[currentMove]);
5711 MultiPV (ChessProgramState *cps, int kind)
5712 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5714 for(i=0; i<cps->nrOptions; i++) {
5715 char *s = cps->option[i].name;
5716 if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5717 if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5718 && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5723 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5724 static int multi, pv_margin;
5725 static ChessProgramState *activeCps;
5728 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5730 int startPV, lineStart, origIndex = index;
5731 char *p, buf2[MSG_SIZ];
5732 ChessProgramState *cps = (pane ? &second : &first);
5734 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5735 lastX = x; lastY = y;
5736 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5737 lineStart = startPV = index;
5738 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5739 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5741 do{ while(buf[index] && buf[index] != '\n') index++;
5742 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5744 if(lineStart == 0 && gameMode == AnalyzeMode) {
5746 if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5747 if(n == 0) { // click not on "fewer" or "more"
5748 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5749 pv_margin = cps->option[multi].value;
5750 activeCps = cps; // non-null signals margin adjustment
5752 } else if((multi = MultiPV(cps, 1)) >= 0) {
5753 n += cps->option[multi].value; if(n < 1) n = 1;
5754 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5755 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5756 cps->option[multi].value = n;
5760 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5761 ExcludeClick(origIndex - lineStart);
5763 } else if(!strncmp(buf+lineStart, "dep\t", 4)) { // column headers clicked
5764 Collapse(origIndex - lineStart);
5767 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5768 *start = startPV; *end = index-1;
5769 extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5776 static char buf[10*MSG_SIZ];
5777 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5779 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5780 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5781 for(i = forwardMostMove; i<endPV; i++){
5782 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5783 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5786 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5787 if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5788 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5794 LoadPV (int x, int y)
5795 { // called on right mouse click to load PV
5796 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5797 lastX = x; lastY = y;
5798 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5806 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5808 if(pv_margin != activeCps->option[multi].value) {
5810 snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5811 SendToProgram(buf, activeCps);
5812 activeCps->option[multi].value = pv_margin;
5817 if(endPV < 0) return;
5818 if(appData.autoCopyPV) CopyFENToClipboard();
5820 if(extendGame && currentMove > forwardMostMove) {
5821 Boolean saveAnimate = appData.animate;
5823 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5824 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5825 } else storedGames--; // abandon shelved tail of original game
5828 forwardMostMove = currentMove;
5829 currentMove = oldFMM;
5830 appData.animate = FALSE;
5831 ToNrEvent(forwardMostMove);
5832 appData.animate = saveAnimate;
5834 currentMove = forwardMostMove;
5835 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5836 ClearPremoveHighlights();
5837 DrawPosition(TRUE, boards[currentMove]);
5841 MovePV (int x, int y, int h)
5842 { // step through PV based on mouse coordinates (called on mouse move)
5843 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5845 if(activeCps) { // adjusting engine's multi-pv margin
5846 if(x > lastX) pv_margin++; else
5847 if(x < lastX) pv_margin -= (pv_margin > 0);
5850 snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5851 DisplayMessage(buf, "");
5856 // we must somehow check if right button is still down (might be released off board!)
5857 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5858 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5859 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5861 lastX = x; lastY = y;
5863 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5864 if(endPV < 0) return;
5865 if(y < margin) step = 1; else
5866 if(y > h - margin) step = -1;
5867 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5868 currentMove += step;
5869 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5870 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5871 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5872 DrawPosition(FALSE, boards[currentMove]);
5876 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5877 // All positions will have equal probability, but the current method will not provide a unique
5878 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5884 int piecesLeft[(int)BlackPawn];
5885 int seed, nrOfShuffles;
5888 GetPositionNumber ()
5889 { // sets global variable seed
5892 seed = appData.defaultFrcPosition;
5893 if(seed < 0) { // randomize based on time for negative FRC position numbers
5894 for(i=0; i<50; i++) seed += random();
5895 seed = random() ^ random() >> 8 ^ random() << 8;
5896 if(seed<0) seed = -seed;
5901 put (Board board, int pieceType, int rank, int n, int shade)
5902 // put the piece on the (n-1)-th empty squares of the given shade
5906 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5907 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5908 board[rank][i] = (ChessSquare) pieceType;
5909 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5911 piecesLeft[pieceType]--;
5920 AddOnePiece (Board board, int pieceType, int rank, int shade)
5921 // calculate where the next piece goes, (any empty square), and put it there
5925 i = seed % squaresLeft[shade];
5926 nrOfShuffles *= squaresLeft[shade];
5927 seed /= squaresLeft[shade];
5928 put(board, pieceType, rank, i, shade);
5932 AddTwoPieces (Board board, int pieceType, int rank)
5933 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5935 int i, n=squaresLeft[ANY], j=n-1, k;
5937 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5938 i = seed % k; // pick one
5941 while(i >= j) i -= j--;
5942 j = n - 1 - j; i += j;
5943 put(board, pieceType, rank, j, ANY);
5944 put(board, pieceType, rank, i, ANY);
5948 SetUpShuffle (Board board, int number)
5952 GetPositionNumber(); nrOfShuffles = 1;
5954 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5955 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5956 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5958 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5960 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5961 p = (int) board[0][i];
5962 if(p < (int) BlackPawn) piecesLeft[p] ++;
5963 board[0][i] = EmptySquare;
5966 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5967 // shuffles restricted to allow normal castling put KRR first
5968 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5969 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5970 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5971 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5972 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5973 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5974 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5975 put(board, WhiteRook, 0, 0, ANY);
5976 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5979 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5980 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5981 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5982 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5983 while(piecesLeft[p] >= 2) {
5984 AddOnePiece(board, p, 0, LITE);
5985 AddOnePiece(board, p, 0, DARK);
5987 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5990 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5991 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5992 // but we leave King and Rooks for last, to possibly obey FRC restriction
5993 if(p == (int)WhiteRook) continue;
5994 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5995 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5998 // now everything is placed, except perhaps King (Unicorn) and Rooks
6000 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
6001 // Last King gets castling rights
6002 while(piecesLeft[(int)WhiteUnicorn]) {
6003 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6004 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
6007 while(piecesLeft[(int)WhiteKing]) {
6008 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6009 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
6014 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
6015 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6018 // Only Rooks can be left; simply place them all
6019 while(piecesLeft[(int)WhiteRook]) {
6020 i = put(board, WhiteRook, 0, 0, ANY);
6021 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6024 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
6026 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
6029 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6030 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6033 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6037 ptclen (const char *s, char *escapes)
6040 if(!*escapes) return strlen(s);
6041 while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6046 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6047 /* [HGM] moved here from winboard.c because of its general usefulness */
6048 /* Basically a safe strcpy that uses the last character as King */
6050 int result = FALSE; int NrPieces;
6051 unsigned char partner[EmptySquare];
6053 if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6054 && NrPieces >= 12 && !(NrPieces&1)) {
6055 int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6057 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6058 for( i=offs=0; i<NrPieces/2-1; i++ ) {
6060 if(map[j] == '/') offs = WhitePBishop - i, j++;
6061 if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6062 table[i+offs] = map[j++];
6063 if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6064 if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6065 if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6067 table[(int) WhiteKing] = map[j++];
6068 for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6070 if(map[j] == '/') offs = WhitePBishop - ii, j++;
6071 i = WHITE_TO_BLACK ii;
6072 if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6073 table[i+offs] = map[j++];
6074 if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6075 if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6076 if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6078 table[(int) BlackKing] = map[j++];
6081 if(*escapes) { // set up promotion pairing
6082 for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6083 // pieceToChar entirely filled, so we can look up specified partners
6084 for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6086 if(c == '^' || c == '-') { // has specified partner
6088 for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6089 if(c == '^') table[i] = '+';
6090 if(p < EmptySquare) {
6091 if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6092 if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6093 promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6095 } else if(c == '*') {
6096 table[i] = partner[i];
6097 promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6109 SetCharTable (unsigned char *table, const char * map)
6111 return SetCharTableEsc(table, map, "");
6115 Prelude (Board board)
6116 { // [HGM] superchess: random selection of exo-pieces
6117 int i, j, k; ChessSquare p;
6118 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6120 GetPositionNumber(); // use FRC position number
6122 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6123 SetCharTable(pieceToChar, appData.pieceToCharTable);
6124 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6125 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6128 j = seed%4; seed /= 4;
6129 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6130 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6131 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
6132 j = seed%3 + (seed%3 >= j); seed /= 3;
6133 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6134 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6135 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
6136 j = seed%3; seed /= 3;
6137 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6138 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6139 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
6140 j = seed%2 + (seed%2 >= j); seed /= 2;
6141 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6142 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
6143 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
6144 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
6145 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
6146 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6147 put(board, exoPieces[0], 0, 0, ANY);
6148 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6152 InitPosition (int redraw)
6154 ChessSquare (* pieces)[BOARD_FILES];
6155 int i, j, pawnRow=1, pieceRows=1, overrule,
6156 oldx = gameInfo.boardWidth,
6157 oldy = gameInfo.boardHeight,
6158 oldh = gameInfo.holdingsWidth;
6161 if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6163 /* [AS] Initialize pv info list [HGM] and game status */
6165 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6166 pvInfoList[i].depth = 0;
6167 boards[i][EP_STATUS] = EP_NONE;
6168 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6171 initialRulePlies = 0; /* 50-move counter start */
6173 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6174 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6178 /* [HGM] logic here is completely changed. In stead of full positions */
6179 /* the initialized data only consist of the two backranks. The switch */
6180 /* selects which one we will use, which is than copied to the Board */
6181 /* initialPosition, which for the rest is initialized by Pawns and */
6182 /* empty squares. This initial position is then copied to boards[0], */
6183 /* possibly after shuffling, so that it remains available. */
6185 gameInfo.holdingsWidth = 0; /* default board sizes */
6186 gameInfo.boardWidth = 8;
6187 gameInfo.boardHeight = 8;
6188 gameInfo.holdingsSize = 0;
6189 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6190 for(i=0; i<BOARD_FILES-6; i++)
6191 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6192 initialPosition[EP_STATUS] = EP_NONE;
6193 initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6194 SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6195 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6196 SetCharTable(pieceNickName, appData.pieceNickNames);
6197 else SetCharTable(pieceNickName, "............");
6200 switch (gameInfo.variant) {
6201 case VariantFischeRandom:
6202 shuffleOpenings = TRUE;
6203 appData.fischerCastling = TRUE;
6206 case VariantShatranj:
6207 pieces = ShatranjArray;
6208 nrCastlingRights = 0;
6209 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6212 pieces = makrukArray;
6213 nrCastlingRights = 0;
6214 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6217 pieces = aseanArray;
6218 nrCastlingRights = 0;
6219 SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6221 case VariantTwoKings:
6222 pieces = twoKingsArray;
6225 pieces = GrandArray;
6226 nrCastlingRights = 0;
6227 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6228 gameInfo.boardWidth = 10;
6229 gameInfo.boardHeight = 10;
6230 gameInfo.holdingsSize = 7;
6232 case VariantCapaRandom:
6233 shuffleOpenings = TRUE;
6234 appData.fischerCastling = TRUE;
6235 case VariantCapablanca:
6236 pieces = CapablancaArray;
6237 gameInfo.boardWidth = 10;
6238 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6241 pieces = GothicArray;
6242 gameInfo.boardWidth = 10;
6243 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6246 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6247 gameInfo.holdingsSize = 7;
6248 for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6251 pieces = JanusArray;
6252 gameInfo.boardWidth = 10;
6253 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6254 nrCastlingRights = 6;
6255 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6256 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6257 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6258 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6259 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6260 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6263 pieces = FalconArray;
6264 gameInfo.boardWidth = 10;
6265 SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6267 case VariantXiangqi:
6268 pieces = XiangqiArray;
6269 gameInfo.boardWidth = 9;
6270 gameInfo.boardHeight = 10;
6271 nrCastlingRights = 0;
6272 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6275 pieces = ShogiArray;
6276 gameInfo.boardWidth = 9;
6277 gameInfo.boardHeight = 9;
6278 gameInfo.holdingsSize = 7;
6279 nrCastlingRights = 0;
6280 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6283 pieces = ChuArray; pieceRows = 3;
6284 gameInfo.boardWidth = 12;
6285 gameInfo.boardHeight = 12;
6286 nrCastlingRights = 0;
6287 // SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6288 // "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6289 SetCharTableEsc(pieceToChar, "P.BRQSEXOG...HD..^DLI^HNV........^T..^L.C...A^AFT/^F^G^M.^E^X^O^I.^P.^B^R..M^S^C^VK"
6290 "p.brqsexog...hd..^dli^hnv........^t..^l.c...a^aft/^f^g^m.^e^x^o^i.^p.^b^r..m^s^c^vk", SUFFIXES);
6292 case VariantCourier:
6293 pieces = CourierArray;
6294 gameInfo.boardWidth = 12;
6295 nrCastlingRights = 0;
6296 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6298 case VariantKnightmate:
6299 pieces = KnightmateArray;
6300 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6302 case VariantSpartan:
6303 pieces = SpartanArray;
6304 SetCharTable(pieceToChar, "PNBRQ.....................K......lw......g...h......ck");
6308 SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6310 case VariantChuChess:
6311 pieces = ChuChessArray;
6312 gameInfo.boardWidth = 10;
6313 gameInfo.boardHeight = 10;
6314 SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6317 pieces = fairyArray;
6318 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6321 pieces = GreatArray;
6322 gameInfo.boardWidth = 10;
6323 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6324 gameInfo.holdingsSize = 8;
6328 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6329 gameInfo.holdingsSize = 8;
6330 startedFromSetupPosition = TRUE;
6332 case VariantCrazyhouse:
6333 case VariantBughouse:
6335 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6336 gameInfo.holdingsSize = 5;
6338 case VariantWildCastle:
6340 /* !!?shuffle with kings guaranteed to be on d or e file */
6341 shuffleOpenings = 1;
6343 case VariantNoCastle:
6345 nrCastlingRights = 0;
6346 /* !!?unconstrained back-rank shuffle */
6347 shuffleOpenings = 1;
6352 if(appData.NrFiles >= 0) {
6353 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6354 gameInfo.boardWidth = appData.NrFiles;
6356 if(appData.NrRanks >= 0) {
6357 gameInfo.boardHeight = appData.NrRanks;
6359 if(appData.holdingsSize >= 0) {
6360 i = appData.holdingsSize;
6361 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6362 gameInfo.holdingsSize = i;
6364 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6365 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6366 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6368 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6369 if(pawnRow < 1) pawnRow = 1;
6370 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6371 gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6372 if(gameInfo.variant == VariantChu) pawnRow = 3;
6374 /* User pieceToChar list overrules defaults */
6375 if(appData.pieceToCharTable != NULL)
6376 SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6378 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6380 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6381 s = (ChessSquare) 0; /* account holding counts in guard band */
6382 for( i=0; i<BOARD_HEIGHT; i++ )
6383 initialPosition[i][j] = s;
6385 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6386 initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6387 initialPosition[pawnRow][j] = WhitePawn;
6388 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6389 if(gameInfo.variant == VariantXiangqi) {
6391 initialPosition[pawnRow][j] =
6392 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6393 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6394 initialPosition[2][j] = WhiteCannon;
6395 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6399 if(gameInfo.variant == VariantChu) {
6400 if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6401 initialPosition[pawnRow+1][j] = WhiteCobra,
6402 initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6403 for(i=1; i<pieceRows; i++) {
6404 initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6405 initialPosition[BOARD_HEIGHT-1-i][j] = pieces[2*i+1][j-gameInfo.holdingsWidth];
6408 if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6409 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6410 initialPosition[0][j] = WhiteRook;
6411 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6414 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] = pieces[1][j-gameInfo.holdingsWidth];
6416 if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6417 if( (gameInfo.variant == VariantShogi) && !overrule ) {
6420 initialPosition[1][j] = WhiteBishop;
6421 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6423 initialPosition[1][j] = WhiteRook;
6424 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6427 if( nrCastlingRights == -1) {
6428 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6429 /* This sets default castling rights from none to normal corners */
6430 /* Variants with other castling rights must set them themselves above */
6431 nrCastlingRights = 6;
6433 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6434 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6435 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6436 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6437 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6438 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6441 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6442 if(gameInfo.variant == VariantGreat) { // promotion commoners
6443 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6444 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6445 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6446 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6448 if( gameInfo.variant == VariantSChess ) {
6449 initialPosition[1][0] = BlackMarshall;
6450 initialPosition[2][0] = BlackAngel;
6451 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6452 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6453 initialPosition[1][1] = initialPosition[2][1] =
6454 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6456 if (appData.debugMode) {
6457 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6459 if(shuffleOpenings) {
6460 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6461 startedFromSetupPosition = TRUE;
6463 if(startedFromPositionFile) {
6464 /* [HGM] loadPos: use PositionFile for every new game */
6465 CopyBoard(initialPosition, filePosition);
6466 for(i=0; i<nrCastlingRights; i++)
6467 initialRights[i] = filePosition[CASTLING][i];
6468 startedFromSetupPosition = TRUE;
6470 if(*appData.men) LoadPieceDesc(appData.men);
6472 CopyBoard(boards[0], initialPosition);
6474 if(oldx != gameInfo.boardWidth ||
6475 oldy != gameInfo.boardHeight ||
6476 oldv != gameInfo.variant ||
6477 oldh != gameInfo.holdingsWidth
6479 InitDrawingSizes(-2 ,0);
6481 oldv = gameInfo.variant;
6483 DrawPosition(TRUE, boards[currentMove]);
6487 SendBoard (ChessProgramState *cps, int moveNum)
6489 char message[MSG_SIZ];
6491 if (cps->useSetboard) {
6492 char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6493 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6494 SendToProgram(message, cps);
6499 int i, j, left=0, right=BOARD_WIDTH;
6500 /* Kludge to set black to move, avoiding the troublesome and now
6501 * deprecated "black" command.
6503 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6504 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn && !pieceDesc[WhitePawn] ? "a2a3\n" : "black\nforce\n", cps);
6506 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6508 SendToProgram("edit\n", cps);
6509 SendToProgram("#\n", cps);
6510 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6511 bp = &boards[moveNum][i][left];
6512 for (j = left; j < right; j++, bp++) {
6513 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6514 if ((int) *bp < (int) BlackPawn) {
6515 if(j == BOARD_RGHT+1)
6516 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6517 else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6518 if(message[0] == '+' || message[0] == '~') {
6519 snprintf(message, MSG_SIZ,"%c%c%d+\n",
6520 PieceToChar((ChessSquare)(DEMOTED(*bp))),
6521 AAA + j, ONE + i - '0');
6523 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6524 message[1] = BOARD_RGHT - 1 - j + '1';
6525 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6527 SendToProgram(message, cps);
6532 SendToProgram("c\n", cps);
6533 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6534 bp = &boards[moveNum][i][left];
6535 for (j = left; j < right; j++, bp++) {
6536 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6537 if (((int) *bp != (int) EmptySquare)
6538 && ((int) *bp >= (int) BlackPawn)) {
6539 if(j == BOARD_LEFT-2)
6540 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6541 else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6542 AAA + j, ONE + i - '0');
6543 if(message[0] == '+' || message[0] == '~') {
6544 snprintf(message, MSG_SIZ,"%c%c%d+\n",
6545 PieceToChar((ChessSquare)(DEMOTED(*bp))),
6546 AAA + j, ONE + i - '0');
6548 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6549 message[1] = BOARD_RGHT - 1 - j + '1';
6550 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6552 SendToProgram(message, cps);
6557 SendToProgram(".\n", cps);
6559 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6562 char exclusionHeader[MSG_SIZ];
6563 int exCnt, excludePtr;
6564 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6565 static Exclusion excluTab[200];
6566 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6572 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6573 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6579 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6580 excludePtr = 24; exCnt = 0;
6585 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6586 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6587 char buf[2*MOVE_LEN], *p;
6588 Exclusion *e = excluTab;
6590 for(i=0; i<exCnt; i++)
6591 if(e[i].ff == fromX && e[i].fr == fromY &&
6592 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6593 if(i == exCnt) { // was not in exclude list; add it
6594 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6595 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6596 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6599 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6600 excludePtr++; e[i].mark = excludePtr++;
6601 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6604 exclusionHeader[e[i].mark] = state;
6608 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6609 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6613 if((signed char)promoChar == -1) { // kludge to indicate best move
6614 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6615 return 1; // if unparsable, abort
6617 // update exclusion map (resolving toggle by consulting existing state)
6618 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6620 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6621 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6622 excludeMap[k] |= 1<<j;
6623 else excludeMap[k] &= ~(1<<j);
6625 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6627 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6628 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6630 return (state == '+');
6634 ExcludeClick (int index)
6637 Exclusion *e = excluTab;
6638 if(index < 25) { // none, best or tail clicked
6639 if(index < 13) { // none: include all
6640 WriteMap(0); // clear map
6641 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6642 SendToBoth("include all\n"); // and inform engine
6643 } else if(index > 18) { // tail
6644 if(exclusionHeader[19] == '-') { // tail was excluded
6645 SendToBoth("include all\n");
6646 WriteMap(0); // clear map completely
6647 // now re-exclude selected moves
6648 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6649 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6650 } else { // tail was included or in mixed state
6651 SendToBoth("exclude all\n");
6652 WriteMap(0xFF); // fill map completely
6653 // now re-include selected moves
6654 j = 0; // count them
6655 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6656 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6657 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6660 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6663 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6664 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6665 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6672 DefaultPromoChoice (int white)
6675 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6676 gameInfo.variant == VariantMakruk)
6677 result = WhiteFerz; // no choice
6678 else if(gameInfo.variant == VariantASEAN)
6679 result = WhiteRook; // no choice
6680 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6681 result= WhiteKing; // in Suicide Q is the last thing we want
6682 else if(gameInfo.variant == VariantSpartan)
6683 result = white ? WhiteQueen : WhiteAngel;
6684 else result = WhiteQueen;
6685 if(!white) result = WHITE_TO_BLACK result;
6689 static int autoQueen; // [HGM] oneclick
6692 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6694 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6695 /* [HGM] add Shogi promotions */
6696 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6697 ChessSquare piece, partner;
6701 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6702 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6704 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6705 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6708 if(legal[toY][toX] == 4) return FALSE;
6710 piece = boards[currentMove][fromY][fromX];
6711 if(gameInfo.variant == VariantChu) {
6712 promotionZoneSize = BOARD_HEIGHT/3;
6713 if(legal[toY][toX] == 6) return FALSE; // no promotion if highlights deny it
6714 highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6715 } else if(gameInfo.variant == VariantShogi) {
6716 promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6717 highestPromotingPiece = (int)WhiteAlfil;
6718 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6719 promotionZoneSize = 3;
6722 // Treat Lance as Pawn when it is not representing Amazon or Lance
6723 if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6724 if(piece == WhiteLance) piece = WhitePawn; else
6725 if(piece == BlackLance) piece = BlackPawn;
6728 // next weed out all moves that do not touch the promotion zone at all
6729 if((int)piece >= BlackPawn) {
6730 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6732 if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6733 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6735 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6736 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6737 if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6741 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6743 // weed out mandatory Shogi promotions
6744 if(gameInfo.variant == VariantShogi) {
6745 if(piece >= BlackPawn) {
6746 if(toY == 0 && piece == BlackPawn ||
6747 toY == 0 && piece == BlackQueen ||
6748 toY <= 1 && piece == BlackKnight) {
6753 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6754 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6755 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6762 // weed out obviously illegal Pawn moves
6763 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6764 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6765 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6766 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6767 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6768 // note we are not allowed to test for valid (non-)capture, due to premove
6771 // we either have a choice what to promote to, or (in Shogi) whether to promote
6772 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6773 gameInfo.variant == VariantMakruk) {
6774 ChessSquare p=BlackFerz; // no choice
6775 while(p < EmptySquare) { //but make sure we use piece that exists
6776 *promoChoice = PieceToChar(p++);
6777 if(*promoChoice != '.') break;
6779 if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6781 // no sense asking what we must promote to if it is going to explode...
6782 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6783 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6786 // give caller the default choice even if we will not make it
6787 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6788 partner = piece; // pieces can promote if the pieceToCharTable says so
6789 if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6790 else if(Partner(&partner)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6791 if( sweepSelect && gameInfo.variant != VariantGreat
6792 && gameInfo.variant != VariantGrand
6793 && gameInfo.variant != VariantSuper) return FALSE;
6794 if(autoQueen) return FALSE; // predetermined
6796 // suppress promotion popup on illegal moves that are not premoves
6797 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6798 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6799 if(appData.testLegality && !premove) {
6800 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6801 fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6802 if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6803 if(moveType != WhitePromotion && moveType != BlackPromotion)
6811 InPalace (int row, int column)
6812 { /* [HGM] for Xiangqi */
6813 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6814 column < (BOARD_WIDTH + 4)/2 &&
6815 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6820 PieceForSquare (int x, int y)
6822 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6825 return boards[currentMove][y][x];
6829 OKToStartUserMove (int x, int y)
6831 ChessSquare from_piece;
6834 if (matchMode) return FALSE;
6835 if (gameMode == EditPosition) return TRUE;
6837 if (x >= 0 && y >= 0)
6838 from_piece = boards[currentMove][y][x];
6840 from_piece = EmptySquare;
6842 if (from_piece == EmptySquare) return FALSE;
6844 white_piece = (int)from_piece >= (int)WhitePawn &&
6845 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6849 case TwoMachinesPlay:
6857 case MachinePlaysWhite:
6858 case IcsPlayingBlack:
6859 if (appData.zippyPlay) return FALSE;
6861 DisplayMoveError(_("You are playing Black"));
6866 case MachinePlaysBlack:
6867 case IcsPlayingWhite:
6868 if (appData.zippyPlay) return FALSE;
6870 DisplayMoveError(_("You are playing White"));
6875 case PlayFromGameFile:
6876 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6879 if (!white_piece && WhiteOnMove(currentMove)) {
6880 DisplayMoveError(_("It is White's turn"));
6883 if (white_piece && !WhiteOnMove(currentMove)) {
6884 DisplayMoveError(_("It is Black's turn"));
6887 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6888 /* Editing correspondence game history */
6889 /* Could disallow this or prompt for confirmation */
6894 case BeginningOfGame:
6895 if (appData.icsActive) return FALSE;
6896 if (!appData.noChessProgram) {
6898 DisplayMoveError(_("You are playing White"));
6905 if (!white_piece && WhiteOnMove(currentMove)) {
6906 DisplayMoveError(_("It is White's turn"));
6909 if (white_piece && !WhiteOnMove(currentMove)) {
6910 DisplayMoveError(_("It is Black's turn"));
6919 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6920 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6921 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6922 && gameMode != AnalyzeFile && gameMode != Training) {
6923 DisplayMoveError(_("Displayed position is not current"));
6930 OnlyMove (int *x, int *y, Boolean captures)
6932 DisambiguateClosure cl;
6933 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6935 case MachinePlaysBlack:
6936 case IcsPlayingWhite:
6937 case BeginningOfGame:
6938 if(!WhiteOnMove(currentMove)) return FALSE;
6940 case MachinePlaysWhite:
6941 case IcsPlayingBlack:
6942 if(WhiteOnMove(currentMove)) return FALSE;
6949 cl.pieceIn = EmptySquare;
6954 cl.promoCharIn = NULLCHAR;
6955 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6956 if( cl.kind == NormalMove ||
6957 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6958 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6959 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6966 if(cl.kind != ImpossibleMove) return FALSE;
6967 cl.pieceIn = EmptySquare;
6972 cl.promoCharIn = NULLCHAR;
6973 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6974 if( cl.kind == NormalMove ||
6975 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6976 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6977 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6982 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6988 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6989 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6990 int lastLoadGameUseList = FALSE;
6991 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6992 ChessMove lastLoadGameStart = EndOfFile;
6994 Boolean addToBookFlag;
6995 static Board rightsBoard, nullBoard;
6998 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
7002 int ff=fromX, rf=fromY, ft=toX, rt=toY;
7004 /* Check if the user is playing in turn. This is complicated because we
7005 let the user "pick up" a piece before it is his turn. So the piece he
7006 tried to pick up may have been captured by the time he puts it down!
7007 Therefore we use the color the user is supposed to be playing in this
7008 test, not the color of the piece that is currently on the starting
7009 square---except in EditGame mode, where the user is playing both
7010 sides; fortunately there the capture race can't happen. (It can
7011 now happen in IcsExamining mode, but that's just too bad. The user
7012 will get a somewhat confusing message in that case.)
7017 case TwoMachinesPlay:
7021 /* We switched into a game mode where moves are not accepted,
7022 perhaps while the mouse button was down. */
7025 case MachinePlaysWhite:
7026 /* User is moving for Black */
7027 if (WhiteOnMove(currentMove)) {
7028 DisplayMoveError(_("It is White's turn"));
7033 case MachinePlaysBlack:
7034 /* User is moving for White */
7035 if (!WhiteOnMove(currentMove)) {
7036 DisplayMoveError(_("It is Black's turn"));
7041 case PlayFromGameFile:
7042 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7045 case BeginningOfGame:
7048 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7049 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7050 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7051 /* User is moving for Black */
7052 if (WhiteOnMove(currentMove)) {
7053 DisplayMoveError(_("It is White's turn"));
7057 /* User is moving for White */
7058 if (!WhiteOnMove(currentMove)) {
7059 DisplayMoveError(_("It is Black's turn"));
7065 case IcsPlayingBlack:
7066 /* User is moving for Black */
7067 if (WhiteOnMove(currentMove)) {
7068 if (!appData.premove) {
7069 DisplayMoveError(_("It is White's turn"));
7070 } else if (toX >= 0 && toY >= 0) {
7073 premoveFromX = fromX;
7074 premoveFromY = fromY;
7075 premovePromoChar = promoChar;
7077 if (appData.debugMode)
7078 fprintf(debugFP, "Got premove: fromX %d,"
7079 "fromY %d, toX %d, toY %d\n",
7080 fromX, fromY, toX, toY);
7082 DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7087 case IcsPlayingWhite:
7088 /* User is moving for White */
7089 if (!WhiteOnMove(currentMove)) {
7090 if (!appData.premove) {
7091 DisplayMoveError(_("It is Black's turn"));
7092 } else if (toX >= 0 && toY >= 0) {
7095 premoveFromX = fromX;
7096 premoveFromY = fromY;
7097 premovePromoChar = promoChar;
7099 if (appData.debugMode)
7100 fprintf(debugFP, "Got premove: fromX %d,"
7101 "fromY %d, toX %d, toY %d\n",
7102 fromX, fromY, toX, toY);
7104 DrawPosition(TRUE, boards[currentMove]);
7113 /* EditPosition, empty square, or different color piece;
7114 click-click move is possible */
7115 if (toX == -2 || toY == -2) {
7116 boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7117 DrawPosition(FALSE, boards[currentMove]);
7119 } else if (toX >= 0 && toY >= 0) {
7120 if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7121 ChessSquare p = boards[0][rf][ff];
7122 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7123 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7124 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
7125 int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7126 DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7129 } else rightsBoard[toY][toX] = 0; // revoke rights on moving
7130 boards[0][toY][toX] = boards[0][fromY][fromX];
7131 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7132 if(boards[0][fromY][0] != EmptySquare) {
7133 if(boards[0][fromY][1]) boards[0][fromY][1]--;
7134 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
7137 if(fromX == BOARD_RGHT+1) {
7138 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7139 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7140 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7143 boards[0][fromY][fromX] = gatingPiece;
7145 DrawPosition(FALSE, boards[currentMove]);
7151 if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7152 pup = boards[currentMove][toY][toX];
7154 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7155 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7156 if( pup != EmptySquare ) return;
7157 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7158 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7159 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7160 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7161 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7162 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7163 while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7167 /* [HGM] always test for legality, to get promotion info */
7168 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7169 fromY, fromX, toY, toX, promoChar);
7171 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7173 if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7175 /* [HGM] but possibly ignore an IllegalMove result */
7176 if (appData.testLegality) {
7177 if (moveType == IllegalMove || moveType == ImpossibleMove) {
7178 DisplayMoveError(_("Illegal move"));
7183 if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7184 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7185 ClearPremoveHighlights(); // was included
7186 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
7187 DrawPosition(FALSE, NULL);
7191 if(addToBookFlag) { // adding moves to book
7192 char buf[MSG_SIZ], move[MSG_SIZ];
7193 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7194 if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7195 killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7196 snprintf(buf, MSG_SIZ, " 0.0%% 1 %s\n", move);
7198 addToBookFlag = FALSE;
7203 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7206 /* Common tail of UserMoveEvent and DropMenuEvent */
7208 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7212 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7213 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7214 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7215 if(WhiteOnMove(currentMove)) {
7216 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7218 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7222 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7223 move type in caller when we know the move is a legal promotion */
7224 if(moveType == NormalMove && promoChar)
7225 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7227 /* [HGM] <popupFix> The following if has been moved here from
7228 UserMoveEvent(). Because it seemed to belong here (why not allow
7229 piece drops in training games?), and because it can only be
7230 performed after it is known to what we promote. */
7231 if (gameMode == Training) {
7232 /* compare the move played on the board to the next move in the
7233 * game. If they match, display the move and the opponent's response.
7234 * If they don't match, display an error message.
7238 CopyBoard(testBoard, boards[currentMove]);
7239 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7241 if (CompareBoards(testBoard, boards[currentMove+1])) {
7242 ForwardInner(currentMove+1);
7244 /* Autoplay the opponent's response.
7245 * if appData.animate was TRUE when Training mode was entered,
7246 * the response will be animated.
7248 saveAnimate = appData.animate;
7249 appData.animate = animateTraining;
7250 ForwardInner(currentMove+1);
7251 appData.animate = saveAnimate;
7253 /* check for the end of the game */
7254 if (currentMove >= forwardMostMove) {
7255 gameMode = PlayFromGameFile;
7257 SetTrainingModeOff();
7258 DisplayInformation(_("End of game"));
7261 DisplayError(_("Incorrect move"), 0);
7266 /* Ok, now we know that the move is good, so we can kill
7267 the previous line in Analysis Mode */
7268 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7269 && currentMove < forwardMostMove) {
7270 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7271 else forwardMostMove = currentMove;
7276 /* If we need the chess program but it's dead, restart it */
7277 ResurrectChessProgram();
7279 /* A user move restarts a paused game*/
7283 thinkOutput[0] = NULLCHAR;
7285 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7287 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7288 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7292 if (gameMode == BeginningOfGame) {
7293 if (appData.noChessProgram) {
7294 gameMode = EditGame;
7298 gameMode = MachinePlaysBlack;
7301 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7303 if (first.sendName) {
7304 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7305 SendToProgram(buf, &first);
7312 /* Relay move to ICS or chess engine */
7313 if (appData.icsActive) {
7314 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7315 gameMode == IcsExamining) {
7316 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7317 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7319 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7321 // also send plain move, in case ICS does not understand atomic claims
7322 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7326 if (first.sendTime && (gameMode == BeginningOfGame ||
7327 gameMode == MachinePlaysWhite ||
7328 gameMode == MachinePlaysBlack)) {
7329 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7331 if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7332 // [HGM] book: if program might be playing, let it use book
7333 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7334 first.maybeThinking = TRUE;
7335 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7336 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7337 SendBoard(&first, currentMove+1);
7338 if(second.analyzing) {
7339 if(!second.useSetboard) SendToProgram("undo\n", &second);
7340 SendBoard(&second, currentMove+1);
7343 SendMoveToProgram(forwardMostMove-1, &first);
7344 if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7346 if (currentMove == cmailOldMove + 1) {
7347 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7351 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7355 if(appData.testLegality)
7356 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7362 if (WhiteOnMove(currentMove)) {
7363 GameEnds(BlackWins, "Black mates", GE_PLAYER);
7365 GameEnds(WhiteWins, "White mates", GE_PLAYER);
7369 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7374 case MachinePlaysBlack:
7375 case MachinePlaysWhite:
7376 /* disable certain menu options while machine is thinking */
7377 SetMachineThinkingEnables();
7384 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7385 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7387 if(bookHit) { // [HGM] book: simulate book reply
7388 static char bookMove[MSG_SIZ]; // a bit generous?
7390 programStats.nodes = programStats.depth = programStats.time =
7391 programStats.score = programStats.got_only_move = 0;
7392 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7394 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7395 strcat(bookMove, bookHit);
7396 HandleMachineMove(bookMove, &first);
7402 MarkByFEN(char *fen)
7405 if(!appData.markers || !appData.highlightDragging) return;
7406 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = marker[r][f] = 0;
7407 r=BOARD_HEIGHT-1-deadRanks; f=BOARD_LEFT;
7410 if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7411 if(*fen == 'B') legal[r][f] = 4; else // request auto-promotion to victim
7412 if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 6; else
7413 if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7414 if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7415 if(*fen == 'T') marker[r][f++] = 0; else
7416 if(*fen == 'Y') marker[r][f++] = 1; else
7417 if(*fen == 'G') marker[r][f++] = 3; else
7418 if(*fen == 'B') marker[r][f++] = 4; else
7419 if(*fen == 'C') marker[r][f++] = 5; else
7420 if(*fen == 'M') marker[r][f++] = 6; else
7421 if(*fen == 'W') marker[r][f++] = 7; else
7422 if(*fen == 'D') marker[r][f++] = 8; else
7423 if(*fen == 'R') marker[r][f++] = 2; else {
7424 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7427 while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7431 DrawPosition(TRUE, NULL);
7434 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7437 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7439 typedef char Markers[BOARD_RANKS][BOARD_FILES];
7440 Markers *m = (Markers *) closure;
7441 if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7442 kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7443 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7444 || kind == WhiteCapturesEnPassant
7445 || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7446 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7449 static int hoverSavedValid;
7452 MarkTargetSquares (int clear)
7455 if(clear) { // no reason to ever suppress clearing
7456 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7457 hoverSavedValid = 0;
7458 if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7461 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7462 !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7463 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7464 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7465 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7467 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7470 DrawPosition(FALSE, NULL);
7474 Explode (Board board, int fromX, int fromY, int toX, int toY)
7476 if(gameInfo.variant == VariantAtomic &&
7477 (board[toY][toX] != EmptySquare || // capture?
7478 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
7479 board[fromY][fromX] == BlackPawn )
7481 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7487 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7490 CanPromote (ChessSquare piece, int y)
7492 int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7493 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7494 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7495 if(IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantXiangqi ||
7496 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
7497 (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7498 gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7499 return (piece == BlackPawn && y <= zone ||
7500 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7501 piece == BlackLance && y <= zone ||
7502 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7506 HoverEvent (int xPix, int yPix, int x, int y)
7508 static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7510 if(!first.highlight) return;
7511 if(fromX != oldFromX || fromY != oldFromY) oldX = oldY = -1; // kludge to fake entry on from-click
7512 if(x == oldX && y == oldY) return; // only do something if we enter new square
7513 oldFromX = fromX; oldFromY = fromY;
7514 if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7515 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7516 baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7517 hoverSavedValid = 1;
7518 } else if(oldX != x || oldY != y) {
7519 // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7520 if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7521 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7522 marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7523 if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7525 snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7526 SendToProgram(buf, &first);
7529 // SetHighlights(fromX, fromY, x, y);
7533 void ReportClick(char *action, int x, int y)
7535 char buf[MSG_SIZ]; // Inform engine of what user does
7537 if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7538 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7539 legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7540 if(!first.highlight || gameMode == EditPosition) return;
7541 snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7542 SendToProgram(buf, &first);
7545 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7546 Boolean deferChoice;
7549 LeftClick (ClickType clickType, int xPix, int yPix)
7552 static Boolean saveAnimate;
7553 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7554 char promoChoice = NULLCHAR;
7556 static TimeMark lastClickTime, prevClickTime;
7558 if(flashing) return;
7560 if(!deferChoice) { // when called for a retry, skip everything to the point where we left off
7561 x = EventToSquare(xPix, BOARD_WIDTH);
7562 y = EventToSquare(yPix, BOARD_HEIGHT);
7563 if (!flipView && y >= 0) {
7564 y = BOARD_HEIGHT - 1 - y;
7566 if (flipView && x >= 0) {
7567 x = BOARD_WIDTH - 1 - x;
7570 if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7572 RightClick(clickType, xPix, yPix, &dummy, &dummy);
7577 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7579 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7581 if (clickType == Press) ErrorPopDown();
7582 lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7584 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7585 defaultPromoChoice = promoSweep;
7586 promoSweep = EmptySquare; // terminate sweep
7587 promoDefaultAltered = TRUE;
7588 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7591 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7592 if(clickType == Release) return; // ignore upclick of click-click destination
7593 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7594 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7595 if(gameInfo.holdingsWidth &&
7596 (WhiteOnMove(currentMove)
7597 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7598 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7599 // click in right holdings, for determining promotion piece
7600 ChessSquare p = boards[currentMove][y][x];
7601 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7602 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7603 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7604 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7609 DrawPosition(FALSE, boards[currentMove]);
7613 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7614 if(clickType == Press
7615 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7616 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7617 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7620 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7621 // could be static click on premove from-square: abort premove
7623 ClearPremoveHighlights();
7626 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7627 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7629 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7630 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7631 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7632 defaultPromoChoice = DefaultPromoChoice(side);
7635 autoQueen = appData.alwaysPromoteToQueen;
7639 gatingPiece = EmptySquare;
7640 if (clickType != Press) {
7641 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7642 DragPieceEnd(xPix, yPix); dragging = 0;
7643 DrawPosition(FALSE, NULL);
7647 doubleClick = FALSE;
7648 if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7649 doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7651 fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7652 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7653 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7654 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7656 if (OKToStartUserMove(fromX, fromY)) {
7658 ReportClick("lift", x, y);
7659 MarkTargetSquares(0);
7660 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7661 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7662 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7663 promoSweep = defaultPromoChoice;
7664 selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7665 Sweep(0); // Pawn that is going to promote: preview promotion piece
7666 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7668 if (appData.highlightDragging) {
7669 SetHighlights(fromX, fromY, -1, -1);
7673 } else fromX = fromY = -1;
7679 if (clickType == Press && gameMode != EditPosition) {
7684 // ignore off-board to clicks
7685 if(y < 0 || x < 0) return;
7687 /* Check if clicking again on the same color piece */
7688 fromP = boards[currentMove][fromY][fromX];
7689 toP = boards[currentMove][y][x];
7690 frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7691 if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7692 marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7693 ((WhitePawn <= fromP && fromP <= WhiteKing &&
7694 WhitePawn <= toP && toP <= WhiteKing &&
7695 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7696 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7697 (BlackPawn <= fromP && fromP <= BlackKing &&
7698 BlackPawn <= toP && toP <= BlackKing &&
7699 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7700 !(fromP == BlackKing && toP == BlackRook && frc)))) {
7701 /* Clicked again on same color piece -- changed his mind */
7702 second = (x == fromX && y == fromY);
7703 killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7704 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7705 second = FALSE; // first double-click rather than scond click
7706 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7708 promoDefaultAltered = FALSE;
7709 if(!second) MarkTargetSquares(1);
7710 if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7711 if (appData.highlightDragging) {
7712 SetHighlights(x, y, -1, -1);
7716 if (OKToStartUserMove(x, y)) {
7717 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7718 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7719 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7720 gatingPiece = boards[currentMove][fromY][fromX];
7721 else gatingPiece = doubleClick ? fromP : EmptySquare;
7723 fromY = y; dragging = 1;
7724 if(!second) ReportClick("lift", x, y);
7725 MarkTargetSquares(0);
7726 DragPieceBegin(xPix, yPix, FALSE);
7727 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7728 promoSweep = defaultPromoChoice;
7729 selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7730 Sweep(0); // Pawn that is going to promote: preview promotion piece
7734 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7737 // ignore clicks on holdings
7738 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7741 if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7742 gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7743 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7747 if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7748 DragPieceEnd(xPix, yPix); dragging = 0;
7750 // a deferred attempt to click-click move an empty square on top of a piece
7751 boards[currentMove][y][x] = EmptySquare;
7753 DrawPosition(FALSE, boards[currentMove]);
7754 fromX = fromY = -1; clearFlag = 0;
7757 if (appData.animateDragging) {
7758 /* Undo animation damage if any */
7759 DrawPosition(FALSE, NULL);
7762 /* Second up/down in same square; just abort move */
7765 gatingPiece = EmptySquare;
7768 ClearPremoveHighlights();
7769 MarkTargetSquares(-1);
7770 DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7772 /* First upclick in same square; start click-click mode */
7773 SetHighlights(x, y, -1, -1);
7780 if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7781 fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7782 if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7783 DisplayMessage(_("only marked squares are legal"),"");
7784 DrawPosition(TRUE, NULL);
7785 return; // ignore to-click
7788 /* we now have a different from- and (possibly off-board) to-square */
7789 /* Completed move */
7790 if(!sweepSelecting) {
7795 piece = boards[currentMove][fromY][fromX];
7797 saveAnimate = appData.animate;
7798 if (clickType == Press) {
7799 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7800 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7801 // must be Edit Position mode with empty-square selected
7802 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7803 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7806 if(dragging == 2) { // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7809 if(x == killX && y == killY) { // second click on this square, which was selected as first-leg target
7810 killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1; // this informs us no second leg is coming, so treat as to-click without intermediate
7812 if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7813 if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7814 if(appData.sweepSelect) {
7815 promoSweep = defaultPromoChoice;
7816 if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7817 selectFlag = 0; lastX = xPix; lastY = yPix;
7818 ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7819 saveFlash = appData.flashCount; appData.flashCount = 0;
7820 Sweep(0); // Pawn that is going to promote: preview promotion piece
7822 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7823 MarkTargetSquares(1);
7825 return; // promo popup appears on up-click
7827 /* Finish clickclick move */
7828 if (appData.animate || appData.highlightLastMove) {
7829 SetHighlights(fromX, fromY, toX, toY);
7833 MarkTargetSquares(1);
7834 } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7835 sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7836 *promoRestrict = 0; appData.flashCount = saveFlash;
7837 if (appData.animate || appData.highlightLastMove) {
7838 SetHighlights(fromX, fromY, toX, toY);
7842 MarkTargetSquares(1);
7845 // [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
7846 /* Finish drag move */
7847 if (appData.highlightLastMove) {
7848 SetHighlights(fromX, fromY, toX, toY);
7853 if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7854 defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7855 if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7856 if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7857 dragging *= 2; // flag button-less dragging if we are dragging
7858 MarkTargetSquares(1);
7859 if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7861 kill2X = killX; kill2Y = killY;
7862 killX = x; killY = y; // remember this square as intermediate
7863 ReportClick("put", x, y); // and inform engine
7864 ReportClick("lift", x, y);
7865 MarkTargetSquares(0);
7869 DragPieceEnd(xPix, yPix); dragging = 0;
7870 /* Don't animate move and drag both */
7871 appData.animate = FALSE;
7872 MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7875 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7876 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7877 ChessSquare piece = boards[currentMove][fromY][fromX];
7878 if(gameMode == EditPosition && piece != EmptySquare &&
7879 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7882 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7883 n = PieceToNumber(piece - (int)BlackPawn);
7884 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7885 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7886 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7888 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7889 n = PieceToNumber(piece);
7890 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7891 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7892 boards[currentMove][n][BOARD_WIDTH-2]++;
7894 boards[currentMove][fromY][fromX] = EmptySquare;
7898 MarkTargetSquares(1);
7899 DrawPosition(TRUE, boards[currentMove]);
7903 // off-board moves should not be highlighted
7904 if(x < 0 || y < 0) {
7906 DrawPosition(FALSE, NULL);
7907 } else ReportClick("put", x, y);
7909 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7912 if(legal[toY][toX] == 2) { // highlight-induced promotion
7913 if(piece == defaultPromoChoice) promoChoice = NULLCHAR; // deferral
7914 else promoChoice = ToLower(PieceToChar(defaultPromoChoice));
7915 } else if(legal[toY][toX] == 4) { // blue target square: engine must supply promotion choice
7916 if(!*promoRestrict) { // but has not done that yet
7917 deferChoice = TRUE; // set up retry for when it does
7918 return; // and wait for that
7920 promoChoice = ToLower(*promoRestrict); // force engine's choice
7921 deferChoice = FALSE;
7924 if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7925 SetHighlights(fromX, fromY, toX, toY);
7926 MarkTargetSquares(1);
7927 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7928 // [HGM] super: promotion to captured piece selected from holdings
7929 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7930 promotionChoice = TRUE;
7931 // kludge follows to temporarily execute move on display, without promoting yet
7932 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7933 boards[currentMove][toY][toX] = p;
7934 DrawPosition(FALSE, boards[currentMove]);
7935 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7936 boards[currentMove][toY][toX] = q;
7937 DisplayMessage("Click in holdings to choose piece", "");
7940 DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
7941 PromotionPopUp(promoChoice);
7943 int oldMove = currentMove;
7944 flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
7945 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7946 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7947 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
7948 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7949 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7950 DrawPosition(TRUE, boards[currentMove]);
7951 else DrawPosition(FALSE, NULL);
7955 appData.animate = saveAnimate;
7956 if (appData.animate || appData.animateDragging) {
7957 /* Undo animation damage if needed */
7958 // DrawPosition(FALSE, NULL);
7963 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7964 { // front-end-free part taken out of PieceMenuPopup
7965 int whichMenu; int xSqr, ySqr;
7967 if(seekGraphUp) { // [HGM] seekgraph
7968 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7969 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7973 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7974 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7975 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7976 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7977 if(action == Press) {
7978 originalFlip = flipView;
7979 flipView = !flipView; // temporarily flip board to see game from partners perspective
7980 DrawPosition(TRUE, partnerBoard);
7981 DisplayMessage(partnerStatus, "");
7983 } else if(action == Release) {
7984 flipView = originalFlip;
7985 DrawPosition(TRUE, boards[currentMove]);
7991 xSqr = EventToSquare(x, BOARD_WIDTH);
7992 ySqr = EventToSquare(y, BOARD_HEIGHT);
7993 if (action == Release) {
7994 if(pieceSweep != EmptySquare) {
7995 EditPositionMenuEvent(pieceSweep, toX, toY);
7996 pieceSweep = EmptySquare;
7997 } else UnLoadPV(); // [HGM] pv
7999 if (action != Press) return -2; // return code to be ignored
8002 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
8004 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
8005 if (xSqr < 0 || ySqr < 0) return -1;
8006 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
8007 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
8008 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
8009 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
8013 if(!appData.icsEngineAnalyze) return -1;
8014 case IcsPlayingWhite:
8015 case IcsPlayingBlack:
8016 if(!appData.zippyPlay) goto noZip;
8019 case MachinePlaysWhite:
8020 case MachinePlaysBlack:
8021 case TwoMachinesPlay: // [HGM] pv: use for showing PV
8022 if (!appData.dropMenu) {
8024 return 2; // flag front-end to grab mouse events
8026 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8027 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8030 if (xSqr < 0 || ySqr < 0) return -1;
8031 if (!appData.dropMenu || appData.testLegality &&
8032 gameInfo.variant != VariantBughouse &&
8033 gameInfo.variant != VariantCrazyhouse) return -1;
8034 whichMenu = 1; // drop menu
8040 if (((*fromX = xSqr) < 0) ||
8041 ((*fromY = ySqr) < 0)) {
8042 *fromX = *fromY = -1;
8046 *fromX = BOARD_WIDTH - 1 - *fromX;
8048 *fromY = BOARD_HEIGHT - 1 - *fromY;
8054 Wheel (int dir, int x, int y)
8056 if(gameMode == EditPosition) {
8057 int xSqr = EventToSquare(x, BOARD_WIDTH);
8058 int ySqr = EventToSquare(y, BOARD_HEIGHT);
8059 if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8060 if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8062 boards[currentMove][ySqr][xSqr] += dir;
8063 if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8064 if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8065 } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8066 DrawPosition(FALSE, boards[currentMove]);
8067 } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8071 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8073 // char * hint = lastHint;
8074 FrontEndProgramStats stats;
8076 stats.which = cps == &first ? 0 : 1;
8077 stats.depth = cpstats->depth;
8078 stats.nodes = cpstats->nodes;
8079 stats.score = cpstats->score;
8080 stats.time = cpstats->time;
8081 stats.pv = cpstats->movelist;
8082 stats.hint = lastHint;
8083 stats.an_move_index = 0;
8084 stats.an_move_count = 0;
8086 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8087 stats.hint = cpstats->move_name;
8088 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8089 stats.an_move_count = cpstats->nr_moves;
8092 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
8094 if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8095 && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8097 SetProgramStats( &stats );
8101 ClearEngineOutputPane (int which)
8103 static FrontEndProgramStats dummyStats;
8104 dummyStats.which = which;
8105 dummyStats.pv = "#";
8106 SetProgramStats( &dummyStats );
8109 #define MAXPLAYERS 500
8112 TourneyStandings (int display)
8114 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8115 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8116 char result, *p, *names[MAXPLAYERS];
8118 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8119 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8120 names[0] = p = strdup(appData.participants);
8121 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8123 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8125 while(result = appData.results[nr]) {
8126 color = Pairing(nr, nPlayers, &w, &b, &dummy);
8127 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8128 wScore = bScore = 0;
8130 case '+': wScore = 2; break;
8131 case '-': bScore = 2; break;
8132 case '=': wScore = bScore = 1; break;
8134 case '*': return strdup("busy"); // tourney not finished
8142 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8143 for(w=0; w<nPlayers; w++) {
8145 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8146 ranking[w] = b; points[w] = bScore; score[b] = -2;
8148 p = malloc(nPlayers*34+1);
8149 for(w=0; w<nPlayers && w<display; w++)
8150 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8156 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8157 { // count all piece types
8159 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8160 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8161 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8164 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8165 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8166 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8167 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8168 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
8169 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8174 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8176 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8177 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8179 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8180 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8181 if(myPawns == 2 && nMine == 3) // KPP
8182 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8183 if(myPawns == 1 && nMine == 2) // KP
8184 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8185 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8186 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8187 if(myPawns) return FALSE;
8188 if(pCnt[WhiteRook+side])
8189 return pCnt[BlackRook-side] ||
8190 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8191 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8192 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8193 if(pCnt[WhiteCannon+side]) {
8194 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8195 return majorDefense || pCnt[BlackAlfil-side] >= 2;
8197 if(pCnt[WhiteKnight+side])
8198 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8203 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8205 VariantClass v = gameInfo.variant;
8207 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8208 if(v == VariantShatranj) return TRUE; // always winnable through baring
8209 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8210 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8212 if(v == VariantXiangqi) {
8213 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8215 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8216 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8217 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8218 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8219 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8220 if(stale) // we have at least one last-rank P plus perhaps C
8221 return majors // KPKX
8222 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8224 return pCnt[WhiteFerz+side] // KCAK
8225 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8226 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8227 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8229 } else if(v == VariantKnightmate) {
8230 if(nMine == 1) return FALSE;
8231 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8232 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8233 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8235 if(nMine == 1) return FALSE; // bare King
8236 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
8237 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8238 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8239 // by now we have King + 1 piece (or multiple Bishops on the same color)
8240 if(pCnt[WhiteKnight+side])
8241 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8242 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8243 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8245 return (pCnt[BlackKnight-side]); // KBKN, KFKN
8246 if(pCnt[WhiteAlfil+side])
8247 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8248 if(pCnt[WhiteWazir+side])
8249 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8256 CompareWithRights (Board b1, Board b2)
8259 if(!CompareBoards(b1, b2)) return FALSE;
8260 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8261 /* compare castling rights */
8262 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8263 rights++; /* King lost rights, while rook still had them */
8264 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8265 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8266 rights++; /* but at least one rook lost them */
8268 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8270 if( b1[CASTLING][5] != NoRights ) {
8271 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8278 Adjudicate (ChessProgramState *cps)
8279 { // [HGM] some adjudications useful with buggy engines
8280 // [HGM] adjudicate: made into separate routine, which now can be called after every move
8281 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8282 // Actually ending the game is now based on the additional internal condition canAdjudicate.
8283 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8284 int k, drop, count = 0; static int bare = 1;
8285 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8286 Boolean canAdjudicate = !appData.icsActive;
8288 // most tests only when we understand the game, i.e. legality-checking on
8289 if( appData.testLegality )
8290 { /* [HGM] Some more adjudications for obstinate engines */
8291 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8292 static int moveCount = 6;
8294 char *reason = NULL;
8296 /* Count what is on board. */
8297 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8299 /* Some material-based adjudications that have to be made before stalemate test */
8300 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8301 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8302 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8303 if(canAdjudicate && appData.checkMates) {
8305 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8306 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8307 "Xboard adjudication: King destroyed", GE_XBOARD );
8312 /* Bare King in Shatranj (loses) or Losers (wins) */
8313 if( nrW == 1 || nrB == 1) {
8314 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8315 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
8316 if(canAdjudicate && appData.checkMates) {
8318 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8319 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8320 "Xboard adjudication: Bare king", GE_XBOARD );
8324 if( gameInfo.variant == VariantShatranj && --bare < 0)
8326 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8327 if(canAdjudicate && appData.checkMates) {
8328 /* but only adjudicate if adjudication enabled */
8330 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8331 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8332 "Xboard adjudication: Bare king", GE_XBOARD );
8339 // don't wait for engine to announce game end if we can judge ourselves
8340 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8342 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8343 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
8344 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8345 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8348 reason = "Xboard adjudication: 3rd check";
8349 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8360 reason = "Xboard adjudication: Stalemate";
8361 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8362 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
8363 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8364 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
8365 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8366 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8367 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8368 EP_CHECKMATE : EP_WINS);
8369 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8370 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8374 reason = "Xboard adjudication: Checkmate";
8375 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8376 if(gameInfo.variant == VariantShogi) {
8377 if(forwardMostMove > backwardMostMove
8378 && moveList[forwardMostMove-1][1] == '@'
8379 && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8380 reason = "XBoard adjudication: pawn-drop mate";
8381 boards[forwardMostMove][EP_STATUS] = EP_WINS;
8387 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8389 result = GameIsDrawn; break;
8391 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8393 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8397 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8399 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8400 GameEnds( result, reason, GE_XBOARD );
8404 /* Next absolutely insufficient mating material. */
8405 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8406 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8407 { /* includes KBK, KNK, KK of KBKB with like Bishops */
8409 /* always flag draws, for judging claims */
8410 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8412 if(canAdjudicate && appData.materialDraws) {
8413 /* but only adjudicate them if adjudication enabled */
8414 if(engineOpponent) {
8415 SendToProgram("force\n", engineOpponent); // suppress reply
8416 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8418 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8423 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8424 if(gameInfo.variant == VariantXiangqi ?
8425 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8427 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8428 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
8429 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
8430 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8432 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8433 { /* if the first 3 moves do not show a tactical win, declare draw */
8434 if(engineOpponent) {
8435 SendToProgram("force\n", engineOpponent); // suppress reply
8436 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8438 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8441 } else moveCount = 6;
8444 // Repetition draws and 50-move rule can be applied independently of legality testing
8446 /* Check for rep-draws */
8448 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8449 && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8450 for(k = forwardMostMove-2;
8451 k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8452 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8453 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8456 if(CompareBoards(boards[k], boards[forwardMostMove])) {
8457 /* compare castling rights */
8458 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8459 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8460 rights++; /* King lost rights, while rook still had them */
8461 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8462 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8463 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8464 rights++; /* but at least one rook lost them */
8466 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8467 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8469 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8470 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8471 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8474 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8475 && appData.drawRepeats > 1) {
8476 /* adjudicate after user-specified nr of repeats */
8477 int result = GameIsDrawn;
8478 char *details = "XBoard adjudication: repetition draw";
8479 if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8480 // [HGM] xiangqi: check for forbidden perpetuals
8481 int m, ourPerpetual = 1, hisPerpetual = 1;
8482 for(m=forwardMostMove; m>k; m-=2) {
8483 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8484 ourPerpetual = 0; // the current mover did not always check
8485 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8486 hisPerpetual = 0; // the opponent did not always check
8488 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8489 ourPerpetual, hisPerpetual);
8490 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8491 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8492 details = "Xboard adjudication: perpetual checking";
8494 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8495 break; // (or we would have caught him before). Abort repetition-checking loop.
8497 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8498 if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8500 details = "Xboard adjudication: repetition";
8502 } else // it must be XQ
8503 // Now check for perpetual chases
8504 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8505 hisPerpetual = PerpetualChase(k, forwardMostMove);
8506 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8507 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8508 static char resdet[MSG_SIZ];
8509 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8511 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8513 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
8514 break; // Abort repetition-checking loop.
8516 // if neither of us is checking or chasing all the time, or both are, it is draw
8518 if(engineOpponent) {
8519 SendToProgram("force\n", engineOpponent); // suppress reply
8520 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8522 GameEnds( result, details, GE_XBOARD );
8525 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8526 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8530 /* Now we test for 50-move draws. Determine ply count */
8531 count = forwardMostMove;
8532 /* look for last irreversble move */
8533 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8535 /* if we hit starting position, add initial plies */
8536 if( count == backwardMostMove )
8537 count -= initialRulePlies;
8538 count = forwardMostMove - count;
8539 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8540 // adjust reversible move counter for checks in Xiangqi
8541 int i = forwardMostMove - count, inCheck = 0, lastCheck;
8542 if(i < backwardMostMove) i = backwardMostMove;
8543 while(i <= forwardMostMove) {
8544 lastCheck = inCheck; // check evasion does not count
8545 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8546 if(inCheck || lastCheck) count--; // check does not count
8551 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8552 /* this is used to judge if draw claims are legal */
8553 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8554 if(engineOpponent) {
8555 SendToProgram("force\n", engineOpponent); // suppress reply
8556 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8558 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8562 /* if draw offer is pending, treat it as a draw claim
8563 * when draw condition present, to allow engines a way to
8564 * claim draws before making their move to avoid a race
8565 * condition occurring after their move
8567 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8569 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8570 p = "Draw claim: 50-move rule";
8571 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8572 p = "Draw claim: 3-fold repetition";
8573 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8574 p = "Draw claim: insufficient mating material";
8575 if( p != NULL && canAdjudicate) {
8576 if(engineOpponent) {
8577 SendToProgram("force\n", engineOpponent); // suppress reply
8578 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8580 GameEnds( GameIsDrawn, p, GE_XBOARD );
8585 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8586 if(engineOpponent) {
8587 SendToProgram("force\n", engineOpponent); // suppress reply
8588 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8590 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8596 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8597 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8598 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8603 int pieces[10], squares[10], cnt=0, r, f, res;
8605 static PPROBE_EGBB probeBB;
8606 if(!appData.testLegality) return 10;
8607 if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8608 if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8609 if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8610 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8611 ChessSquare piece = boards[forwardMostMove][r][f];
8612 int black = (piece >= BlackPawn);
8613 int type = piece - black*BlackPawn;
8614 if(piece == EmptySquare) continue;
8615 if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8616 if(type == WhiteKing) type = WhiteQueen + 1;
8617 type = egbbCode[type];
8618 squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8619 pieces[cnt] = type + black*6;
8620 if(++cnt > 5) return 11;
8622 pieces[cnt] = squares[cnt] = 0;
8624 if(loaded == 2) return 13; // loading failed before
8626 char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8629 loaded = 2; // prepare for failure
8630 if(!path) return 13; // no egbb installed
8631 strncpy(buf, path + 8, MSG_SIZ);
8632 if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8633 snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8634 lib = LoadLibrary(buf);
8635 if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8636 loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8637 probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8638 if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8639 p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8640 loaded = 1; // success!
8642 res = probeBB(forwardMostMove & 1, pieces, squares);
8643 return res > 0 ? 1 : res < 0 ? -1 : 0;
8647 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8648 { // [HGM] book: this routine intercepts moves to simulate book replies
8649 char *bookHit = NULL;
8651 if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8653 snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8654 SendToProgram(buf, cps);
8656 //first determine if the incoming move brings opponent into his book
8657 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8658 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8659 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8660 if(bookHit != NULL && !cps->bookSuspend) {
8661 // make sure opponent is not going to reply after receiving move to book position
8662 SendToProgram("force\n", cps);
8663 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8665 if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8666 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8667 // now arrange restart after book miss
8669 // after a book hit we never send 'go', and the code after the call to this routine
8670 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8671 char buf[MSG_SIZ], *move = bookHit;
8673 int fromX, fromY, toX, toY;
8677 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8678 &fromX, &fromY, &toX, &toY, &promoChar)) {
8679 (void) CoordsToAlgebraic(boards[forwardMostMove],
8680 PosFlags(forwardMostMove),
8681 fromY, fromX, toY, toX, promoChar, move);
8683 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8687 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8688 SendToProgram(buf, cps);
8689 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8690 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8691 SendToProgram("go\n", cps);
8692 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8693 } else { // 'go' might be sent based on 'firstMove' after this routine returns
8694 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8695 SendToProgram("go\n", cps);
8696 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8698 return bookHit; // notify caller of hit, so it can take action to send move to opponent
8702 LoadError (char *errmess, ChessProgramState *cps)
8703 { // unloads engine and switches back to -ncp mode if it was first
8704 if(cps->initDone) return FALSE;
8705 cps->isr = NULL; // this should suppress further error popups from breaking pipes
8706 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8709 appData.noChessProgram = TRUE;
8710 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8711 gameMode = BeginningOfGame; ModeHighlight();
8714 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8715 DisplayMessage("", ""); // erase waiting message
8716 if(errmess) DisplayError(errmess, 0); // announce reason, if given
8721 ChessProgramState *savedState;
8723 DeferredBookMove (void)
8725 if(savedState->lastPing != savedState->lastPong)
8726 ScheduleDelayedEvent(DeferredBookMove, 10);
8728 HandleMachineMove(savedMessage, savedState);
8731 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8732 static ChessProgramState *stalledEngine;
8733 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8736 HandleMachineMove (char *message, ChessProgramState *cps)
8738 static char firstLeg[20], legs;
8739 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8740 char realname[MSG_SIZ];
8741 int fromX, fromY, toX, toY;
8743 char promoChar, roar;
8748 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8749 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8750 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8751 DisplayError(_("Invalid pairing from pairing engine"), 0);
8754 pairingReceived = 1;
8756 return; // Skim the pairing messages here.
8759 oldError = cps->userError; cps->userError = 0;
8761 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8763 * Kludge to ignore BEL characters
8765 while (*message == '\007') message++;
8768 * [HGM] engine debug message: ignore lines starting with '#' character
8770 if(cps->debug && *message == '#') return;
8773 * Look for book output
8775 if (cps == &first && bookRequested) {
8776 if (message[0] == '\t' || message[0] == ' ') {
8777 /* Part of the book output is here; append it */
8778 strcat(bookOutput, message);
8779 strcat(bookOutput, " \n");
8781 } else if (bookOutput[0] != NULLCHAR) {
8782 /* All of book output has arrived; display it */
8783 char *p = bookOutput;
8784 while (*p != NULLCHAR) {
8785 if (*p == '\t') *p = ' ';
8788 DisplayInformation(bookOutput);
8789 bookRequested = FALSE;
8790 /* Fall through to parse the current output */
8795 * Look for machine move.
8797 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8798 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8800 if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8801 if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8802 safeStrCpy(stashedInputMove, message, MSG_SIZ);
8803 stalledEngine = cps;
8804 if(appData.ponderNextMove) { // bring opponent out of ponder
8805 if(gameMode == TwoMachinesPlay) {
8806 if(cps->other->pause)
8807 PauseEngine(cps->other);
8809 SendToProgram("easy\n", cps->other);
8818 /* This method is only useful on engines that support ping */
8819 if(abortEngineThink) {
8820 if (appData.debugMode) {
8821 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8823 SendToProgram("undo\n", cps);
8827 if (cps->lastPing != cps->lastPong) {
8828 /* Extra move from before last new; ignore */
8829 if (appData.debugMode) {
8830 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8837 int machineWhite = FALSE;
8840 case BeginningOfGame:
8841 /* Extra move from before last reset; ignore */
8842 if (appData.debugMode) {
8843 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8850 /* Extra move after we tried to stop. The mode test is
8851 not a reliable way of detecting this problem, but it's
8852 the best we can do on engines that don't support ping.
8854 if (appData.debugMode) {
8855 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8856 cps->which, gameMode);
8858 SendToProgram("undo\n", cps);
8861 case MachinePlaysWhite:
8862 case IcsPlayingWhite:
8863 machineWhite = TRUE;
8866 case MachinePlaysBlack:
8867 case IcsPlayingBlack:
8868 machineWhite = FALSE;
8871 case TwoMachinesPlay:
8872 machineWhite = (cps->twoMachinesColor[0] == 'w');
8875 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8876 if (appData.debugMode) {
8878 "Ignoring move out of turn by %s, gameMode %d"
8879 ", forwardMost %d\n",
8880 cps->which, gameMode, forwardMostMove);
8886 if(cps->alphaRank) AlphaRank(machineMove, 4);
8888 // [HGM] lion: (some very limited) support for Alien protocol
8889 killX = killY = kill2X = kill2Y = -1;
8890 if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8891 if(legs++) return; // middle leg contains only redundant info, ignore (but count it)
8892 safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8895 if(p = strchr(machineMove, ',')) { // we got both legs in one (happens on book move)
8896 char *q = strchr(p+1, ','); // second comma?
8897 safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8898 if(q) legs = 2, p = q; else legs = 1; // with 3-leg move we clipof first two legs!
8899 safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8901 if(firstLeg[0]) { // there was a previous leg;
8902 // only support case where same piece makes two step
8903 char buf[20], *p = machineMove+1, *q = buf+1, f;
8904 safeStrCpy(buf, machineMove, 20);
8905 while(isdigit(*q)) q++; // find start of to-square
8906 safeStrCpy(machineMove, firstLeg, 20);
8907 while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8908 if(legs == 2) sscanf(p, "%c%d", &f, &kill2Y), kill2X = f - AAA, kill2Y -= ONE - '0'; // in 3-leg move 2nd kill is to-sqr of 1st leg
8909 else if(*p == *buf) // if first-leg to not equal to second-leg from first leg says unmodified (assume it is King move of castling)
8910 safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8911 sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8912 firstLeg[0] = NULLCHAR; legs = 0;
8915 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8916 &fromX, &fromY, &toX, &toY, &promoChar)) {
8917 /* Machine move could not be parsed; ignore it. */
8918 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8919 machineMove, _(cps->which));
8920 DisplayMoveError(buf1);
8921 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8922 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8923 if (gameMode == TwoMachinesPlay) {
8924 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8930 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8931 /* So we have to redo legality test with true e.p. status here, */
8932 /* to make sure an illegal e.p. capture does not slip through, */
8933 /* to cause a forfeit on a justified illegal-move complaint */
8934 /* of the opponent. */
8935 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8937 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8938 fromY, fromX, toY, toX, promoChar);
8939 if(moveType == IllegalMove) {
8940 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8941 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8942 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8945 } else if(!appData.fischerCastling)
8946 /* [HGM] Kludge to handle engines that send FRC-style castling
8947 when they shouldn't (like TSCP-Gothic) */
8949 case WhiteASideCastleFR:
8950 case BlackASideCastleFR:
8952 currentMoveString[2]++;
8954 case WhiteHSideCastleFR:
8955 case BlackHSideCastleFR:
8957 currentMoveString[2]--;
8959 default: ; // nothing to do, but suppresses warning of pedantic compilers
8962 hintRequested = FALSE;
8963 lastHint[0] = NULLCHAR;
8964 bookRequested = FALSE;
8965 /* Program may be pondering now */
8966 cps->maybeThinking = TRUE;
8967 if (cps->sendTime == 2) cps->sendTime = 1;
8968 if (cps->offeredDraw) cps->offeredDraw--;
8970 /* [AS] Save move info*/
8971 pvInfoList[ forwardMostMove ].score = programStats.score;
8972 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8973 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8975 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8977 /* Test suites abort the 'game' after one move */
8978 if(*appData.finger) {
8980 char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8981 if(!f) f = fopen(appData.finger, "w");
8982 if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8983 else { DisplayFatalError("Bad output file", errno, 0); return; }
8985 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8988 if(solvingTime >= 0) {
8989 snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
8990 totalTime += solvingTime; first.matchWins++; solvingTime = -1;
8992 snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
8993 if(solvingTime == -2) second.matchWins++;
8995 OutputKibitz(2, buf1);
8996 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8999 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
9000 if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
9003 while( count < adjudicateLossPlies ) {
9004 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
9007 score = -score; /* Flip score for winning side */
9010 if( score > appData.adjudicateLossThreshold ) {
9017 if( count >= adjudicateLossPlies ) {
9018 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9020 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
9021 "Xboard adjudication",
9028 if(Adjudicate(cps)) {
9029 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9030 return; // [HGM] adjudicate: for all automatic game ends
9034 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9036 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9037 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9039 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9041 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9043 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9044 char buf[3*MSG_SIZ];
9046 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9047 programStats.score / 100.,
9049 programStats.time / 100.,
9050 (unsigned int)programStats.nodes,
9051 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9052 programStats.movelist);
9058 /* [AS] Clear stats for next move */
9059 ClearProgramStats();
9060 thinkOutput[0] = NULLCHAR;
9061 hiddenThinkOutputState = 0;
9064 if (gameMode == TwoMachinesPlay) {
9065 /* [HGM] relaying draw offers moved to after reception of move */
9066 /* and interpreting offer as claim if it brings draw condition */
9067 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9068 SendToProgram("draw\n", cps->other);
9070 if (cps->other->sendTime) {
9071 SendTimeRemaining(cps->other,
9072 cps->other->twoMachinesColor[0] == 'w');
9074 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9075 if (firstMove && !bookHit) {
9077 if (cps->other->useColors) {
9078 SendToProgram(cps->other->twoMachinesColor, cps->other);
9080 SendToProgram("go\n", cps->other);
9082 cps->other->maybeThinking = TRUE;
9085 roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9087 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9089 if (!pausing && appData.ringBellAfterMoves) {
9090 if(!roar) RingBell();
9094 * Reenable menu items that were disabled while
9095 * machine was thinking
9097 if (gameMode != TwoMachinesPlay)
9098 SetUserThinkingEnables();
9100 // [HGM] book: after book hit opponent has received move and is now in force mode
9101 // force the book reply into it, and then fake that it outputted this move by jumping
9102 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9104 static char bookMove[MSG_SIZ]; // a bit generous?
9106 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9107 strcat(bookMove, bookHit);
9110 programStats.nodes = programStats.depth = programStats.time =
9111 programStats.score = programStats.got_only_move = 0;
9112 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9114 if(cps->lastPing != cps->lastPong) {
9115 savedMessage = message; // args for deferred call
9117 ScheduleDelayedEvent(DeferredBookMove, 10);
9126 /* Set special modes for chess engines. Later something general
9127 * could be added here; for now there is just one kludge feature,
9128 * needed because Crafty 15.10 and earlier don't ignore SIGINT
9129 * when "xboard" is given as an interactive command.
9131 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9132 cps->useSigint = FALSE;
9133 cps->useSigterm = FALSE;
9135 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9136 ParseFeatures(message+8, cps);
9137 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9140 if (!strncmp(message, "setup ", 6) &&
9141 (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9142 NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9143 ) { // [HGM] allow first engine to define opening position
9144 int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9145 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9147 if(sscanf(message, "setup (%s", buf) == 1) {
9148 s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9149 ASSIGN(appData.pieceToCharTable, buf);
9151 dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9153 while(message[s] && message[s++] != ' ');
9154 if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9155 dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9156 if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
9157 appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9158 if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant
9159 InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9160 if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9161 startedFromSetupPosition = FALSE;
9164 if(startedFromSetupPosition) return;
9165 ParseFEN(boards[0], &dummy, message+s, FALSE);
9166 DrawPosition(TRUE, boards[0]);
9167 CopyBoard(initialPosition, boards[0]);
9168 startedFromSetupPosition = TRUE;
9171 if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9172 ChessSquare piece = WhitePawn;
9173 char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0;
9174 if(*p == '+') promoted++, ID = *++p;
9175 if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9176 piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece);
9177 if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9178 /* always accept definition of */ && piece != WhiteFalcon && piece != BlackFalcon
9179 /* wild-card pieces. */ && piece != WhiteCobra && piece != BlackCobra
9180 /* For variants we don't have */ && gameInfo.variant != VariantBerolina
9181 /* correct rules for, we cannot */ && gameInfo.variant != VariantCylinder
9182 /* enforce legality on our own! */ && gameInfo.variant != VariantUnknown
9183 && gameInfo.variant != VariantGreat
9184 && gameInfo.variant != VariantFairy ) return;
9185 if(piece < EmptySquare) {
9187 ASSIGN(pieceDesc[piece], buf1);
9188 if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9192 if(!appData.testLegality && sscanf(message, "choice %s", promoRestrict) == 1) {
9194 LeftClick(Press, 0, 0); // finish the click that was interrupted
9195 } else if(promoSweep != EmptySquare) {
9196 promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9197 if(strlen(promoRestrict) > 1) Sweep(0);
9201 /* [HGM] Allow engine to set up a position. Don't ask me why one would
9202 * want this, I was asked to put it in, and obliged.
9204 if (!strncmp(message, "setboard ", 9)) {
9205 Board initial_position;
9207 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9209 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9210 DisplayError(_("Bad FEN received from engine"), 0);
9214 CopyBoard(boards[0], initial_position);
9215 initialRulePlies = FENrulePlies;
9216 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9217 else gameMode = MachinePlaysBlack;
9218 DrawPosition(FALSE, boards[currentMove]);
9224 * Look for communication commands
9226 if (!strncmp(message, "telluser ", 9)) {
9227 if(message[9] == '\\' && message[10] == '\\')
9228 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9230 DisplayNote(message + 9);
9233 if (!strncmp(message, "tellusererror ", 14)) {
9235 if(message[14] == '\\' && message[15] == '\\')
9236 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9238 DisplayError(message + 14, 0);
9241 if (!strncmp(message, "tellopponent ", 13)) {
9242 if (appData.icsActive) {
9244 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9248 DisplayNote(message + 13);
9252 if (!strncmp(message, "tellothers ", 11)) {
9253 if (appData.icsActive) {
9255 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9258 } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9261 if (!strncmp(message, "tellall ", 8)) {
9262 if (appData.icsActive) {
9264 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9268 DisplayNote(message + 8);
9272 if (strncmp(message, "warning", 7) == 0) {
9273 /* Undocumented feature, use tellusererror in new code */
9274 DisplayError(message, 0);
9277 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9278 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9279 strcat(realname, " query");
9280 AskQuestion(realname, buf2, buf1, cps->pr);
9283 /* Commands from the engine directly to ICS. We don't allow these to be
9284 * sent until we are logged on. Crafty kibitzes have been known to
9285 * interfere with the login process.
9288 if (!strncmp(message, "tellics ", 8)) {
9289 SendToICS(message + 8);
9293 if (!strncmp(message, "tellicsnoalias ", 15)) {
9294 SendToICS(ics_prefix);
9295 SendToICS(message + 15);
9299 /* The following are for backward compatibility only */
9300 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9301 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9302 SendToICS(ics_prefix);
9308 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9309 if(initPing == cps->lastPong) {
9310 if(gameInfo.variant == VariantUnknown) {
9311 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9312 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9313 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9317 if(cps->lastPing == cps->lastPong && abortEngineThink) {
9318 abortEngineThink = FALSE;
9319 DisplayMessage("", "");
9324 if(!strncmp(message, "highlight ", 10)) {
9325 if(appData.testLegality && !*engineVariant && appData.markers) return;
9326 MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9329 if(!strncmp(message, "click ", 6)) {
9330 char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9331 if(appData.testLegality || !appData.oneClick) return;
9332 sscanf(message+6, "%c%d%c", &f, &y, &c);
9333 x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9334 if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9335 x = x*squareSize + (x+1)*lineGap + squareSize/2;
9336 y = y*squareSize + (y+1)*lineGap + squareSize/2;
9337 f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9338 if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9339 LeftClick(Release, lastLeftX, lastLeftY);
9340 controlKey = (c == ',');
9341 LeftClick(Press, x, y);
9342 LeftClick(Release, x, y);
9343 first.highlight = f;
9347 * If the move is illegal, cancel it and redraw the board.
9348 * Also deal with other error cases. Matching is rather loose
9349 * here to accommodate engines written before the spec.
9351 if (strncmp(message + 1, "llegal move", 11) == 0 ||
9352 strncmp(message, "Error", 5) == 0) {
9353 if (StrStr(message, "name") ||
9354 StrStr(message, "rating") || StrStr(message, "?") ||
9355 StrStr(message, "result") || StrStr(message, "board") ||
9356 StrStr(message, "bk") || StrStr(message, "computer") ||
9357 StrStr(message, "variant") || StrStr(message, "hint") ||
9358 StrStr(message, "random") || StrStr(message, "depth") ||
9359 StrStr(message, "accepted")) {
9362 if (StrStr(message, "protover")) {
9363 /* Program is responding to input, so it's apparently done
9364 initializing, and this error message indicates it is
9365 protocol version 1. So we don't need to wait any longer
9366 for it to initialize and send feature commands. */
9367 FeatureDone(cps, 1);
9368 cps->protocolVersion = 1;
9371 cps->maybeThinking = FALSE;
9373 if (StrStr(message, "draw")) {
9374 /* Program doesn't have "draw" command */
9375 cps->sendDrawOffers = 0;
9378 if (cps->sendTime != 1 &&
9379 (StrStr(message, "time") || StrStr(message, "otim"))) {
9380 /* Program apparently doesn't have "time" or "otim" command */
9384 if (StrStr(message, "analyze")) {
9385 cps->analysisSupport = FALSE;
9386 cps->analyzing = FALSE;
9387 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9388 EditGameEvent(); // [HGM] try to preserve loaded game
9389 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9390 DisplayError(buf2, 0);
9393 if (StrStr(message, "(no matching move)st")) {
9394 /* Special kludge for GNU Chess 4 only */
9395 cps->stKludge = TRUE;
9396 SendTimeControl(cps, movesPerSession, timeControl,
9397 timeIncrement, appData.searchDepth,
9401 if (StrStr(message, "(no matching move)sd")) {
9402 /* Special kludge for GNU Chess 4 only */
9403 cps->sdKludge = TRUE;
9404 SendTimeControl(cps, movesPerSession, timeControl,
9405 timeIncrement, appData.searchDepth,
9409 if (!StrStr(message, "llegal")) {
9412 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9413 gameMode == IcsIdle) return;
9414 if (forwardMostMove <= backwardMostMove) return;
9415 if (pausing) PauseEvent();
9416 if(appData.forceIllegal) {
9417 // [HGM] illegal: machine refused move; force position after move into it
9418 SendToProgram("force\n", cps);
9419 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9420 // we have a real problem now, as SendBoard will use the a2a3 kludge
9421 // when black is to move, while there might be nothing on a2 or black
9422 // might already have the move. So send the board as if white has the move.
9423 // But first we must change the stm of the engine, as it refused the last move
9424 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9425 if(WhiteOnMove(forwardMostMove)) {
9426 SendToProgram("a7a6\n", cps); // for the engine black still had the move
9427 SendBoard(cps, forwardMostMove); // kludgeless board
9429 SendToProgram("a2a3\n", cps); // for the engine white still had the move
9430 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9431 SendBoard(cps, forwardMostMove+1); // kludgeless board
9433 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9434 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9435 gameMode == TwoMachinesPlay)
9436 SendToProgram("go\n", cps);
9439 if (gameMode == PlayFromGameFile) {
9440 /* Stop reading this game file */
9441 gameMode = EditGame;
9444 /* [HGM] illegal-move claim should forfeit game when Xboard */
9445 /* only passes fully legal moves */
9446 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9447 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9448 "False illegal-move claim", GE_XBOARD );
9449 return; // do not take back move we tested as valid
9451 currentMove = forwardMostMove-1;
9452 DisplayMove(currentMove-1); /* before DisplayMoveError */
9453 SwitchClocks(forwardMostMove-1); // [HGM] race
9454 DisplayBothClocks();
9455 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9456 parseList[currentMove], _(cps->which));
9457 DisplayMoveError(buf1);
9458 DrawPosition(FALSE, boards[currentMove]);
9460 SetUserThinkingEnables();
9463 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9464 /* Program has a broken "time" command that
9465 outputs a string not ending in newline.
9469 if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9470 if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9471 sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1 ) return;
9475 * If chess program startup fails, exit with an error message.
9476 * Attempts to recover here are futile. [HGM] Well, we try anyway
9478 if ((StrStr(message, "unknown host") != NULL)
9479 || (StrStr(message, "No remote directory") != NULL)
9480 || (StrStr(message, "not found") != NULL)
9481 || (StrStr(message, "No such file") != NULL)
9482 || (StrStr(message, "can't alloc") != NULL)
9483 || (StrStr(message, "Permission denied") != NULL)) {
9485 cps->maybeThinking = FALSE;
9486 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9487 _(cps->which), cps->program, cps->host, message);
9488 RemoveInputSource(cps->isr);
9489 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9490 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9491 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9497 * Look for hint output
9499 if (sscanf(message, "Hint: %s", buf1) == 1) {
9500 if (cps == &first && hintRequested) {
9501 hintRequested = FALSE;
9502 if (ParseOneMove(buf1, forwardMostMove, &moveType,
9503 &fromX, &fromY, &toX, &toY, &promoChar)) {
9504 (void) CoordsToAlgebraic(boards[forwardMostMove],
9505 PosFlags(forwardMostMove),
9506 fromY, fromX, toY, toX, promoChar, buf1);
9507 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9508 DisplayInformation(buf2);
9510 /* Hint move could not be parsed!? */
9511 snprintf(buf2, sizeof(buf2),
9512 _("Illegal hint move \"%s\"\nfrom %s chess program"),
9513 buf1, _(cps->which));
9514 DisplayError(buf2, 0);
9517 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9523 * Ignore other messages if game is not in progress
9525 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9526 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9529 * look for win, lose, draw, or draw offer
9531 if (strncmp(message, "1-0", 3) == 0) {
9532 char *p, *q, *r = "";
9533 p = strchr(message, '{');
9541 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9543 } else if (strncmp(message, "0-1", 3) == 0) {
9544 char *p, *q, *r = "";
9545 p = strchr(message, '{');
9553 /* Kludge for Arasan 4.1 bug */
9554 if (strcmp(r, "Black resigns") == 0) {
9555 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9558 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9560 } else if (strncmp(message, "1/2", 3) == 0) {
9561 char *p, *q, *r = "";
9562 p = strchr(message, '{');
9571 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9574 } else if (strncmp(message, "White resign", 12) == 0) {
9575 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9577 } else if (strncmp(message, "Black resign", 12) == 0) {
9578 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9580 } else if (strncmp(message, "White matches", 13) == 0 ||
9581 strncmp(message, "Black matches", 13) == 0 ) {
9582 /* [HGM] ignore GNUShogi noises */
9584 } else if (strncmp(message, "White", 5) == 0 &&
9585 message[5] != '(' &&
9586 StrStr(message, "Black") == NULL) {
9587 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9589 } else if (strncmp(message, "Black", 5) == 0 &&
9590 message[5] != '(') {
9591 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9593 } else if (strcmp(message, "resign") == 0 ||
9594 strcmp(message, "computer resigns") == 0) {
9596 case MachinePlaysBlack:
9597 case IcsPlayingBlack:
9598 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9600 case MachinePlaysWhite:
9601 case IcsPlayingWhite:
9602 GameEnds(BlackWins, "White resigns", GE_ENGINE);
9604 case TwoMachinesPlay:
9605 if (cps->twoMachinesColor[0] == 'w')
9606 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9608 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9615 } else if (strncmp(message, "opponent mates", 14) == 0) {
9617 case MachinePlaysBlack:
9618 case IcsPlayingBlack:
9619 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9621 case MachinePlaysWhite:
9622 case IcsPlayingWhite:
9623 GameEnds(BlackWins, "Black mates", GE_ENGINE);
9625 case TwoMachinesPlay:
9626 if (cps->twoMachinesColor[0] == 'w')
9627 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9629 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9636 } else if (strncmp(message, "computer mates", 14) == 0) {
9638 case MachinePlaysBlack:
9639 case IcsPlayingBlack:
9640 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9642 case MachinePlaysWhite:
9643 case IcsPlayingWhite:
9644 GameEnds(WhiteWins, "White mates", GE_ENGINE);
9646 case TwoMachinesPlay:
9647 if (cps->twoMachinesColor[0] == 'w')
9648 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9650 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9657 } else if (strncmp(message, "checkmate", 9) == 0) {
9658 if (WhiteOnMove(forwardMostMove)) {
9659 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9661 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9664 } else if (strstr(message, "Draw") != NULL ||
9665 strstr(message, "game is a draw") != NULL) {
9666 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9668 } else if (strstr(message, "offer") != NULL &&
9669 strstr(message, "draw") != NULL) {
9671 if (appData.zippyPlay && first.initDone) {
9672 /* Relay offer to ICS */
9673 SendToICS(ics_prefix);
9674 SendToICS("draw\n");
9677 cps->offeredDraw = 2; /* valid until this engine moves twice */
9678 if (gameMode == TwoMachinesPlay) {
9679 if (cps->other->offeredDraw) {
9680 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9681 /* [HGM] in two-machine mode we delay relaying draw offer */
9682 /* until after we also have move, to see if it is really claim */
9684 } else if (gameMode == MachinePlaysWhite ||
9685 gameMode == MachinePlaysBlack) {
9686 if (userOfferedDraw) {
9687 DisplayInformation(_("Machine accepts your draw offer"));
9688 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9690 DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9697 * Look for thinking output
9699 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9700 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9702 int plylev, mvleft, mvtot, curscore, time;
9703 char mvname[MOVE_LEN];
9707 int prefixHint = FALSE;
9708 mvname[0] = NULLCHAR;
9711 case MachinePlaysBlack:
9712 case IcsPlayingBlack:
9713 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9715 case MachinePlaysWhite:
9716 case IcsPlayingWhite:
9717 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9722 case IcsObserving: /* [DM] icsEngineAnalyze */
9723 if (!appData.icsEngineAnalyze) ignore = TRUE;
9725 case TwoMachinesPlay:
9726 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9736 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9739 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9740 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9741 char score_buf[MSG_SIZ];
9743 if(nodes>>32 == u64Const(0xFFFFFFFF)) // [HGM] negative node count read
9744 nodes += u64Const(0x100000000);
9746 if (plyext != ' ' && plyext != '\t') {
9750 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9751 if( cps->scoreIsAbsolute &&
9752 ( gameMode == MachinePlaysBlack ||
9753 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9754 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
9755 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9756 !WhiteOnMove(currentMove)
9759 curscore = -curscore;
9762 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9764 if(*bestMove) { // rememer time best EPD move was first found
9765 int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9766 ChessMove mt; char *p = bestMove;
9767 int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9769 while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9770 if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9771 solvingTime = (solvingTime < 0 ? time : solvingTime);
9775 while(*p && *p != ' ') p++;
9776 while(*p == ' ') p++;
9778 if(!solved) solvingTime = -1;
9780 if(*avoidMove && !solved) {
9781 int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9782 ChessMove mt; char *p = avoidMove, solved = 1;
9783 int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9784 while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9785 if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9786 solved = 0; solvingTime = -2;
9789 while(*p && *p != ' ') p++;
9790 while(*p == ' ') p++;
9792 if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9795 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9798 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9799 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9800 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9801 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9802 if(f = fopen(buf, "w")) { // export PV to applicable PV file
9803 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9807 /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9808 DisplayError(_("failed writing PV"), 0);
9811 tempStats.depth = plylev;
9812 tempStats.nodes = nodes;
9813 tempStats.time = time;
9814 tempStats.score = curscore;
9815 tempStats.got_only_move = 0;
9817 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9820 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
9821 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9822 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9823 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9824 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9825 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9826 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9827 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9830 /* Buffer overflow protection */
9831 if (pv[0] != NULLCHAR) {
9832 if (strlen(pv) >= sizeof(tempStats.movelist)
9833 && appData.debugMode) {
9835 "PV is too long; using the first %u bytes.\n",
9836 (unsigned) sizeof(tempStats.movelist) - 1);
9839 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9841 sprintf(tempStats.movelist, " no PV\n");
9844 if (tempStats.seen_stat) {
9845 tempStats.ok_to_send = 1;
9848 if (strchr(tempStats.movelist, '(') != NULL) {
9849 tempStats.line_is_book = 1;
9850 tempStats.nr_moves = 0;
9851 tempStats.moves_left = 0;
9853 tempStats.line_is_book = 0;
9856 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9857 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9859 SendProgramStatsToFrontend( cps, &tempStats );
9862 [AS] Protect the thinkOutput buffer from overflow... this
9863 is only useful if buf1 hasn't overflowed first!
9865 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9866 if(curscore >= MATE_SCORE)
9867 snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9868 else if(curscore <= -MATE_SCORE)
9869 snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9871 snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9872 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9874 (gameMode == TwoMachinesPlay ?
9875 ToUpper(cps->twoMachinesColor[0]) : ' '),
9877 prefixHint ? lastHint : "",
9878 prefixHint ? " " : "" );
9880 if( buf1[0] != NULLCHAR ) {
9881 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9883 if( strlen(pv) > max_len ) {
9884 if( appData.debugMode) {
9885 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9887 pv[max_len+1] = '\0';
9890 strcat( thinkOutput, pv);
9893 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9894 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9895 DisplayMove(currentMove - 1);
9899 } else if ((p=StrStr(message, "(only move)")) != NULL) {
9900 /* crafty (9.25+) says "(only move) <move>"
9901 * if there is only 1 legal move
9903 sscanf(p, "(only move) %s", buf1);
9904 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9905 sprintf(programStats.movelist, "%s (only move)", buf1);
9906 programStats.depth = 1;
9907 programStats.nr_moves = 1;
9908 programStats.moves_left = 1;
9909 programStats.nodes = 1;
9910 programStats.time = 1;
9911 programStats.got_only_move = 1;
9913 /* Not really, but we also use this member to
9914 mean "line isn't going to change" (Crafty
9915 isn't searching, so stats won't change) */
9916 programStats.line_is_book = 1;
9918 SendProgramStatsToFrontend( cps, &programStats );
9920 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9921 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9922 DisplayMove(currentMove - 1);
9925 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9926 &time, &nodes, &plylev, &mvleft,
9927 &mvtot, mvname) >= 5) {
9928 /* The stat01: line is from Crafty (9.29+) in response
9929 to the "." command */
9930 programStats.seen_stat = 1;
9931 cps->maybeThinking = TRUE;
9933 if (programStats.got_only_move || !appData.periodicUpdates)
9936 programStats.depth = plylev;
9937 programStats.time = time;
9938 programStats.nodes = nodes;
9939 programStats.moves_left = mvleft;
9940 programStats.nr_moves = mvtot;
9941 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9942 programStats.ok_to_send = 1;
9943 programStats.movelist[0] = '\0';
9945 SendProgramStatsToFrontend( cps, &programStats );
9949 } else if (strncmp(message,"++",2) == 0) {
9950 /* Crafty 9.29+ outputs this */
9951 programStats.got_fail = 2;
9954 } else if (strncmp(message,"--",2) == 0) {
9955 /* Crafty 9.29+ outputs this */
9956 programStats.got_fail = 1;
9959 } else if (thinkOutput[0] != NULLCHAR &&
9960 strncmp(message, " ", 4) == 0) {
9961 unsigned message_len;
9964 while (*p && *p == ' ') p++;
9966 message_len = strlen( p );
9968 /* [AS] Avoid buffer overflow */
9969 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9970 strcat(thinkOutput, " ");
9971 strcat(thinkOutput, p);
9974 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9975 strcat(programStats.movelist, " ");
9976 strcat(programStats.movelist, p);
9979 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9980 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9981 DisplayMove(currentMove - 1);
9989 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9990 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9992 ChessProgramStats cpstats;
9994 if (plyext != ' ' && plyext != '\t') {
9998 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9999 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
10000 curscore = -curscore;
10003 cpstats.depth = plylev;
10004 cpstats.nodes = nodes;
10005 cpstats.time = time;
10006 cpstats.score = curscore;
10007 cpstats.got_only_move = 0;
10008 cpstats.movelist[0] = '\0';
10010 if (buf1[0] != NULLCHAR) {
10011 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
10014 cpstats.ok_to_send = 0;
10015 cpstats.line_is_book = 0;
10016 cpstats.nr_moves = 0;
10017 cpstats.moves_left = 0;
10019 SendProgramStatsToFrontend( cps, &cpstats );
10026 /* Parse a game score from the character string "game", and
10027 record it as the history of the current game. The game
10028 score is NOT assumed to start from the standard position.
10029 The display is not updated in any way.
10032 ParseGameHistory (char *game)
10034 ChessMove moveType;
10035 int fromX, fromY, toX, toY, boardIndex;
10040 if (appData.debugMode)
10041 fprintf(debugFP, "Parsing game history: %s\n", game);
10043 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10044 gameInfo.site = StrSave(appData.icsHost);
10045 gameInfo.date = PGNDate();
10046 gameInfo.round = StrSave("-");
10048 /* Parse out names of players */
10049 while (*game == ' ') game++;
10051 while (*game != ' ') *p++ = *game++;
10053 gameInfo.white = StrSave(buf);
10054 while (*game == ' ') game++;
10056 while (*game != ' ' && *game != '\n') *p++ = *game++;
10058 gameInfo.black = StrSave(buf);
10061 boardIndex = blackPlaysFirst ? 1 : 0;
10064 yyboardindex = boardIndex;
10065 moveType = (ChessMove) Myylex();
10066 switch (moveType) {
10067 case IllegalMove: /* maybe suicide chess, etc. */
10068 if (appData.debugMode) {
10069 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10070 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10071 setbuf(debugFP, NULL);
10073 case WhitePromotion:
10074 case BlackPromotion:
10075 case WhiteNonPromotion:
10076 case BlackNonPromotion:
10079 case WhiteCapturesEnPassant:
10080 case BlackCapturesEnPassant:
10081 case WhiteKingSideCastle:
10082 case WhiteQueenSideCastle:
10083 case BlackKingSideCastle:
10084 case BlackQueenSideCastle:
10085 case WhiteKingSideCastleWild:
10086 case WhiteQueenSideCastleWild:
10087 case BlackKingSideCastleWild:
10088 case BlackQueenSideCastleWild:
10090 case WhiteHSideCastleFR:
10091 case WhiteASideCastleFR:
10092 case BlackHSideCastleFR:
10093 case BlackASideCastleFR:
10095 fromX = currentMoveString[0] - AAA;
10096 fromY = currentMoveString[1] - ONE;
10097 toX = currentMoveString[2] - AAA;
10098 toY = currentMoveString[3] - ONE;
10099 promoChar = currentMoveString[4];
10103 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10104 fromX = moveType == WhiteDrop ?
10105 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10106 (int) CharToPiece(ToLower(currentMoveString[0]));
10108 toX = currentMoveString[2] - AAA;
10109 toY = currentMoveString[3] - ONE;
10110 promoChar = NULLCHAR;
10112 case AmbiguousMove:
10114 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10115 if (appData.debugMode) {
10116 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10117 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10118 setbuf(debugFP, NULL);
10120 DisplayError(buf, 0);
10122 case ImpossibleMove:
10124 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10125 if (appData.debugMode) {
10126 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10127 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10128 setbuf(debugFP, NULL);
10130 DisplayError(buf, 0);
10133 if (boardIndex < backwardMostMove) {
10134 /* Oops, gap. How did that happen? */
10135 DisplayError(_("Gap in move list"), 0);
10138 backwardMostMove = blackPlaysFirst ? 1 : 0;
10139 if (boardIndex > forwardMostMove) {
10140 forwardMostMove = boardIndex;
10144 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10145 strcat(parseList[boardIndex-1], " ");
10146 strcat(parseList[boardIndex-1], yy_text);
10158 case GameUnfinished:
10159 if (gameMode == IcsExamining) {
10160 if (boardIndex < backwardMostMove) {
10161 /* Oops, gap. How did that happen? */
10164 backwardMostMove = blackPlaysFirst ? 1 : 0;
10167 gameInfo.result = moveType;
10168 p = strchr(yy_text, '{');
10169 if (p == NULL) p = strchr(yy_text, '(');
10172 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10174 q = strchr(p, *p == '{' ? '}' : ')');
10175 if (q != NULL) *q = NULLCHAR;
10178 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10179 gameInfo.resultDetails = StrSave(p);
10182 if (boardIndex >= forwardMostMove &&
10183 !(gameMode == IcsObserving && ics_gamenum == -1)) {
10184 backwardMostMove = blackPlaysFirst ? 1 : 0;
10187 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10188 fromY, fromX, toY, toX, promoChar,
10189 parseList[boardIndex]);
10190 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10191 /* currentMoveString is set as a side-effect of yylex */
10192 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10193 strcat(moveList[boardIndex], "\n");
10195 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10196 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10202 if(!IS_SHOGI(gameInfo.variant))
10203 strcat(parseList[boardIndex - 1], "+");
10207 strcat(parseList[boardIndex - 1], "#");
10214 /* Apply a move to the given board */
10216 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10218 ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, epFile, lastFile, lastRank, berolina = 0;
10219 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10221 /* [HGM] compute & store e.p. status and castling rights for new position */
10222 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10224 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10225 oldEP = (signed char)board[EP_STATUS]; epRank = board[EP_RANK]; epFile = board[EP_FILE]; lastFile = board[LAST_FILE],lastRank = board[LAST_RANK];
10226 board[EP_STATUS] = EP_NONE;
10227 board[EP_FILE] = board[EP_RANK] = board[LAST_FILE] = board[LAST_RANK] = 100;
10229 if (fromY == DROP_RANK) {
10230 /* must be first */
10231 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10232 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10235 piece = board[toY][toX] = (ChessSquare) fromX;
10237 // ChessSquare victim;
10240 if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10241 // victim = board[killY][killX],
10242 killed = board[killY][killX],
10243 board[killY][killX] = EmptySquare,
10244 board[EP_STATUS] = EP_CAPTURE;
10245 if( kill2X >= 0 && kill2Y >= 0)
10246 killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10249 if( board[toY][toX] != EmptySquare ) {
10250 board[EP_STATUS] = EP_CAPTURE;
10251 if( (fromX != toX || fromY != toY) && // not igui!
10252 (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10253 captured == BlackLion && board[fromY][fromX] != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules
10254 board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10258 pawn = board[fromY][fromX];
10259 if(pieceDesc[pawn] && strchr(pieceDesc[pawn], 'e')) { // piece with user-defined e.p. capture
10260 if(captured == EmptySquare && toX == epFile && (toY == (epRank & 127) || toY + (pawn < BlackPawn ? -1 : 1) == epRank - 128)) {
10261 captured = board[lastRank][lastFile]; // remove victim
10262 board[lastRank][lastFile] = EmptySquare;
10263 pawn = EmptySquare; // kludge to suppress old e.p. code
10266 if( pawn == WhiteLance || pawn == BlackLance ) {
10267 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10268 if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10269 else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10272 if( pawn == WhitePawn ) {
10273 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10274 board[EP_STATUS] = EP_PAWN_MOVE;
10275 if( toY-fromY>=2) {
10276 board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10277 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
10278 gameInfo.variant != VariantBerolina || toX < fromX)
10279 board[EP_STATUS] = toX | berolina;
10280 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10281 gameInfo.variant != VariantBerolina || toX > fromX)
10282 board[EP_STATUS] = toX;
10283 board[LAST_FILE] = toX; board[LAST_RANK] = toY;
10286 if( pawn == BlackPawn ) {
10287 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10288 board[EP_STATUS] = EP_PAWN_MOVE;
10289 if( toY-fromY<= -2) {
10290 board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10291 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
10292 gameInfo.variant != VariantBerolina || toX < fromX)
10293 board[EP_STATUS] = toX | berolina;
10294 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10295 gameInfo.variant != VariantBerolina || toX > fromX)
10296 board[EP_STATUS] = toX;
10297 board[LAST_FILE] = toX; board[LAST_RANK] = toY;
10301 if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10302 if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10303 if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10304 if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10306 for(i=0; i<nrCastlingRights; i++) {
10307 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10308 board[CASTLING][i] == toX && castlingRank[i] == toY
10309 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10312 if(gameInfo.variant == VariantSChess) { // update virginity
10313 if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10314 if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10315 if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture
10316 if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B;
10319 if (fromX == toX && fromY == toY && killX < 0) return;
10321 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10322 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10323 if(gameInfo.variant == VariantKnightmate)
10324 king += (int) WhiteUnicorn - (int) WhiteKing;
10326 if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10327 && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) { // and tramples own
10328 board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10329 board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10330 board[EP_STATUS] = EP_NONE; // capture was fake!
10332 if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10333 board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10334 board[toY][toX] = piece;
10335 board[EP_STATUS] = EP_NONE; // capture was fake!
10337 /* Code added by Tord: */
10338 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10339 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10340 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10341 board[EP_STATUS] = EP_NONE; // capture was fake!
10342 board[fromY][fromX] = EmptySquare;
10343 board[toY][toX] = EmptySquare;
10344 if((toX > fromX) != (piece == WhiteRook)) {
10345 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10347 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10349 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10350 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10351 board[EP_STATUS] = EP_NONE;
10352 board[fromY][fromX] = EmptySquare;
10353 board[toY][toX] = EmptySquare;
10354 if((toX > fromX) != (piece == BlackRook)) {
10355 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10357 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10359 /* End of code added by Tord */
10361 } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10362 board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10363 board[toY][toX] = piece;
10364 } else if (board[fromY][fromX] == king
10365 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10366 && toY == fromY && toX > fromX+1) {
10367 for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10368 ; // castle with nearest piece
10369 board[fromY][toX-1] = board[fromY][rookX];
10370 board[fromY][rookX] = EmptySquare;
10371 board[fromY][fromX] = EmptySquare;
10372 board[toY][toX] = king;
10373 } else if (board[fromY][fromX] == king
10374 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10375 && toY == fromY && toX < fromX-1) {
10376 for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10377 ; // castle with nearest piece
10378 board[fromY][toX+1] = board[fromY][rookX];
10379 board[fromY][rookX] = EmptySquare;
10380 board[fromY][fromX] = EmptySquare;
10381 board[toY][toX] = king;
10382 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10383 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10384 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10386 /* white pawn promotion */
10387 board[toY][toX] = CharToPiece(ToUpper(promoChar));
10388 if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10389 board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10390 board[fromY][fromX] = EmptySquare;
10391 } else if ((fromY >= BOARD_HEIGHT>>1)
10392 && (epFile == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10394 && gameInfo.variant != VariantXiangqi
10395 && gameInfo.variant != VariantBerolina
10396 && (pawn == WhitePawn)
10397 && (board[toY][toX] == EmptySquare)) {
10398 if(lastFile == 100) lastFile = (board[fromY][toX] == BlackPawn ? toX : fromX), lastRank = fromY; // assume FIDE e.p. if victim present
10399 board[fromY][fromX] = EmptySquare;
10400 board[toY][toX] = piece;
10401 captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10402 } else if ((fromY == BOARD_HEIGHT-4)
10404 && gameInfo.variant == VariantBerolina
10405 && (board[fromY][fromX] == WhitePawn)
10406 && (board[toY][toX] == EmptySquare)) {
10407 board[fromY][fromX] = EmptySquare;
10408 board[toY][toX] = WhitePawn;
10409 if(oldEP & EP_BEROLIN_A) {
10410 captured = board[fromY][fromX-1];
10411 board[fromY][fromX-1] = EmptySquare;
10412 }else{ captured = board[fromY][fromX+1];
10413 board[fromY][fromX+1] = EmptySquare;
10415 } else if (board[fromY][fromX] == king
10416 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10417 && toY == fromY && toX > fromX+1) {
10418 for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10420 board[fromY][toX-1] = board[fromY][rookX];
10421 board[fromY][rookX] = EmptySquare;
10422 board[fromY][fromX] = EmptySquare;
10423 board[toY][toX] = king;
10424 } else if (board[fromY][fromX] == king
10425 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10426 && toY == fromY && toX < fromX-1) {
10427 for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10429 board[fromY][toX+1] = board[fromY][rookX];
10430 board[fromY][rookX] = EmptySquare;
10431 board[fromY][fromX] = EmptySquare;
10432 board[toY][toX] = king;
10433 } else if (fromY == 7 && fromX == 3
10434 && board[fromY][fromX] == BlackKing
10435 && toY == 7 && toX == 5) {
10436 board[fromY][fromX] = EmptySquare;
10437 board[toY][toX] = BlackKing;
10438 board[fromY][7] = EmptySquare;
10439 board[toY][4] = BlackRook;
10440 } else if (fromY == 7 && fromX == 3
10441 && board[fromY][fromX] == BlackKing
10442 && toY == 7 && toX == 1) {
10443 board[fromY][fromX] = EmptySquare;
10444 board[toY][toX] = BlackKing;
10445 board[fromY][0] = EmptySquare;
10446 board[toY][2] = BlackRook;
10447 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10448 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10449 && toY < promoRank && promoChar
10451 /* black pawn promotion */
10452 board[toY][toX] = CharToPiece(ToLower(promoChar));
10453 if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10454 board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10455 board[fromY][fromX] = EmptySquare;
10456 } else if ((fromY < BOARD_HEIGHT>>1)
10457 && (epFile == toX && epRank == toY || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10459 && gameInfo.variant != VariantXiangqi
10460 && gameInfo.variant != VariantBerolina
10461 && (pawn == BlackPawn)
10462 && (board[toY][toX] == EmptySquare)) {
10463 if(lastFile == 100) lastFile = (board[fromY][toX] == WhitePawn ? toX : fromX), lastRank = fromY;
10464 board[fromY][fromX] = EmptySquare;
10465 board[toY][toX] = piece;
10466 captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10467 } else if ((fromY == 3)
10469 && gameInfo.variant == VariantBerolina
10470 && (board[fromY][fromX] == BlackPawn)
10471 && (board[toY][toX] == EmptySquare)) {
10472 board[fromY][fromX] = EmptySquare;
10473 board[toY][toX] = BlackPawn;
10474 if(oldEP & EP_BEROLIN_A) {
10475 captured = board[fromY][fromX-1];
10476 board[fromY][fromX-1] = EmptySquare;
10477 }else{ captured = board[fromY][fromX+1];
10478 board[fromY][fromX+1] = EmptySquare;
10481 ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10482 board[fromY][fromX] = EmptySquare;
10483 board[toY][toX] = piece;
10487 if (gameInfo.holdingsWidth != 0) {
10489 /* !!A lot more code needs to be written to support holdings */
10490 /* [HGM] OK, so I have written it. Holdings are stored in the */
10491 /* penultimate board files, so they are automaticlly stored */
10492 /* in the game history. */
10493 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10494 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10495 /* Delete from holdings, by decreasing count */
10496 /* and erasing image if necessary */
10497 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10498 if(p < (int) BlackPawn) { /* white drop */
10499 p -= (int)WhitePawn;
10500 p = PieceToNumber((ChessSquare)p);
10501 if(p >= gameInfo.holdingsSize) p = 0;
10502 if(--board[p][BOARD_WIDTH-2] <= 0)
10503 board[p][BOARD_WIDTH-1] = EmptySquare;
10504 if((int)board[p][BOARD_WIDTH-2] < 0)
10505 board[p][BOARD_WIDTH-2] = 0;
10506 } else { /* black drop */
10507 p -= (int)BlackPawn;
10508 p = PieceToNumber((ChessSquare)p);
10509 if(p >= gameInfo.holdingsSize) p = 0;
10510 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10511 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10512 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10513 board[BOARD_HEIGHT-1-p][1] = 0;
10516 if (captured != EmptySquare && gameInfo.holdingsSize > 0
10517 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
10518 /* [HGM] holdings: Add to holdings, if holdings exist */
10519 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10520 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10521 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10523 p = (int) captured;
10524 if (p >= (int) BlackPawn) {
10525 p -= (int)BlackPawn;
10526 if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10527 /* Restore shogi-promoted piece to its original first */
10528 captured = (ChessSquare) (DEMOTED(captured));
10531 p = PieceToNumber((ChessSquare)p);
10532 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10533 board[p][BOARD_WIDTH-2]++;
10534 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10536 p -= (int)WhitePawn;
10537 if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10538 captured = (ChessSquare) (DEMOTED(captured));
10541 p = PieceToNumber((ChessSquare)p);
10542 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10543 board[BOARD_HEIGHT-1-p][1]++;
10544 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10547 } else if (gameInfo.variant == VariantAtomic) {
10548 if (captured != EmptySquare) {
10550 for (y = toY-1; y <= toY+1; y++) {
10551 for (x = toX-1; x <= toX+1; x++) {
10552 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10553 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10554 board[y][x] = EmptySquare;
10558 board[toY][toX] = EmptySquare;
10562 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10563 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10565 if(promoChar == '+') {
10566 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10567 board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10568 if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10569 board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10570 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10571 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10572 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10573 && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10574 board[toY][toX] = newPiece;
10576 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10577 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10578 // [HGM] superchess: take promotion piece out of holdings
10579 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10580 if((int)piece < (int)BlackPawn) { // determine stm from piece color
10581 if(!--board[k][BOARD_WIDTH-2])
10582 board[k][BOARD_WIDTH-1] = EmptySquare;
10584 if(!--board[BOARD_HEIGHT-1-k][1])
10585 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10590 /* Updates forwardMostMove */
10592 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10594 int x = toX, y = toY;
10595 char *s = parseList[forwardMostMove];
10596 ChessSquare p = boards[forwardMostMove][toY][toX];
10597 // forwardMostMove++; // [HGM] bare: moved downstream
10599 if(kill2X >= 0) x = kill2X, y = kill2Y; else
10600 if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10601 (void) CoordsToAlgebraic(boards[forwardMostMove],
10602 PosFlags(forwardMostMove),
10603 fromY, fromX, y, x, (killX < 0)*promoChar,
10605 if(kill2X >= 0 && kill2Y >= 0)
10606 sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10607 if(killX >= 0 && killY >= 0)
10608 sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10609 toX + AAA, toY + ONE - '0', promoChar);
10611 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10612 int timeLeft; static int lastLoadFlag=0; int king, piece;
10613 piece = boards[forwardMostMove][fromY][fromX];
10614 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10615 if(gameInfo.variant == VariantKnightmate)
10616 king += (int) WhiteUnicorn - (int) WhiteKing;
10617 if(forwardMostMove == 0) {
10618 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10619 fprintf(serverMoves, "%s;", UserName());
10620 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10621 fprintf(serverMoves, "%s;", second.tidy);
10622 fprintf(serverMoves, "%s;", first.tidy);
10623 if(gameMode == MachinePlaysWhite)
10624 fprintf(serverMoves, "%s;", UserName());
10625 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10626 fprintf(serverMoves, "%s;", second.tidy);
10627 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10628 lastLoadFlag = loadFlag;
10630 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10631 // print castling suffix
10632 if( toY == fromY && piece == king ) {
10634 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10636 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10639 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10640 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
10641 boards[forwardMostMove][toY][toX] == EmptySquare
10642 && fromX != toX && fromY != toY)
10643 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10644 // promotion suffix
10645 if(promoChar != NULLCHAR) {
10646 if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10647 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10648 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10649 else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10652 char buf[MOVE_LEN*2], *p; int len;
10653 fprintf(serverMoves, "/%d/%d",
10654 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10655 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10656 else timeLeft = blackTimeRemaining/1000;
10657 fprintf(serverMoves, "/%d", timeLeft);
10658 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10659 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10660 if(p = strchr(buf, '=')) *p = NULLCHAR;
10661 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10662 fprintf(serverMoves, "/%s", buf);
10664 fflush(serverMoves);
10667 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10668 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10671 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10672 if (commentList[forwardMostMove+1] != NULL) {
10673 free(commentList[forwardMostMove+1]);
10674 commentList[forwardMostMove+1] = NULL;
10676 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10677 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10678 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10679 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10680 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10681 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10682 adjustedClock = FALSE;
10683 gameInfo.result = GameUnfinished;
10684 if (gameInfo.resultDetails != NULL) {
10685 free(gameInfo.resultDetails);
10686 gameInfo.resultDetails = NULL;
10688 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10689 moveList[forwardMostMove - 1]);
10690 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10696 if(!IS_SHOGI(gameInfo.variant))
10697 strcat(parseList[forwardMostMove - 1], "+");
10701 strcat(parseList[forwardMostMove - 1], "#");
10706 /* Updates currentMove if not pausing */
10708 ShowMove (int fromX, int fromY, int toX, int toY)
10710 int instant = (gameMode == PlayFromGameFile) ?
10711 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10712 if(appData.noGUI) return;
10713 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10715 if (forwardMostMove == currentMove + 1) {
10716 AnimateMove(boards[forwardMostMove - 1],
10717 fromX, fromY, toX, toY);
10720 currentMove = forwardMostMove;
10723 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10725 if (instant) return;
10727 DisplayMove(currentMove - 1);
10728 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10729 if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10730 SetHighlights(fromX, fromY, toX, toY);
10733 DrawPosition(FALSE, boards[currentMove]);
10734 DisplayBothClocks();
10735 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10739 SendEgtPath (ChessProgramState *cps)
10740 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10741 char buf[MSG_SIZ], name[MSG_SIZ], *p;
10743 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10746 char c, *q = name+1, *r, *s;
10748 name[0] = ','; // extract next format name from feature and copy with prefixed ','
10749 while(*p && *p != ',') *q++ = *p++;
10750 *q++ = ':'; *q = 0;
10751 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10752 strcmp(name, ",nalimov:") == 0 ) {
10753 // take nalimov path from the menu-changeable option first, if it is defined
10754 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10755 SendToProgram(buf,cps); // send egtbpath command for nalimov
10757 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10758 (s = StrStr(appData.egtFormats, name)) != NULL) {
10759 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10760 s = r = StrStr(s, ":") + 1; // beginning of path info
10761 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10762 c = *r; *r = 0; // temporarily null-terminate path info
10763 *--q = 0; // strip of trailig ':' from name
10764 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10766 SendToProgram(buf,cps); // send egtbpath command for this format
10768 if(*p == ',') p++; // read away comma to position for next format name
10773 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10775 int width = 8, height = 8, holdings = 0; // most common sizes
10776 if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10777 // correct the deviations default for each variant
10778 if( v == VariantXiangqi ) width = 9, height = 10;
10779 if( v == VariantShogi ) width = 9, height = 9, holdings = 7;
10780 if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10781 if( v == VariantCapablanca || v == VariantCapaRandom ||
10782 v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10784 if( v == VariantCourier ) width = 12;
10785 if( v == VariantSuper ) holdings = 8;
10786 if( v == VariantGreat ) width = 10, holdings = 8;
10787 if( v == VariantSChess ) holdings = 7;
10788 if( v == VariantGrand ) width = 10, height = 10, holdings = 7;
10789 if( v == VariantChuChess) width = 10, height = 10;
10790 if( v == VariantChu ) width = 12, height = 12;
10791 return boardWidth >= 0 && boardWidth != width || // -1 is default,
10792 boardHeight >= 0 && boardHeight != height || // and thus by definition OK
10793 holdingsSize >= 0 && holdingsSize != holdings;
10796 char variantError[MSG_SIZ];
10799 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10800 { // returns error message (recognizable by upper-case) if engine does not support the variant
10801 char *p, *variant = VariantName(v);
10802 static char b[MSG_SIZ];
10803 if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10804 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10805 holdingsSize, variant); // cook up sized variant name
10806 /* [HGM] varsize: try first if this deviant size variant is specifically known */
10807 if(StrStr(list, b) == NULL) {
10808 // specific sized variant not known, check if general sizing allowed
10809 if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10810 snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10811 boardWidth, boardHeight, holdingsSize, engine);
10814 /* [HGM] here we really should compare with the maximum supported board size */
10816 } else snprintf(b, MSG_SIZ,"%s", variant);
10817 if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10818 p = StrStr(list, b);
10819 while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10821 // occurs not at all in list, or only as sub-string
10822 snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10823 if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10824 int l = strlen(variantError);
10826 while(p != list && p[-1] != ',') p--;
10827 q = strchr(p, ',');
10828 if(q) *q = NULLCHAR;
10829 snprintf(variantError + l, MSG_SIZ - l, _(", but %s is"), p);
10838 InitChessProgram (ChessProgramState *cps, int setup)
10839 /* setup needed to setup FRC opening position */
10841 char buf[MSG_SIZ], *b;
10842 if (appData.noChessProgram) return;
10843 hintRequested = FALSE;
10844 bookRequested = FALSE;
10846 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10847 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10848 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10849 if(cps->memSize) { /* [HGM] memory */
10850 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10851 SendToProgram(buf, cps);
10853 SendEgtPath(cps); /* [HGM] EGT */
10854 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10855 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10856 SendToProgram(buf, cps);
10859 setboardSpoiledMachineBlack = FALSE;
10860 SendToProgram(cps->initString, cps);
10861 if (gameInfo.variant != VariantNormal &&
10862 gameInfo.variant != VariantLoadable
10863 /* [HGM] also send variant if board size non-standard */
10864 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10866 b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10867 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10871 char c, *q = cps->variants, *p = strchr(q, ',');
10872 if(p) *p = NULLCHAR;
10873 v = StringToVariant(q);
10874 DisplayError(variantError, 0);
10875 if(v != VariantUnknown && cps == &first) {
10877 if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10878 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10879 ASSIGN(appData.variant, q);
10880 Reset(TRUE, FALSE);
10886 snprintf(buf, MSG_SIZ, "variant %s\n", b);
10887 SendToProgram(buf, cps);
10889 currentlyInitializedVariant = gameInfo.variant;
10891 /* [HGM] send opening position in FRC to first engine */
10893 SendToProgram("force\n", cps);
10895 /* engine is now in force mode! Set flag to wake it up after first move. */
10896 setboardSpoiledMachineBlack = 1;
10899 if (cps->sendICS) {
10900 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10901 SendToProgram(buf, cps);
10903 cps->maybeThinking = FALSE;
10904 cps->offeredDraw = 0;
10905 if (!appData.icsActive) {
10906 SendTimeControl(cps, movesPerSession, timeControl,
10907 timeIncrement, appData.searchDepth,
10910 if (appData.showThinking
10911 // [HGM] thinking: four options require thinking output to be sent
10912 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10914 SendToProgram("post\n", cps);
10916 SendToProgram("hard\n", cps);
10917 if (!appData.ponderNextMove) {
10918 /* Warning: "easy" is a toggle in GNU Chess, so don't send
10919 it without being sure what state we are in first. "hard"
10920 is not a toggle, so that one is OK.
10922 SendToProgram("easy\n", cps);
10924 if (cps->usePing) {
10925 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10926 SendToProgram(buf, cps);
10928 cps->initDone = TRUE;
10929 ClearEngineOutputPane(cps == &second);
10934 ResendOptions (ChessProgramState *cps, int toEngine)
10935 { // send the stored value of the options
10937 static char buf2[MSG_SIZ*10];
10938 char buf[MSG_SIZ], *p = buf2;
10939 Option *opt = cps->option;
10941 for(i=0; i<cps->nrOptions; i++, opt++) {
10943 switch(opt->type) {
10947 if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
10948 snprintf(buf, MSG_SIZ, "%s=%d", opt->name, opt->value);
10951 if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
10952 snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->choice[opt->value]);
10955 if(strcmp(opt->textValue, opt->name + MSG_SIZ - 100))
10956 snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->textValue);
10964 snprintf(buf2, MSG_SIZ, "option %s\n", buf);
10965 SendToProgram(buf2, cps);
10967 if(p != buf2) *p++ = ',';
10968 strncpy(p, buf, 10*MSG_SIZ-1 - (p - buf2));
10977 StartChessProgram (ChessProgramState *cps)
10982 if (appData.noChessProgram) return;
10983 cps->initDone = FALSE;
10985 if (strcmp(cps->host, "localhost") == 0) {
10986 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10987 } else if (*appData.remoteShell == NULLCHAR) {
10988 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10990 if (*appData.remoteUser == NULLCHAR) {
10991 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10994 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10995 cps->host, appData.remoteUser, cps->program);
10997 err = StartChildProcess(buf, "", &cps->pr);
11001 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
11002 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
11003 if(cps != &first) return;
11004 appData.noChessProgram = TRUE;
11007 // DisplayFatalError(buf, err, 1);
11008 // cps->pr = NoProc;
11009 // cps->isr = NULL;
11013 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
11014 if (cps->protocolVersion > 1) {
11015 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
11016 if(!cps->reload) { // do not clear options when reloading because of -xreuse
11017 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
11018 cps->comboCnt = 0; // and values of combo boxes
11020 SendToProgram(buf, cps);
11021 if(cps->reload) ResendOptions(cps, TRUE);
11023 SendToProgram("xboard\n", cps);
11028 TwoMachinesEventIfReady P((void))
11030 static int curMess = 0;
11031 if (first.lastPing != first.lastPong) {
11032 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
11033 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11036 if (second.lastPing != second.lastPong) {
11037 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
11038 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11041 DisplayMessage("", ""); curMess = 0;
11042 TwoMachinesEvent();
11046 MakeName (char *template)
11050 static char buf[MSG_SIZ];
11054 clock = time((time_t *)NULL);
11055 tm = localtime(&clock);
11057 while(*p++ = *template++) if(p[-1] == '%') {
11058 switch(*template++) {
11059 case 0: *p = 0; return buf;
11060 case 'Y': i = tm->tm_year+1900; break;
11061 case 'y': i = tm->tm_year-100; break;
11062 case 'M': i = tm->tm_mon+1; break;
11063 case 'd': i = tm->tm_mday; break;
11064 case 'h': i = tm->tm_hour; break;
11065 case 'm': i = tm->tm_min; break;
11066 case 's': i = tm->tm_sec; break;
11069 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11075 CountPlayers (char *p)
11078 while(p = strchr(p, '\n')) p++, n++; // count participants
11083 WriteTourneyFile (char *results, FILE *f)
11084 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11085 if(f == NULL) f = fopen(appData.tourneyFile, "w");
11086 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11087 // create a file with tournament description
11088 fprintf(f, "-participants {%s}\n", appData.participants);
11089 fprintf(f, "-seedBase %d\n", appData.seedBase);
11090 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11091 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11092 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11093 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11094 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11095 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11096 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11097 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11098 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11099 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11100 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11101 fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11102 fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11103 fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11104 fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11105 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11106 fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11107 fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11108 fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11109 fprintf(f, "-smpCores %d\n", appData.smpCores);
11111 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11113 fprintf(f, "-mps %d\n", appData.movesPerSession);
11114 fprintf(f, "-tc %s\n", appData.timeControl);
11115 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11117 fprintf(f, "-results \"%s\"\n", results);
11122 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11125 Substitute (char *participants, int expunge)
11127 int i, changed, changes=0, nPlayers=0;
11128 char *p, *q, *r, buf[MSG_SIZ];
11129 if(participants == NULL) return;
11130 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11131 r = p = participants; q = appData.participants;
11132 while(*p && *p == *q) {
11133 if(*p == '\n') r = p+1, nPlayers++;
11136 if(*p) { // difference
11137 while(*p && *p++ != '\n')
11139 while(*q && *q++ != '\n')
11141 changed = nPlayers;
11142 changes = 1 + (strcmp(p, q) != 0);
11144 if(changes == 1) { // a single engine mnemonic was changed
11145 q = r; while(*q) nPlayers += (*q++ == '\n');
11146 p = buf; while(*r && (*p = *r++) != '\n') p++;
11148 NamesToList(firstChessProgramNames, command, mnemonic, "all");
11149 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11150 if(mnemonic[i]) { // The substitute is valid
11152 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11153 flock(fileno(f), LOCK_EX);
11154 ParseArgsFromFile(f);
11155 fseek(f, 0, SEEK_SET);
11156 FREE(appData.participants); appData.participants = participants;
11157 if(expunge) { // erase results of replaced engine
11158 int len = strlen(appData.results), w, b, dummy;
11159 for(i=0; i<len; i++) {
11160 Pairing(i, nPlayers, &w, &b, &dummy);
11161 if((w == changed || b == changed) && appData.results[i] == '*') {
11162 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11167 for(i=0; i<len; i++) {
11168 Pairing(i, nPlayers, &w, &b, &dummy);
11169 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11172 WriteTourneyFile(appData.results, f);
11173 fclose(f); // release lock
11176 } else DisplayError(_("No engine with the name you gave is installed"), 0);
11178 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11179 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
11180 free(participants);
11185 CheckPlayers (char *participants)
11188 char buf[MSG_SIZ], *p;
11189 NamesToList(firstChessProgramNames, command, mnemonic, "all");
11190 while(p = strchr(participants, '\n')) {
11192 for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11194 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11196 DisplayError(buf, 0);
11200 participants = p + 1;
11206 CreateTourney (char *name)
11209 if(matchMode && strcmp(name, appData.tourneyFile)) {
11210 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11212 if(name[0] == NULLCHAR) {
11213 if(appData.participants[0])
11214 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11217 f = fopen(name, "r");
11218 if(f) { // file exists
11219 ASSIGN(appData.tourneyFile, name);
11220 ParseArgsFromFile(f); // parse it
11222 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11223 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11224 DisplayError(_("Not enough participants"), 0);
11227 if(CheckPlayers(appData.participants)) return 0;
11228 ASSIGN(appData.tourneyFile, name);
11229 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11230 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11233 appData.noChessProgram = FALSE;
11234 appData.clockMode = TRUE;
11240 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11242 char buf[2*MSG_SIZ], *p, *q;
11243 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11244 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11245 skip = !all && group[0]; // if group requested, we start in skip mode
11246 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11247 p = names; q = buf; header = 0;
11248 while(*p && *p != '\n') *q++ = *p++;
11250 if(*p == '\n') p++;
11251 if(buf[0] == '#') {
11252 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11253 depth++; // we must be entering a new group
11254 if(all) continue; // suppress printing group headers when complete list requested
11256 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11258 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11259 if(engineList[i]) free(engineList[i]);
11260 engineList[i] = strdup(buf);
11261 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11262 if(engineMnemonic[i]) free(engineMnemonic[i]);
11263 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11265 sscanf(q + 8, "%s", buf + strlen(buf));
11268 engineMnemonic[i] = strdup(buf);
11271 engineList[i] = engineMnemonic[i] = NULL;
11276 SaveEngineSettings (int n)
11278 int len; char *p, *q, *s, buf[MSG_SIZ], *optionSettings;
11279 if(!currentEngine[n] || !currentEngine[n][0]) return; // no engine from list is loaded
11280 p = strstr(firstChessProgramNames, currentEngine[n]);
11281 if(!p) return; // sanity check; engine could be deleted from list after loading
11282 optionSettings = ResendOptions(n ? &second : &first, FALSE);
11283 len = strlen(currentEngine[n]);
11284 q = p + len; *p = 0; // cut list into head and tail piece
11285 s = strstr(currentEngine[n], "firstOptions");
11286 if(s && (s[-1] == '-' || s[-1] == '/') && (s[12] == ' ' || s[12] == '=') && (s[13] == '"' || s[13] == '\'')) {
11288 while(*r && *r != s[13]) r++;
11289 s[14] = 0; // cut currentEngine into head and tail part, removing old settings
11290 snprintf(buf, MSG_SIZ, "%s%s%s", currentEngine[n], optionSettings, *r ? r : "\""); // synthesize new engine line
11291 } else if(*optionSettings) {
11292 snprintf(buf, MSG_SIZ, "%s -firstOptions \"%s\"", currentEngine[n], optionSettings);
11294 ASSIGN(currentEngine[n], buf); // updated engine line
11295 len = p - firstChessProgramNames + strlen(q) + strlen(currentEngine[n]) + 1;
11297 snprintf(s, len, "%s%s%s", firstChessProgramNames, currentEngine[n], q);
11298 FREE(firstChessProgramNames); firstChessProgramNames = s; // new list
11301 // following implemented as macro to avoid type limitations
11302 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11305 SwapEngines (int n)
11306 { // swap settings for first engine and other engine (so far only some selected options)
11311 SWAP(chessProgram, p)
11313 SWAP(hasOwnBookUCI, h)
11314 SWAP(protocolVersion, h)
11316 SWAP(scoreIsAbsolute, h)
11321 SWAP(engOptions, p)
11322 SWAP(engInitString, p)
11323 SWAP(computerString, p)
11325 SWAP(fenOverride, p)
11327 SWAP(accumulateTC, h)
11334 GetEngineLine (char *s, int n)
11338 extern char *icsNames;
11339 if(!s || !*s) return 0;
11340 NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11341 for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11342 if(!mnemonic[i]) return 0;
11343 if(n == 11) return 1; // just testing if there was a match
11344 snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11345 if(n == 1) SwapEngines(n);
11346 ParseArgsFromString(buf);
11347 if(n == 1) SwapEngines(n);
11348 if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11349 SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11350 ParseArgsFromString(buf);
11356 SetPlayer (int player, char *p)
11357 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
11359 char buf[MSG_SIZ], *engineName;
11360 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11361 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11362 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11364 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11365 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11366 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11367 ParseArgsFromString(buf);
11368 } else { // no engine with this nickname is installed!
11369 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11370 ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11371 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11373 DisplayError(buf, 0);
11380 char *recentEngines;
11383 RecentEngineEvent (int nr)
11386 // SwapEngines(1); // bump first to second
11387 // ReplaceEngine(&second, 1); // and load it there
11388 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11389 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11390 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11391 ReplaceEngine(&first, 0);
11392 FloatToFront(&appData.recentEngineList, command[n]);
11397 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11398 { // determine players from game number
11399 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11401 if(appData.tourneyType == 0) {
11402 roundsPerCycle = (nPlayers - 1) | 1;
11403 pairingsPerRound = nPlayers / 2;
11404 } else if(appData.tourneyType > 0) {
11405 roundsPerCycle = nPlayers - appData.tourneyType;
11406 pairingsPerRound = appData.tourneyType;
11408 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11409 gamesPerCycle = gamesPerRound * roundsPerCycle;
11410 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11411 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11412 curRound = nr / gamesPerRound; nr %= gamesPerRound;
11413 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11414 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11415 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11417 if(appData.cycleSync) *syncInterval = gamesPerCycle;
11418 if(appData.roundSync) *syncInterval = gamesPerRound;
11420 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11422 if(appData.tourneyType == 0) {
11423 if(curPairing == (nPlayers-1)/2 ) {
11424 *whitePlayer = curRound;
11425 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11427 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11428 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11429 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11430 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11432 } else if(appData.tourneyType > 1) {
11433 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11434 *whitePlayer = curRound + appData.tourneyType;
11435 } else if(appData.tourneyType > 0) {
11436 *whitePlayer = curPairing;
11437 *blackPlayer = curRound + appData.tourneyType;
11440 // take care of white/black alternation per round.
11441 // For cycles and games this is already taken care of by default, derived from matchGame!
11442 return curRound & 1;
11446 NextTourneyGame (int nr, int *swapColors)
11447 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11449 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11451 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11452 tf = fopen(appData.tourneyFile, "r");
11453 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11454 ParseArgsFromFile(tf); fclose(tf);
11455 InitTimeControls(); // TC might be altered from tourney file
11457 nPlayers = CountPlayers(appData.participants); // count participants
11458 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11459 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11462 p = q = appData.results;
11463 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11464 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11465 DisplayMessage(_("Waiting for other game(s)"),"");
11466 waitingForGame = TRUE;
11467 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11470 waitingForGame = FALSE;
11473 if(appData.tourneyType < 0) {
11474 if(nr>=0 && !pairingReceived) {
11476 if(pairing.pr == NoProc) {
11477 if(!appData.pairingEngine[0]) {
11478 DisplayFatalError(_("No pairing engine specified"), 0, 1);
11481 StartChessProgram(&pairing); // starts the pairing engine
11483 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11484 SendToProgram(buf, &pairing);
11485 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11486 SendToProgram(buf, &pairing);
11487 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11489 pairingReceived = 0; // ... so we continue here
11491 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11492 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11493 matchGame = 1; roundNr = nr / syncInterval + 1;
11496 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11498 // redefine engines, engine dir, etc.
11499 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11500 if(first.pr == NoProc) {
11501 if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11502 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
11504 if(second.pr == NoProc) {
11506 if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11507 SwapEngines(1); // and make that valid for second engine by swapping
11508 InitEngine(&second, 1);
11510 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
11511 UpdateLogos(FALSE); // leave display to ModeHiglight()
11517 { // performs game initialization that does not invoke engines, and then tries to start the game
11518 int res, firstWhite, swapColors = 0;
11519 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11520 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
11522 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11523 if(strcmp(buf, currentDebugFile)) { // name has changed
11524 FILE *f = fopen(buf, "w");
11525 if(f) { // if opening the new file failed, just keep using the old one
11526 ASSIGN(currentDebugFile, buf);
11530 if(appData.serverFileName) {
11531 if(serverFP) fclose(serverFP);
11532 serverFP = fopen(appData.serverFileName, "w");
11533 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11534 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11538 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11539 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11540 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
11541 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11542 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11543 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11544 Reset(FALSE, first.pr != NoProc);
11545 res = LoadGameOrPosition(matchGame); // setup game
11546 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11547 if(!res) return; // abort when bad game/pos file
11548 if(appData.epd) {// in EPD mode we make sure first engine is to move
11549 firstWhite = !(forwardMostMove & 1);
11550 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
11551 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11553 TwoMachinesEvent();
11557 UserAdjudicationEvent (int result)
11559 ChessMove gameResult = GameIsDrawn;
11562 gameResult = WhiteWins;
11564 else if( result < 0 ) {
11565 gameResult = BlackWins;
11568 if( gameMode == TwoMachinesPlay ) {
11569 GameEnds( gameResult, "User adjudication", GE_XBOARD );
11574 // [HGM] save: calculate checksum of game to make games easily identifiable
11576 StringCheckSum (char *s)
11579 if(s==NULL) return 0;
11580 while(*s) i = i*259 + *s++;
11588 for(i=backwardMostMove; i<forwardMostMove; i++) {
11589 sum += pvInfoList[i].depth;
11590 sum += StringCheckSum(parseList[i]);
11591 sum += StringCheckSum(commentList[i]);
11594 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11595 return sum + StringCheckSum(commentList[i]);
11596 } // end of save patch
11599 GameEnds (ChessMove result, char *resultDetails, int whosays)
11601 GameMode nextGameMode;
11603 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11605 if(endingGame) return; /* [HGM] crash: forbid recursion */
11607 if(twoBoards) { // [HGM] dual: switch back to one board
11608 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11609 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11611 if (appData.debugMode) {
11612 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11613 result, resultDetails ? resultDetails : "(null)", whosays);
11616 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11618 if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11620 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11621 /* If we are playing on ICS, the server decides when the
11622 game is over, but the engine can offer to draw, claim
11626 if (appData.zippyPlay && first.initDone) {
11627 if (result == GameIsDrawn) {
11628 /* In case draw still needs to be claimed */
11629 SendToICS(ics_prefix);
11630 SendToICS("draw\n");
11631 } else if (StrCaseStr(resultDetails, "resign")) {
11632 SendToICS(ics_prefix);
11633 SendToICS("resign\n");
11637 endingGame = 0; /* [HGM] crash */
11641 /* If we're loading the game from a file, stop */
11642 if (whosays == GE_FILE) {
11643 (void) StopLoadGameTimer();
11647 /* Cancel draw offers */
11648 first.offeredDraw = second.offeredDraw = 0;
11650 /* If this is an ICS game, only ICS can really say it's done;
11651 if not, anyone can. */
11652 isIcsGame = (gameMode == IcsPlayingWhite ||
11653 gameMode == IcsPlayingBlack ||
11654 gameMode == IcsObserving ||
11655 gameMode == IcsExamining);
11657 if (!isIcsGame || whosays == GE_ICS) {
11658 /* OK -- not an ICS game, or ICS said it was done */
11660 if (!isIcsGame && !appData.noChessProgram)
11661 SetUserThinkingEnables();
11663 /* [HGM] if a machine claims the game end we verify this claim */
11664 if(gameMode == TwoMachinesPlay && appData.testClaims) {
11665 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11667 ChessMove trueResult = (ChessMove) -1;
11669 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
11670 first.twoMachinesColor[0] :
11671 second.twoMachinesColor[0] ;
11673 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11674 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11675 /* [HGM] verify: engine mate claims accepted if they were flagged */
11676 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11678 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11679 /* [HGM] verify: engine mate claims accepted if they were flagged */
11680 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11682 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11683 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11686 // now verify win claims, but not in drop games, as we don't understand those yet
11687 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11688 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11689 (result == WhiteWins && claimer == 'w' ||
11690 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
11691 if (appData.debugMode) {
11692 fprintf(debugFP, "result=%d sp=%d move=%d\n",
11693 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11695 if(result != trueResult) {
11696 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11697 result = claimer == 'w' ? BlackWins : WhiteWins;
11698 resultDetails = buf;
11701 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11702 && (forwardMostMove <= backwardMostMove ||
11703 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11704 (claimer=='b')==(forwardMostMove&1))
11706 /* [HGM] verify: draws that were not flagged are false claims */
11707 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11708 result = claimer == 'w' ? BlackWins : WhiteWins;
11709 resultDetails = buf;
11711 /* (Claiming a loss is accepted no questions asked!) */
11712 } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11713 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11714 result = GameUnfinished;
11715 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11717 /* [HGM] bare: don't allow bare King to win */
11718 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11719 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11720 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11721 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11722 && result != GameIsDrawn)
11723 { int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11724 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11725 int p = (int)boards[forwardMostMove][i][j] - color;
11726 if(p >= 0 && p <= (int)WhiteKing) k++;
11727 oppoKings += (p + color == WhiteKing + BlackPawn - color);
11729 if (appData.debugMode) {
11730 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11731 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11733 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11734 result = GameIsDrawn;
11735 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11736 resultDetails = buf;
11742 if(serverMoves != NULL && !loadFlag) { char c = '=';
11743 if(result==WhiteWins) c = '+';
11744 if(result==BlackWins) c = '-';
11745 if(resultDetails != NULL)
11746 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11748 if (resultDetails != NULL) {
11749 gameInfo.result = result;
11750 gameInfo.resultDetails = StrSave(resultDetails);
11752 /* display last move only if game was not loaded from file */
11753 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11754 DisplayMove(currentMove - 1);
11756 if (forwardMostMove != 0) {
11757 if (gameMode != PlayFromGameFile && gameMode != EditGame
11758 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11760 if (*appData.saveGameFile != NULLCHAR) {
11761 if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11762 AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11764 SaveGameToFile(appData.saveGameFile, TRUE);
11765 } else if (appData.autoSaveGames) {
11766 if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11768 if (*appData.savePositionFile != NULLCHAR) {
11769 SavePositionToFile(appData.savePositionFile);
11771 AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11775 /* Tell program how game ended in case it is learning */
11776 /* [HGM] Moved this to after saving the PGN, just in case */
11777 /* engine died and we got here through time loss. In that */
11778 /* case we will get a fatal error writing the pipe, which */
11779 /* would otherwise lose us the PGN. */
11780 /* [HGM] crash: not needed anymore, but doesn't hurt; */
11781 /* output during GameEnds should never be fatal anymore */
11782 if (gameMode == MachinePlaysWhite ||
11783 gameMode == MachinePlaysBlack ||
11784 gameMode == TwoMachinesPlay ||
11785 gameMode == IcsPlayingWhite ||
11786 gameMode == IcsPlayingBlack ||
11787 gameMode == BeginningOfGame) {
11789 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11791 if (first.pr != NoProc) {
11792 SendToProgram(buf, &first);
11794 if (second.pr != NoProc &&
11795 gameMode == TwoMachinesPlay) {
11796 SendToProgram(buf, &second);
11801 if (appData.icsActive) {
11802 if (appData.quietPlay &&
11803 (gameMode == IcsPlayingWhite ||
11804 gameMode == IcsPlayingBlack)) {
11805 SendToICS(ics_prefix);
11806 SendToICS("set shout 1\n");
11808 nextGameMode = IcsIdle;
11809 ics_user_moved = FALSE;
11810 /* clean up premove. It's ugly when the game has ended and the
11811 * premove highlights are still on the board.
11814 gotPremove = FALSE;
11815 ClearPremoveHighlights();
11816 DrawPosition(FALSE, boards[currentMove]);
11818 if (whosays == GE_ICS) {
11821 if (gameMode == IcsPlayingWhite)
11823 else if(gameMode == IcsPlayingBlack)
11824 PlayIcsLossSound();
11827 if (gameMode == IcsPlayingBlack)
11829 else if(gameMode == IcsPlayingWhite)
11830 PlayIcsLossSound();
11833 PlayIcsDrawSound();
11836 PlayIcsUnfinishedSound();
11839 if(appData.quitNext) { ExitEvent(0); return; }
11840 } else if (gameMode == EditGame ||
11841 gameMode == PlayFromGameFile ||
11842 gameMode == AnalyzeMode ||
11843 gameMode == AnalyzeFile) {
11844 nextGameMode = gameMode;
11846 nextGameMode = EndOfGame;
11851 nextGameMode = gameMode;
11854 if (appData.noChessProgram) {
11855 gameMode = nextGameMode;
11857 endingGame = 0; /* [HGM] crash */
11862 /* Put first chess program into idle state */
11863 if (first.pr != NoProc &&
11864 (gameMode == MachinePlaysWhite ||
11865 gameMode == MachinePlaysBlack ||
11866 gameMode == TwoMachinesPlay ||
11867 gameMode == IcsPlayingWhite ||
11868 gameMode == IcsPlayingBlack ||
11869 gameMode == BeginningOfGame)) {
11870 SendToProgram("force\n", &first);
11871 if (first.usePing) {
11873 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11874 SendToProgram(buf, &first);
11877 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11878 /* Kill off first chess program */
11879 if (first.isr != NULL)
11880 RemoveInputSource(first.isr);
11883 if (first.pr != NoProc) {
11885 DoSleep( appData.delayBeforeQuit );
11886 SendToProgram("quit\n", &first);
11887 DestroyChildProcess(first.pr, 4 + first.useSigterm);
11888 first.reload = TRUE;
11892 if (second.reuse) {
11893 /* Put second chess program into idle state */
11894 if (second.pr != NoProc &&
11895 gameMode == TwoMachinesPlay) {
11896 SendToProgram("force\n", &second);
11897 if (second.usePing) {
11899 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11900 SendToProgram(buf, &second);
11903 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11904 /* Kill off second chess program */
11905 if (second.isr != NULL)
11906 RemoveInputSource(second.isr);
11909 if (second.pr != NoProc) {
11910 DoSleep( appData.delayBeforeQuit );
11911 SendToProgram("quit\n", &second);
11912 DestroyChildProcess(second.pr, 4 + second.useSigterm);
11913 second.reload = TRUE;
11915 second.pr = NoProc;
11918 if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11919 char resChar = '=';
11923 if (first.twoMachinesColor[0] == 'w') {
11926 second.matchWins++;
11931 if (first.twoMachinesColor[0] == 'b') {
11934 second.matchWins++;
11937 case GameUnfinished:
11943 if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11944 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11945 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11946 ReserveGame(nextGame, resChar); // sets nextGame
11947 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11948 else ranking = strdup("busy"); //suppress popup when aborted but not finished
11949 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11951 if (nextGame <= appData.matchGames && !abortMatch) {
11952 gameMode = nextGameMode;
11953 matchGame = nextGame; // this will be overruled in tourney mode!
11954 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11955 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11956 endingGame = 0; /* [HGM] crash */
11959 gameMode = nextGameMode;
11961 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
11962 OutputKibitz(2, buf);
11963 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
11964 OutputKibitz(2, buf);
11965 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
11966 if(second.matchWins) OutputKibitz(2, buf);
11967 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
11968 OutputKibitz(2, buf);
11970 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11971 first.tidy, second.tidy,
11972 first.matchWins, second.matchWins,
11973 appData.matchGames - (first.matchWins + second.matchWins));
11974 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11975 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11976 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11977 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11978 first.twoMachinesColor = "black\n";
11979 second.twoMachinesColor = "white\n";
11981 first.twoMachinesColor = "white\n";
11982 second.twoMachinesColor = "black\n";
11986 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11987 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11989 gameMode = nextGameMode;
11991 endingGame = 0; /* [HGM] crash */
11992 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11993 if(matchMode == TRUE) { // match through command line: exit with or without popup
11995 ToNrEvent(forwardMostMove);
11996 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11998 } else DisplayFatalError(buf, 0, 0);
11999 } else { // match through menu; just stop, with or without popup
12000 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
12003 if(strcmp(ranking, "busy")) DisplayNote(ranking);
12004 } else DisplayNote(buf);
12006 if(ranking) free(ranking);
12010 /* Assumes program was just initialized (initString sent).
12011 Leaves program in force mode. */
12013 FeedMovesToProgram (ChessProgramState *cps, int upto)
12017 if (appData.debugMode)
12018 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
12019 startedFromSetupPosition ? "position and " : "",
12020 backwardMostMove, upto, cps->which);
12021 if(currentlyInitializedVariant != gameInfo.variant) {
12023 // [HGM] variantswitch: make engine aware of new variant
12024 if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
12025 gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
12026 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
12027 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
12028 SendToProgram(buf, cps);
12029 currentlyInitializedVariant = gameInfo.variant;
12031 SendToProgram("force\n", cps);
12032 if (startedFromSetupPosition) {
12033 SendBoard(cps, backwardMostMove);
12034 if (appData.debugMode) {
12035 fprintf(debugFP, "feedMoves\n");
12038 for (i = backwardMostMove; i < upto; i++) {
12039 SendMoveToProgram(i, cps);
12045 ResurrectChessProgram ()
12047 /* The chess program may have exited.
12048 If so, restart it and feed it all the moves made so far. */
12049 static int doInit = 0;
12051 if (appData.noChessProgram) return 1;
12053 if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
12054 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
12055 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
12056 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
12058 if (first.pr != NoProc) return 1;
12059 StartChessProgram(&first);
12061 InitChessProgram(&first, FALSE);
12062 FeedMovesToProgram(&first, currentMove);
12064 if (!first.sendTime) {
12065 /* can't tell gnuchess what its clock should read,
12066 so we bow to its notion. */
12068 timeRemaining[0][currentMove] = whiteTimeRemaining;
12069 timeRemaining[1][currentMove] = blackTimeRemaining;
12072 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
12073 appData.icsEngineAnalyze) && first.analysisSupport) {
12074 SendToProgram("analyze\n", &first);
12075 first.analyzing = TRUE;
12081 * Button procedures
12084 Reset (int redraw, int init)
12088 if (appData.debugMode) {
12089 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12090 redraw, init, gameMode);
12092 pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12093 deadRanks = 0; // assume entire board is used
12094 for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12095 CleanupTail(); // [HGM] vari: delete any stored variations
12096 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12097 pausing = pauseExamInvalid = FALSE;
12098 startedFromSetupPosition = blackPlaysFirst = FALSE;
12100 whiteFlag = blackFlag = FALSE;
12101 userOfferedDraw = FALSE;
12102 hintRequested = bookRequested = FALSE;
12103 first.maybeThinking = FALSE;
12104 second.maybeThinking = FALSE;
12105 first.bookSuspend = FALSE; // [HGM] book
12106 second.bookSuspend = FALSE;
12107 thinkOutput[0] = NULLCHAR;
12108 lastHint[0] = NULLCHAR;
12109 ClearGameInfo(&gameInfo);
12110 gameInfo.variant = StringToVariant(appData.variant);
12111 if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12112 gameInfo.variant = VariantUnknown;
12113 strncpy(engineVariant, appData.variant, MSG_SIZ);
12115 ics_user_moved = ics_clock_paused = FALSE;
12116 ics_getting_history = H_FALSE;
12118 white_holding[0] = black_holding[0] = NULLCHAR;
12119 ClearProgramStats();
12120 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12124 flipView = appData.flipView;
12125 ClearPremoveHighlights();
12126 gotPremove = FALSE;
12127 alarmSounded = FALSE;
12128 killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12130 GameEnds(EndOfFile, NULL, GE_PLAYER);
12131 if(appData.serverMovesName != NULL) {
12132 /* [HGM] prepare to make moves file for broadcasting */
12133 clock_t t = clock();
12134 if(serverMoves != NULL) fclose(serverMoves);
12135 serverMoves = fopen(appData.serverMovesName, "r");
12136 if(serverMoves != NULL) {
12137 fclose(serverMoves);
12138 /* delay 15 sec before overwriting, so all clients can see end */
12139 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12141 serverMoves = fopen(appData.serverMovesName, "w");
12145 gameMode = BeginningOfGame;
12147 if(appData.icsActive) gameInfo.variant = VariantNormal;
12148 currentMove = forwardMostMove = backwardMostMove = 0;
12149 MarkTargetSquares(1);
12150 InitPosition(redraw);
12151 for (i = 0; i < MAX_MOVES; i++) {
12152 if (commentList[i] != NULL) {
12153 free(commentList[i]);
12154 commentList[i] = NULL;
12158 timeRemaining[0][0] = whiteTimeRemaining;
12159 timeRemaining[1][0] = blackTimeRemaining;
12161 if (first.pr == NoProc) {
12162 StartChessProgram(&first);
12165 InitChessProgram(&first, startedFromSetupPosition);
12168 DisplayMessage("", "");
12169 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12170 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12171 ClearMap(); // [HGM] exclude: invalidate map
12175 AutoPlayGameLoop ()
12178 if (!AutoPlayOneMove())
12180 if (matchMode || appData.timeDelay == 0)
12182 if (appData.timeDelay < 0)
12184 StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12192 ReloadGame(1); // next game
12198 int fromX, fromY, toX, toY;
12200 if (appData.debugMode) {
12201 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12204 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12207 if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12208 pvInfoList[currentMove].depth = programStats.depth;
12209 pvInfoList[currentMove].score = programStats.score;
12210 pvInfoList[currentMove].time = 0;
12211 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12212 else { // append analysis of final position as comment
12214 snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12215 AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12217 programStats.depth = 0;
12220 if (currentMove >= forwardMostMove) {
12221 if(gameMode == AnalyzeFile) {
12222 if(appData.loadGameIndex == -1) {
12223 GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12224 ScheduleDelayedEvent(AnalyzeNextGame, 10);
12226 ExitAnalyzeMode(); SendToProgram("force\n", &first);
12229 // gameMode = EndOfGame;
12230 // ModeHighlight();
12232 /* [AS] Clear current move marker at the end of a game */
12233 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12238 toX = moveList[currentMove][2] - AAA;
12239 toY = moveList[currentMove][3] - ONE;
12241 if (moveList[currentMove][1] == '@') {
12242 if (appData.highlightLastMove) {
12243 SetHighlights(-1, -1, toX, toY);
12246 fromX = moveList[currentMove][0] - AAA;
12247 fromY = moveList[currentMove][1] - ONE;
12249 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12251 if(moveList[currentMove][4] == ';') { // multi-leg
12252 killX = moveList[currentMove][5] - AAA;
12253 killY = moveList[currentMove][6] - ONE;
12255 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12256 killX = killY = -1;
12258 if (appData.highlightLastMove) {
12259 SetHighlights(fromX, fromY, toX, toY);
12262 DisplayMove(currentMove);
12263 SendMoveToProgram(currentMove++, &first);
12264 DisplayBothClocks();
12265 DrawPosition(FALSE, boards[currentMove]);
12266 // [HGM] PV info: always display, routine tests if empty
12267 DisplayComment(currentMove - 1, commentList[currentMove]);
12273 LoadGameOneMove (ChessMove readAhead)
12275 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12276 char promoChar = NULLCHAR;
12277 ChessMove moveType;
12278 char move[MSG_SIZ];
12281 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12282 gameMode != AnalyzeMode && gameMode != Training) {
12287 yyboardindex = forwardMostMove;
12288 if (readAhead != EndOfFile) {
12289 moveType = readAhead;
12291 if (gameFileFP == NULL)
12293 moveType = (ChessMove) Myylex();
12297 switch (moveType) {
12299 if (appData.debugMode)
12300 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12303 /* append the comment but don't display it */
12304 AppendComment(currentMove, p, FALSE);
12307 case WhiteCapturesEnPassant:
12308 case BlackCapturesEnPassant:
12309 case WhitePromotion:
12310 case BlackPromotion:
12311 case WhiteNonPromotion:
12312 case BlackNonPromotion:
12315 case WhiteKingSideCastle:
12316 case WhiteQueenSideCastle:
12317 case BlackKingSideCastle:
12318 case BlackQueenSideCastle:
12319 case WhiteKingSideCastleWild:
12320 case WhiteQueenSideCastleWild:
12321 case BlackKingSideCastleWild:
12322 case BlackQueenSideCastleWild:
12324 case WhiteHSideCastleFR:
12325 case WhiteASideCastleFR:
12326 case BlackHSideCastleFR:
12327 case BlackASideCastleFR:
12329 if (appData.debugMode)
12330 fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12331 fromX = currentMoveString[0] - AAA;
12332 fromY = currentMoveString[1] - ONE;
12333 toX = currentMoveString[2] - AAA;
12334 toY = currentMoveString[3] - ONE;
12335 promoChar = currentMoveString[4];
12336 if(promoChar == ';') promoChar = currentMoveString[7];
12341 if (appData.debugMode)
12342 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12343 fromX = moveType == WhiteDrop ?
12344 (int) CharToPiece(ToUpper(currentMoveString[0])) :
12345 (int) CharToPiece(ToLower(currentMoveString[0]));
12347 toX = currentMoveString[2] - AAA;
12348 toY = currentMoveString[3] - ONE;
12354 case GameUnfinished:
12355 if (appData.debugMode)
12356 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12357 p = strchr(yy_text, '{');
12358 if (p == NULL) p = strchr(yy_text, '(');
12361 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12363 q = strchr(p, *p == '{' ? '}' : ')');
12364 if (q != NULL) *q = NULLCHAR;
12367 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12368 GameEnds(moveType, p, GE_FILE);
12370 if (cmailMsgLoaded) {
12372 flipView = WhiteOnMove(currentMove);
12373 if (moveType == GameUnfinished) flipView = !flipView;
12374 if (appData.debugMode)
12375 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12380 if (appData.debugMode)
12381 fprintf(debugFP, "Parser hit end of file\n");
12382 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12388 if (WhiteOnMove(currentMove)) {
12389 GameEnds(BlackWins, "Black mates", GE_FILE);
12391 GameEnds(WhiteWins, "White mates", GE_FILE);
12395 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12401 case MoveNumberOne:
12402 if (lastLoadGameStart == GNUChessGame) {
12403 /* GNUChessGames have numbers, but they aren't move numbers */
12404 if (appData.debugMode)
12405 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12406 yy_text, (int) moveType);
12407 return LoadGameOneMove(EndOfFile); /* tail recursion */
12409 /* else fall thru */
12414 /* Reached start of next game in file */
12415 if (appData.debugMode)
12416 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12417 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12423 if (WhiteOnMove(currentMove)) {
12424 GameEnds(BlackWins, "Black mates", GE_FILE);
12426 GameEnds(WhiteWins, "White mates", GE_FILE);
12430 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12436 case PositionDiagram: /* should not happen; ignore */
12437 case ElapsedTime: /* ignore */
12438 case NAG: /* ignore */
12439 if (appData.debugMode)
12440 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12441 yy_text, (int) moveType);
12442 return LoadGameOneMove(EndOfFile); /* tail recursion */
12445 if (appData.testLegality) {
12446 if (appData.debugMode)
12447 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12448 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12449 (forwardMostMove / 2) + 1,
12450 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12451 DisplayError(move, 0);
12454 if (appData.debugMode)
12455 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12456 yy_text, currentMoveString);
12457 if(currentMoveString[1] == '@') {
12458 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12461 fromX = currentMoveString[0] - AAA;
12462 fromY = currentMoveString[1] - ONE;
12464 toX = currentMoveString[2] - AAA;
12465 toY = currentMoveString[3] - ONE;
12466 promoChar = currentMoveString[4];
12470 case AmbiguousMove:
12471 if (appData.debugMode)
12472 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12473 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12474 (forwardMostMove / 2) + 1,
12475 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12476 DisplayError(move, 0);
12481 case ImpossibleMove:
12482 if (appData.debugMode)
12483 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12484 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12485 (forwardMostMove / 2) + 1,
12486 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12487 DisplayError(move, 0);
12493 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12494 DrawPosition(FALSE, boards[currentMove]);
12495 DisplayBothClocks();
12496 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12497 DisplayComment(currentMove - 1, commentList[currentMove]);
12499 (void) StopLoadGameTimer();
12501 cmailOldMove = forwardMostMove;
12504 /* currentMoveString is set as a side-effect of yylex */
12506 thinkOutput[0] = NULLCHAR;
12507 MakeMove(fromX, fromY, toX, toY, promoChar);
12508 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12509 currentMove = forwardMostMove;
12514 /* Load the nth game from the given file */
12516 LoadGameFromFile (char *filename, int n, char *title, int useList)
12521 if (strcmp(filename, "-") == 0) {
12525 f = fopen(filename, "rb");
12527 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12528 DisplayError(buf, errno);
12532 if (fseek(f, 0, 0) == -1) {
12533 /* f is not seekable; probably a pipe */
12536 if (useList && n == 0) {
12537 int error = GameListBuild(f);
12539 DisplayError(_("Cannot build game list"), error);
12540 } else if (!ListEmpty(&gameList) &&
12541 ((ListGame *) gameList.tailPred)->number > 1) {
12542 GameListPopUp(f, title);
12549 return LoadGame(f, n, title, FALSE);
12554 MakeRegisteredMove ()
12556 int fromX, fromY, toX, toY;
12558 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12559 switch (cmailMoveType[lastLoadGameNumber - 1]) {
12562 if (appData.debugMode)
12563 fprintf(debugFP, "Restoring %s for game %d\n",
12564 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12566 thinkOutput[0] = NULLCHAR;
12567 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12568 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12569 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12570 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12571 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12572 promoChar = cmailMove[lastLoadGameNumber - 1][4];
12573 MakeMove(fromX, fromY, toX, toY, promoChar);
12574 ShowMove(fromX, fromY, toX, toY);
12576 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12583 if (WhiteOnMove(currentMove)) {
12584 GameEnds(BlackWins, "Black mates", GE_PLAYER);
12586 GameEnds(WhiteWins, "White mates", GE_PLAYER);
12591 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12598 if (WhiteOnMove(currentMove)) {
12599 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12601 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12606 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12617 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12619 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12623 if (gameNumber > nCmailGames) {
12624 DisplayError(_("No more games in this message"), 0);
12627 if (f == lastLoadGameFP) {
12628 int offset = gameNumber - lastLoadGameNumber;
12630 cmailMsg[0] = NULLCHAR;
12631 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12632 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12633 nCmailMovesRegistered--;
12635 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12636 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12637 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12640 if (! RegisterMove()) return FALSE;
12644 retVal = LoadGame(f, gameNumber, title, useList);
12646 /* Make move registered during previous look at this game, if any */
12647 MakeRegisteredMove();
12649 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12650 commentList[currentMove]
12651 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12652 DisplayComment(currentMove - 1, commentList[currentMove]);
12658 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12660 ReloadGame (int offset)
12662 int gameNumber = lastLoadGameNumber + offset;
12663 if (lastLoadGameFP == NULL) {
12664 DisplayError(_("No game has been loaded yet"), 0);
12667 if (gameNumber <= 0) {
12668 DisplayError(_("Can't back up any further"), 0);
12671 if (cmailMsgLoaded) {
12672 return CmailLoadGame(lastLoadGameFP, gameNumber,
12673 lastLoadGameTitle, lastLoadGameUseList);
12675 return LoadGame(lastLoadGameFP, gameNumber,
12676 lastLoadGameTitle, lastLoadGameUseList);
12680 int keys[EmptySquare+1];
12683 PositionMatches (Board b1, Board b2)
12686 switch(appData.searchMode) {
12687 case 1: return CompareWithRights(b1, b2);
12689 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12690 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12694 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12695 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12696 sum += keys[b1[r][f]] - keys[b2[r][f]];
12700 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12701 sum += keys[b1[r][f]] - keys[b2[r][f]];
12713 int pieceList[256], quickBoard[256];
12714 ChessSquare pieceType[256] = { EmptySquare };
12715 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12716 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12717 int soughtTotal, turn;
12718 Boolean epOK, flipSearch;
12721 unsigned char piece, to;
12724 #define DSIZE (250000)
12726 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12727 Move *moveDatabase = initialSpace;
12728 unsigned int movePtr, dataSize = DSIZE;
12731 MakePieceList (Board board, int *counts)
12733 int r, f, n=Q_PROMO, total=0;
12734 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12735 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12736 int sq = f + (r<<4);
12737 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12738 quickBoard[sq] = ++n;
12740 pieceType[n] = board[r][f];
12741 counts[board[r][f]]++;
12742 if(board[r][f] == WhiteKing) pieceList[1] = n; else
12743 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12747 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12752 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12754 int sq = fromX + (fromY<<4);
12755 int piece = quickBoard[sq], rook;
12756 quickBoard[sq] = 0;
12757 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12758 if(piece == pieceList[1] && fromY == toY) {
12759 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12760 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12761 moveDatabase[movePtr++].piece = Q_WCASTL;
12762 quickBoard[sq] = piece;
12763 piece = quickBoard[from]; quickBoard[from] = 0;
12764 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12765 } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12766 quickBoard[sq] = 0; // remove Rook
12767 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12768 moveDatabase[movePtr++].piece = Q_WCASTL;
12769 quickBoard[sq] = pieceList[1]; // put King
12771 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12774 if(piece == pieceList[2] && fromY == toY) {
12775 if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12776 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12777 moveDatabase[movePtr++].piece = Q_BCASTL;
12778 quickBoard[sq] = piece;
12779 piece = quickBoard[from]; quickBoard[from] = 0;
12780 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12781 } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12782 quickBoard[sq] = 0; // remove Rook
12783 moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12784 moveDatabase[movePtr++].piece = Q_BCASTL;
12785 quickBoard[sq] = pieceList[2]; // put King
12787 moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12790 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12791 quickBoard[(fromY<<4)+toX] = 0;
12792 moveDatabase[movePtr].piece = Q_EP;
12793 moveDatabase[movePtr++].to = (fromY<<4)+toX;
12794 moveDatabase[movePtr].to = sq;
12796 if(promoPiece != pieceType[piece]) {
12797 moveDatabase[movePtr++].piece = Q_PROMO;
12798 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12800 moveDatabase[movePtr].piece = piece;
12801 quickBoard[sq] = piece;
12806 PackGame (Board board)
12808 Move *newSpace = NULL;
12809 moveDatabase[movePtr].piece = 0; // terminate previous game
12810 if(movePtr > dataSize) {
12811 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12812 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12813 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12816 Move *p = moveDatabase, *q = newSpace;
12817 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
12818 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12819 moveDatabase = newSpace;
12820 } else { // calloc failed, we must be out of memory. Too bad...
12821 dataSize = 0; // prevent calloc events for all subsequent games
12822 return 0; // and signal this one isn't cached
12826 MakePieceList(board, counts);
12831 QuickCompare (Board board, int *minCounts, int *maxCounts)
12832 { // compare according to search mode
12834 switch(appData.searchMode)
12836 case 1: // exact position match
12837 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12838 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12839 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12842 case 2: // can have extra material on empty squares
12843 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12844 if(board[r][f] == EmptySquare) continue;
12845 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12848 case 3: // material with exact Pawn structure
12849 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12850 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12851 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12852 } // fall through to material comparison
12853 case 4: // exact material
12854 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12856 case 6: // material range with given imbalance
12857 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12858 // fall through to range comparison
12859 case 5: // material range
12860 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12866 QuickScan (Board board, Move *move)
12867 { // reconstruct game,and compare all positions in it
12868 int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12870 int piece = move->piece;
12871 int to = move->to, from = pieceList[piece];
12872 if(found < 0) { // if already found just scan to game end for final piece count
12873 if(QuickCompare(soughtBoard, minSought, maxSought) ||
12874 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12875 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12876 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12878 static int lastCounts[EmptySquare+1];
12880 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12881 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12882 } else stretch = 0;
12883 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12884 if(found >= 0 && !appData.minPieces) return found;
12886 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12887 if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12888 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12889 piece = (++move)->piece;
12890 from = pieceList[piece];
12891 counts[pieceType[piece]]--;
12892 pieceType[piece] = (ChessSquare) move->to;
12893 counts[move->to]++;
12894 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12895 counts[pieceType[quickBoard[to]]]--;
12896 quickBoard[to] = 0; total--;
12899 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12900 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12901 from = pieceList[piece]; // so this must be King
12902 quickBoard[from] = 0;
12903 pieceList[piece] = to;
12904 from = pieceList[(++move)->piece]; // for FRC this has to be done here
12905 quickBoard[from] = 0; // rook
12906 quickBoard[to] = piece;
12907 to = move->to; piece = move->piece;
12911 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12912 if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12913 quickBoard[from] = 0;
12915 quickBoard[to] = piece;
12916 pieceList[piece] = to;
12926 flipSearch = FALSE;
12927 CopyBoard(soughtBoard, boards[currentMove]);
12928 soughtTotal = MakePieceList(soughtBoard, maxSought);
12929 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12930 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12931 CopyBoard(reverseBoard, boards[currentMove]);
12932 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12933 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12934 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12935 reverseBoard[r][f] = piece;
12937 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12938 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12939 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12940 || (boards[currentMove][CASTLING][2] == NoRights ||
12941 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12942 && (boards[currentMove][CASTLING][5] == NoRights ||
12943 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12946 CopyBoard(flipBoard, soughtBoard);
12947 CopyBoard(rotateBoard, reverseBoard);
12948 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12949 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
12950 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12953 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12954 if(appData.searchMode >= 5) {
12955 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12956 MakePieceList(soughtBoard, minSought);
12957 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12959 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12960 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12963 GameInfo dummyInfo;
12964 static int creatingBook;
12967 GameContainsPosition (FILE *f, ListGame *lg)
12969 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12970 int fromX, fromY, toX, toY;
12972 static int initDone=FALSE;
12974 // weed out games based on numerical tag comparison
12975 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12976 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12977 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12978 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12980 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12983 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12984 else CopyBoard(boards[scratch], initialPosition); // default start position
12987 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12988 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12991 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12992 fseek(f, lg->offset, 0);
12995 yyboardindex = scratch;
12996 quickFlag = plyNr+1;
13001 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
13007 if(plyNr) return -1; // after we have seen moves, this is for new game
13010 case AmbiguousMove: // we cannot reconstruct the game beyond these two
13011 case ImpossibleMove:
13012 case WhiteWins: // game ends here with these four
13015 case GameUnfinished:
13019 if(appData.testLegality) return -1;
13020 case WhiteCapturesEnPassant:
13021 case BlackCapturesEnPassant:
13022 case WhitePromotion:
13023 case BlackPromotion:
13024 case WhiteNonPromotion:
13025 case BlackNonPromotion:
13028 case WhiteKingSideCastle:
13029 case WhiteQueenSideCastle:
13030 case BlackKingSideCastle:
13031 case BlackQueenSideCastle:
13032 case WhiteKingSideCastleWild:
13033 case WhiteQueenSideCastleWild:
13034 case BlackKingSideCastleWild:
13035 case BlackQueenSideCastleWild:
13036 case WhiteHSideCastleFR:
13037 case WhiteASideCastleFR:
13038 case BlackHSideCastleFR:
13039 case BlackASideCastleFR:
13040 fromX = currentMoveString[0] - AAA;
13041 fromY = currentMoveString[1] - ONE;
13042 toX = currentMoveString[2] - AAA;
13043 toY = currentMoveString[3] - ONE;
13044 promoChar = currentMoveString[4];
13048 fromX = next == WhiteDrop ?
13049 (int) CharToPiece(ToUpper(currentMoveString[0])) :
13050 (int) CharToPiece(ToLower(currentMoveString[0]));
13052 toX = currentMoveString[2] - AAA;
13053 toY = currentMoveString[3] - ONE;
13057 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
13059 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
13060 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13061 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
13062 if(appData.findMirror) {
13063 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
13064 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
13069 /* Load the nth game from open file f */
13071 LoadGame (FILE *f, int gameNumber, char *title, int useList)
13075 int gn = gameNumber;
13076 ListGame *lg = NULL;
13077 int numPGNTags = 0, i;
13079 GameMode oldGameMode;
13080 VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13081 char oldName[MSG_SIZ];
13083 safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13085 if (appData.debugMode)
13086 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13088 if (gameMode == Training )
13089 SetTrainingModeOff();
13091 oldGameMode = gameMode;
13092 if (gameMode != BeginningOfGame) {
13093 Reset(FALSE, TRUE);
13095 killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13098 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13099 fclose(lastLoadGameFP);
13103 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13106 fseek(f, lg->offset, 0);
13107 GameListHighlight(gameNumber);
13108 pos = lg->position;
13112 if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13113 appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13115 DisplayError(_("Game number out of range"), 0);
13120 if (fseek(f, 0, 0) == -1) {
13121 if (f == lastLoadGameFP ?
13122 gameNumber == lastLoadGameNumber + 1 :
13126 DisplayError(_("Can't seek on game file"), 0);
13131 lastLoadGameFP = f;
13132 lastLoadGameNumber = gameNumber;
13133 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13134 lastLoadGameUseList = useList;
13138 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13139 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13140 lg->gameInfo.black);
13142 } else if (*title != NULLCHAR) {
13143 if (gameNumber > 1) {
13144 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13147 DisplayTitle(title);
13151 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13152 gameMode = PlayFromGameFile;
13156 currentMove = forwardMostMove = backwardMostMove = 0;
13157 CopyBoard(boards[0], initialPosition);
13161 * Skip the first gn-1 games in the file.
13162 * Also skip over anything that precedes an identifiable
13163 * start of game marker, to avoid being confused by
13164 * garbage at the start of the file. Currently
13165 * recognized start of game markers are the move number "1",
13166 * the pattern "gnuchess .* game", the pattern
13167 * "^[#;%] [^ ]* game file", and a PGN tag block.
13168 * A game that starts with one of the latter two patterns
13169 * will also have a move number 1, possibly
13170 * following a position diagram.
13171 * 5-4-02: Let's try being more lenient and allowing a game to
13172 * start with an unnumbered move. Does that break anything?
13174 cm = lastLoadGameStart = EndOfFile;
13176 yyboardindex = forwardMostMove;
13177 cm = (ChessMove) Myylex();
13180 if (cmailMsgLoaded) {
13181 nCmailGames = CMAIL_MAX_GAMES - gn;
13184 DisplayError(_("Game not found in file"), 0);
13191 lastLoadGameStart = cm;
13194 case MoveNumberOne:
13195 switch (lastLoadGameStart) {
13200 case MoveNumberOne:
13202 gn--; /* count this game */
13203 lastLoadGameStart = cm;
13212 switch (lastLoadGameStart) {
13215 case MoveNumberOne:
13217 gn--; /* count this game */
13218 lastLoadGameStart = cm;
13221 lastLoadGameStart = cm; /* game counted already */
13229 yyboardindex = forwardMostMove;
13230 cm = (ChessMove) Myylex();
13231 } while (cm == PGNTag || cm == Comment);
13238 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13239 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
13240 != CMAIL_OLD_RESULT) {
13242 cmailResult[ CMAIL_MAX_GAMES
13243 - gn - 1] = CMAIL_OLD_RESULT;
13250 /* Only a NormalMove can be at the start of a game
13251 * without a position diagram. */
13252 if (lastLoadGameStart == EndOfFile ) {
13254 lastLoadGameStart = MoveNumberOne;
13263 if (appData.debugMode)
13264 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13266 for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13268 if (cm == XBoardGame) {
13269 /* Skip any header junk before position diagram and/or move 1 */
13271 yyboardindex = forwardMostMove;
13272 cm = (ChessMove) Myylex();
13274 if (cm == EndOfFile ||
13275 cm == GNUChessGame || cm == XBoardGame) {
13276 /* Empty game; pretend end-of-file and handle later */
13281 if (cm == MoveNumberOne || cm == PositionDiagram ||
13282 cm == PGNTag || cm == Comment)
13285 } else if (cm == GNUChessGame) {
13286 if (gameInfo.event != NULL) {
13287 free(gameInfo.event);
13289 gameInfo.event = StrSave(yy_text);
13292 startedFromSetupPosition = startedFromPositionFile; // [HGM]
13293 while (cm == PGNTag) {
13294 if (appData.debugMode)
13295 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13296 err = ParsePGNTag(yy_text, &gameInfo);
13297 if (!err) numPGNTags++;
13299 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13300 if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13301 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13302 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13303 InitPosition(TRUE);
13304 oldVariant = gameInfo.variant;
13305 if (appData.debugMode)
13306 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13310 if (gameInfo.fen != NULL) {
13311 Board initial_position;
13312 startedFromSetupPosition = TRUE;
13313 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13315 DisplayError(_("Bad FEN position in file"), 0);
13318 CopyBoard(boards[0], initial_position);
13319 if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13320 CopyBoard(initialPosition, initial_position);
13321 if (blackPlaysFirst) {
13322 currentMove = forwardMostMove = backwardMostMove = 1;
13323 CopyBoard(boards[1], initial_position);
13324 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13325 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13326 timeRemaining[0][1] = whiteTimeRemaining;
13327 timeRemaining[1][1] = blackTimeRemaining;
13328 if (commentList[0] != NULL) {
13329 commentList[1] = commentList[0];
13330 commentList[0] = NULL;
13333 currentMove = forwardMostMove = backwardMostMove = 0;
13335 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13337 initialRulePlies = FENrulePlies;
13338 for( i=0; i< nrCastlingRights; i++ )
13339 initialRights[i] = initial_position[CASTLING][i];
13341 yyboardindex = forwardMostMove;
13342 free(gameInfo.fen);
13343 gameInfo.fen = NULL;
13346 yyboardindex = forwardMostMove;
13347 cm = (ChessMove) Myylex();
13349 /* Handle comments interspersed among the tags */
13350 while (cm == Comment) {
13352 if (appData.debugMode)
13353 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13355 AppendComment(currentMove, p, FALSE);
13356 yyboardindex = forwardMostMove;
13357 cm = (ChessMove) Myylex();
13361 /* don't rely on existence of Event tag since if game was
13362 * pasted from clipboard the Event tag may not exist
13364 if (numPGNTags > 0){
13366 if (gameInfo.variant == VariantNormal) {
13367 VariantClass v = StringToVariant(gameInfo.event);
13368 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13369 if(v < VariantShogi) gameInfo.variant = v;
13372 if( appData.autoDisplayTags ) {
13373 tags = PGNTags(&gameInfo);
13374 TagsPopUp(tags, CmailMsg());
13379 /* Make something up, but don't display it now */
13384 if (cm == PositionDiagram) {
13387 Board initial_position;
13389 if (appData.debugMode)
13390 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13392 if (!startedFromSetupPosition) {
13394 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13395 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13406 initial_position[i][j++] = CharToPiece(*p);
13409 while (*p == ' ' || *p == '\t' ||
13410 *p == '\n' || *p == '\r') p++;
13412 if (strncmp(p, "black", strlen("black"))==0)
13413 blackPlaysFirst = TRUE;
13415 blackPlaysFirst = FALSE;
13416 startedFromSetupPosition = TRUE;
13418 CopyBoard(boards[0], initial_position);
13419 if (blackPlaysFirst) {
13420 currentMove = forwardMostMove = backwardMostMove = 1;
13421 CopyBoard(boards[1], initial_position);
13422 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13423 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13424 timeRemaining[0][1] = whiteTimeRemaining;
13425 timeRemaining[1][1] = blackTimeRemaining;
13426 if (commentList[0] != NULL) {
13427 commentList[1] = commentList[0];
13428 commentList[0] = NULL;
13431 currentMove = forwardMostMove = backwardMostMove = 0;
13434 yyboardindex = forwardMostMove;
13435 cm = (ChessMove) Myylex();
13438 if(!creatingBook) {
13439 if (first.pr == NoProc) {
13440 StartChessProgram(&first);
13442 InitChessProgram(&first, FALSE);
13443 if(gameInfo.variant == VariantUnknown && *oldName) {
13444 safeStrCpy(engineVariant, oldName, MSG_SIZ);
13445 gameInfo.variant = v;
13447 SendToProgram("force\n", &first);
13448 if (startedFromSetupPosition) {
13449 SendBoard(&first, forwardMostMove);
13450 if (appData.debugMode) {
13451 fprintf(debugFP, "Load Game\n");
13453 DisplayBothClocks();
13457 /* [HGM] server: flag to write setup moves in broadcast file as one */
13458 loadFlag = appData.suppressLoadMoves;
13460 while (cm == Comment) {
13462 if (appData.debugMode)
13463 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13465 AppendComment(currentMove, p, FALSE);
13466 yyboardindex = forwardMostMove;
13467 cm = (ChessMove) Myylex();
13470 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13471 cm == WhiteWins || cm == BlackWins ||
13472 cm == GameIsDrawn || cm == GameUnfinished) {
13473 DisplayMessage("", _("No moves in game"));
13474 if (cmailMsgLoaded) {
13475 if (appData.debugMode)
13476 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13480 DrawPosition(FALSE, boards[currentMove]);
13481 DisplayBothClocks();
13482 gameMode = EditGame;
13489 // [HGM] PV info: routine tests if comment empty
13490 if (!matchMode && (pausing || appData.timeDelay != 0)) {
13491 DisplayComment(currentMove - 1, commentList[currentMove]);
13493 if (!matchMode && appData.timeDelay != 0)
13494 DrawPosition(FALSE, boards[currentMove]);
13496 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13497 programStats.ok_to_send = 1;
13500 /* if the first token after the PGN tags is a move
13501 * and not move number 1, retrieve it from the parser
13503 if (cm != MoveNumberOne)
13504 LoadGameOneMove(cm);
13506 /* load the remaining moves from the file */
13507 while (LoadGameOneMove(EndOfFile)) {
13508 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13509 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13512 /* rewind to the start of the game */
13513 currentMove = backwardMostMove;
13515 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13517 if (oldGameMode == AnalyzeFile) {
13518 appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13519 AnalyzeFileEvent();
13521 if (oldGameMode == AnalyzeMode) {
13522 AnalyzeFileEvent();
13525 if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13526 long int w, b; // [HGM] adjourn: restore saved clock times
13527 char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13528 if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13529 timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13530 timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13534 if(creatingBook) return TRUE;
13535 if (!matchMode && pos > 0) {
13536 ToNrEvent(pos); // [HGM] no autoplay if selected on position
13538 if (matchMode || appData.timeDelay == 0) {
13540 } else if (appData.timeDelay > 0) {
13541 AutoPlayGameLoop();
13544 if (appData.debugMode)
13545 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13547 loadFlag = 0; /* [HGM] true game starts */
13551 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13553 ReloadPosition (int offset)
13555 int positionNumber = lastLoadPositionNumber + offset;
13556 if (lastLoadPositionFP == NULL) {
13557 DisplayError(_("No position has been loaded yet"), 0);
13560 if (positionNumber <= 0) {
13561 DisplayError(_("Can't back up any further"), 0);
13564 return LoadPosition(lastLoadPositionFP, positionNumber,
13565 lastLoadPositionTitle);
13568 /* Load the nth position from the given file */
13570 LoadPositionFromFile (char *filename, int n, char *title)
13575 if (strcmp(filename, "-") == 0) {
13576 return LoadPosition(stdin, n, "stdin");
13578 f = fopen(filename, "rb");
13580 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13581 DisplayError(buf, errno);
13584 return LoadPosition(f, n, title);
13589 /* Load the nth position from the given open file, and close it */
13591 LoadPosition (FILE *f, int positionNumber, char *title)
13593 char *p, line[MSG_SIZ];
13594 Board initial_position;
13595 int i, j, fenMode, pn;
13597 if (gameMode == Training )
13598 SetTrainingModeOff();
13600 if (gameMode != BeginningOfGame) {
13601 Reset(FALSE, TRUE);
13603 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13604 fclose(lastLoadPositionFP);
13606 if (positionNumber == 0) positionNumber = 1;
13607 lastLoadPositionFP = f;
13608 lastLoadPositionNumber = positionNumber;
13609 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13610 if (first.pr == NoProc && !appData.noChessProgram) {
13611 StartChessProgram(&first);
13612 InitChessProgram(&first, FALSE);
13614 pn = positionNumber;
13615 if (positionNumber < 0) {
13616 /* Negative position number means to seek to that byte offset */
13617 if (fseek(f, -positionNumber, 0) == -1) {
13618 DisplayError(_("Can't seek on position file"), 0);
13623 if (fseek(f, 0, 0) == -1) {
13624 if (f == lastLoadPositionFP ?
13625 positionNumber == lastLoadPositionNumber + 1 :
13626 positionNumber == 1) {
13629 DisplayError(_("Can't seek on position file"), 0);
13634 /* See if this file is FEN or old-style xboard */
13635 if (fgets(line, MSG_SIZ, f) == NULL) {
13636 DisplayError(_("Position not found in file"), 0);
13639 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13640 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13643 if (fenMode || line[0] == '#') pn--;
13645 /* skip positions before number pn */
13646 if (fgets(line, MSG_SIZ, f) == NULL) {
13648 DisplayError(_("Position not found in file"), 0);
13651 if (fenMode || line[0] == '#') pn--;
13657 if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13658 DisplayError(_("Bad FEN position in file"), 0);
13661 if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13662 sscanf(p+4, "%[^;]", bestMove);
13663 } else *bestMove = NULLCHAR;
13664 if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13665 sscanf(p+4, "%[^;]", avoidMove);
13666 } else *avoidMove = NULLCHAR;
13668 (void) fgets(line, MSG_SIZ, f);
13669 (void) fgets(line, MSG_SIZ, f);
13671 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13672 (void) fgets(line, MSG_SIZ, f);
13673 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13676 initial_position[i][j++] = CharToPiece(*p);
13680 blackPlaysFirst = FALSE;
13682 (void) fgets(line, MSG_SIZ, f);
13683 if (strncmp(line, "black", strlen("black"))==0)
13684 blackPlaysFirst = TRUE;
13687 startedFromSetupPosition = TRUE;
13689 CopyBoard(boards[0], initial_position);
13690 if (blackPlaysFirst) {
13691 currentMove = forwardMostMove = backwardMostMove = 1;
13692 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13693 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13694 CopyBoard(boards[1], initial_position);
13695 DisplayMessage("", _("Black to play"));
13697 currentMove = forwardMostMove = backwardMostMove = 0;
13698 DisplayMessage("", _("White to play"));
13700 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13701 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13702 SendToProgram("force\n", &first);
13703 SendBoard(&first, forwardMostMove);
13705 if (appData.debugMode) {
13707 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13708 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13709 fprintf(debugFP, "Load Position\n");
13712 if (positionNumber > 1) {
13713 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13714 DisplayTitle(line);
13716 DisplayTitle(title);
13718 gameMode = EditGame;
13721 timeRemaining[0][1] = whiteTimeRemaining;
13722 timeRemaining[1][1] = blackTimeRemaining;
13723 DrawPosition(FALSE, boards[currentMove]);
13730 CopyPlayerNameIntoFileName (char **dest, char *src)
13732 while (*src != NULLCHAR && *src != ',') {
13737 *(*dest)++ = *src++;
13743 DefaultFileName (char *ext)
13745 static char def[MSG_SIZ];
13748 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13750 CopyPlayerNameIntoFileName(&p, gameInfo.white);
13752 CopyPlayerNameIntoFileName(&p, gameInfo.black);
13754 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13761 /* Save the current game to the given file */
13763 SaveGameToFile (char *filename, int append)
13767 int result, i, t,tot=0;
13769 if (strcmp(filename, "-") == 0) {
13770 return SaveGame(stdout, 0, NULL);
13772 for(i=0; i<10; i++) { // upto 10 tries
13773 f = fopen(filename, append ? "a" : "w");
13774 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13775 if(f || errno != 13) break;
13776 DoSleep(t = 5 + random()%11); // wait 5-15 msec
13780 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13781 DisplayError(buf, errno);
13784 safeStrCpy(buf, lastMsg, MSG_SIZ);
13785 DisplayMessage(_("Waiting for access to save file"), "");
13786 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13787 DisplayMessage(_("Saving game"), "");
13788 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
13789 result = SaveGame(f, 0, NULL);
13790 DisplayMessage(buf, "");
13797 SavePart (char *str)
13799 static char buf[MSG_SIZ];
13802 p = strchr(str, ' ');
13803 if (p == NULL) return str;
13804 strncpy(buf, str, p - str);
13805 buf[p - str] = NULLCHAR;
13809 #define PGN_MAX_LINE 75
13811 #define PGN_SIDE_WHITE 0
13812 #define PGN_SIDE_BLACK 1
13815 FindFirstMoveOutOfBook (int side)
13819 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13820 int index = backwardMostMove;
13821 int has_book_hit = 0;
13823 if( (index % 2) != side ) {
13827 while( index < forwardMostMove ) {
13828 /* Check to see if engine is in book */
13829 int depth = pvInfoList[index].depth;
13830 int score = pvInfoList[index].score;
13836 else if( score == 0 && depth == 63 ) {
13837 in_book = 1; /* Zappa */
13839 else if( score == 2 && depth == 99 ) {
13840 in_book = 1; /* Abrok */
13843 has_book_hit += in_book;
13859 GetOutOfBookInfo (char * buf)
13863 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13865 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13866 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13870 if( oob[0] >= 0 || oob[1] >= 0 ) {
13871 for( i=0; i<2; i++ ) {
13875 if( i > 0 && oob[0] >= 0 ) {
13876 strcat( buf, " " );
13879 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13880 sprintf( buf+strlen(buf), "%s%.2f",
13881 pvInfoList[idx].score >= 0 ? "+" : "",
13882 pvInfoList[idx].score / 100.0 );
13888 /* Save game in PGN style */
13890 SaveGamePGN2 (FILE *f)
13892 int i, offset, linelen, newblock;
13895 int movelen, numlen, blank;
13896 char move_buffer[100]; /* [AS] Buffer for move+PV info */
13898 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13900 PrintPGNTags(f, &gameInfo);
13902 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13904 if (backwardMostMove > 0 || startedFromSetupPosition) {
13905 char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13906 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13907 fprintf(f, "\n{--------------\n");
13908 PrintPosition(f, backwardMostMove);
13909 fprintf(f, "--------------}\n");
13913 /* [AS] Out of book annotation */
13914 if( appData.saveOutOfBookInfo ) {
13917 GetOutOfBookInfo( buf );
13919 if( buf[0] != '\0' ) {
13920 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13927 i = backwardMostMove;
13931 while (i < forwardMostMove) {
13932 /* Print comments preceding this move */
13933 if (commentList[i] != NULL) {
13934 if (linelen > 0) fprintf(f, "\n");
13935 fprintf(f, "%s", commentList[i]);
13940 /* Format move number */
13942 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13945 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13947 numtext[0] = NULLCHAR;
13949 numlen = strlen(numtext);
13952 /* Print move number */
13953 blank = linelen > 0 && numlen > 0;
13954 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13963 fprintf(f, "%s", numtext);
13967 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13968 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13971 blank = linelen > 0 && movelen > 0;
13972 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13981 fprintf(f, "%s", move_buffer);
13982 linelen += movelen;
13984 /* [AS] Add PV info if present */
13985 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13986 /* [HGM] add time */
13987 char buf[MSG_SIZ]; int seconds;
13989 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13995 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13998 seconds = (seconds + 4)/10; // round to full seconds
14000 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
14002 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
14005 if(appData.cumulativeTimePGN) {
14006 snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
14009 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
14010 pvInfoList[i].score >= 0 ? "+" : "",
14011 pvInfoList[i].score / 100.0,
14012 pvInfoList[i].depth,
14015 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
14017 /* Print score/depth */
14018 blank = linelen > 0 && movelen > 0;
14019 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14028 fprintf(f, "%s", move_buffer);
14029 linelen += movelen;
14035 /* Start a new line */
14036 if (linelen > 0) fprintf(f, "\n");
14038 /* Print comments after last move */
14039 if (commentList[i] != NULL) {
14040 fprintf(f, "%s\n", commentList[i]);
14044 if (gameInfo.resultDetails != NULL &&
14045 gameInfo.resultDetails[0] != NULLCHAR) {
14046 char buf[MSG_SIZ], *p = gameInfo.resultDetails;
14047 if(gameInfo.result == GameUnfinished && appData.clockMode &&
14048 (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
14049 snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
14050 fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
14052 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14056 /* Save game in PGN style and close the file */
14058 SaveGamePGN (FILE *f)
14062 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14066 /* Save game in old style and close the file */
14068 SaveGameOldStyle (FILE *f)
14073 tm = time((time_t *) NULL);
14075 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
14078 if (backwardMostMove > 0 || startedFromSetupPosition) {
14079 fprintf(f, "\n[--------------\n");
14080 PrintPosition(f, backwardMostMove);
14081 fprintf(f, "--------------]\n");
14086 i = backwardMostMove;
14087 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14089 while (i < forwardMostMove) {
14090 if (commentList[i] != NULL) {
14091 fprintf(f, "[%s]\n", commentList[i]);
14094 if ((i % 2) == 1) {
14095 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
14098 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
14100 if (commentList[i] != NULL) {
14104 if (i >= forwardMostMove) {
14108 fprintf(f, "%s\n", parseList[i]);
14113 if (commentList[i] != NULL) {
14114 fprintf(f, "[%s]\n", commentList[i]);
14117 /* This isn't really the old style, but it's close enough */
14118 if (gameInfo.resultDetails != NULL &&
14119 gameInfo.resultDetails[0] != NULLCHAR) {
14120 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14121 gameInfo.resultDetails);
14123 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14130 /* Save the current game to open file f and close the file */
14132 SaveGame (FILE *f, int dummy, char *dummy2)
14134 if (gameMode == EditPosition) EditPositionDone(TRUE);
14135 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14136 if (appData.oldSaveStyle)
14137 return SaveGameOldStyle(f);
14139 return SaveGamePGN(f);
14142 /* Save the current position to the given file */
14144 SavePositionToFile (char *filename)
14149 if (strcmp(filename, "-") == 0) {
14150 return SavePosition(stdout, 0, NULL);
14152 f = fopen(filename, "a");
14154 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14155 DisplayError(buf, errno);
14158 safeStrCpy(buf, lastMsg, MSG_SIZ);
14159 DisplayMessage(_("Waiting for access to save file"), "");
14160 flock(fileno(f), LOCK_EX); // [HGM] lock
14161 DisplayMessage(_("Saving position"), "");
14162 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
14163 SavePosition(f, 0, NULL);
14164 DisplayMessage(buf, "");
14170 /* Save the current position to the given open file and close the file */
14172 SavePosition (FILE *f, int dummy, char *dummy2)
14177 if (gameMode == EditPosition) EditPositionDone(TRUE);
14178 if (appData.oldSaveStyle) {
14179 tm = time((time_t *) NULL);
14181 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14183 fprintf(f, "[--------------\n");
14184 PrintPosition(f, currentMove);
14185 fprintf(f, "--------------]\n");
14187 fen = PositionToFEN(currentMove, NULL, 1);
14188 fprintf(f, "%s\n", fen);
14196 ReloadCmailMsgEvent (int unregister)
14199 static char *inFilename = NULL;
14200 static char *outFilename;
14202 struct stat inbuf, outbuf;
14205 /* Any registered moves are unregistered if unregister is set, */
14206 /* i.e. invoked by the signal handler */
14208 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14209 cmailMoveRegistered[i] = FALSE;
14210 if (cmailCommentList[i] != NULL) {
14211 free(cmailCommentList[i]);
14212 cmailCommentList[i] = NULL;
14215 nCmailMovesRegistered = 0;
14218 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14219 cmailResult[i] = CMAIL_NOT_RESULT;
14223 if (inFilename == NULL) {
14224 /* Because the filenames are static they only get malloced once */
14225 /* and they never get freed */
14226 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14227 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14229 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14230 sprintf(outFilename, "%s.out", appData.cmailGameName);
14233 status = stat(outFilename, &outbuf);
14235 cmailMailedMove = FALSE;
14237 status = stat(inFilename, &inbuf);
14238 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14241 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14242 counts the games, notes how each one terminated, etc.
14244 It would be nice to remove this kludge and instead gather all
14245 the information while building the game list. (And to keep it
14246 in the game list nodes instead of having a bunch of fixed-size
14247 parallel arrays.) Note this will require getting each game's
14248 termination from the PGN tags, as the game list builder does
14249 not process the game moves. --mann
14251 cmailMsgLoaded = TRUE;
14252 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14254 /* Load first game in the file or popup game menu */
14255 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14257 #endif /* !WIN32 */
14265 char string[MSG_SIZ];
14267 if ( cmailMailedMove
14268 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14269 return TRUE; /* Allow free viewing */
14272 /* Unregister move to ensure that we don't leave RegisterMove */
14273 /* with the move registered when the conditions for registering no */
14275 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14276 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14277 nCmailMovesRegistered --;
14279 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14281 free(cmailCommentList[lastLoadGameNumber - 1]);
14282 cmailCommentList[lastLoadGameNumber - 1] = NULL;
14286 if (cmailOldMove == -1) {
14287 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14291 if (currentMove > cmailOldMove + 1) {
14292 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14296 if (currentMove < cmailOldMove) {
14297 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14301 if (forwardMostMove > currentMove) {
14302 /* Silently truncate extra moves */
14306 if ( (currentMove == cmailOldMove + 1)
14307 || ( (currentMove == cmailOldMove)
14308 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14309 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14310 if (gameInfo.result != GameUnfinished) {
14311 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14314 if (commentList[currentMove] != NULL) {
14315 cmailCommentList[lastLoadGameNumber - 1]
14316 = StrSave(commentList[currentMove]);
14318 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14320 if (appData.debugMode)
14321 fprintf(debugFP, "Saving %s for game %d\n",
14322 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14324 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14326 f = fopen(string, "w");
14327 if (appData.oldSaveStyle) {
14328 SaveGameOldStyle(f); /* also closes the file */
14330 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14331 f = fopen(string, "w");
14332 SavePosition(f, 0, NULL); /* also closes the file */
14334 fprintf(f, "{--------------\n");
14335 PrintPosition(f, currentMove);
14336 fprintf(f, "--------------}\n\n");
14338 SaveGame(f, 0, NULL); /* also closes the file*/
14341 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14342 nCmailMovesRegistered ++;
14343 } else if (nCmailGames == 1) {
14344 DisplayError(_("You have not made a move yet"), 0);
14355 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14356 FILE *commandOutput;
14357 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14358 int nBytes = 0; /* Suppress warnings on uninitialized variables */
14364 if (! cmailMsgLoaded) {
14365 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14369 if (nCmailGames == nCmailResults) {
14370 DisplayError(_("No unfinished games"), 0);
14374 #if CMAIL_PROHIBIT_REMAIL
14375 if (cmailMailedMove) {
14376 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);
14377 DisplayError(msg, 0);
14382 if (! (cmailMailedMove || RegisterMove())) return;
14384 if ( cmailMailedMove
14385 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14386 snprintf(string, MSG_SIZ, partCommandString,
14387 appData.debugMode ? " -v" : "", appData.cmailGameName);
14388 commandOutput = popen(string, "r");
14390 if (commandOutput == NULL) {
14391 DisplayError(_("Failed to invoke cmail"), 0);
14393 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14394 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14396 if (nBuffers > 1) {
14397 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14398 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14399 nBytes = MSG_SIZ - 1;
14401 (void) memcpy(msg, buffer, nBytes);
14403 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14405 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14406 cmailMailedMove = TRUE; /* Prevent >1 moves */
14409 for (i = 0; i < nCmailGames; i ++) {
14410 if (cmailResult[i] == CMAIL_NOT_RESULT) {
14415 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14417 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14419 appData.cmailGameName,
14421 LoadGameFromFile(buffer, 1, buffer, FALSE);
14422 cmailMsgLoaded = FALSE;
14426 DisplayInformation(msg);
14427 pclose(commandOutput);
14430 if ((*cmailMsg) != '\0') {
14431 DisplayInformation(cmailMsg);
14436 #endif /* !WIN32 */
14445 int prependComma = 0;
14447 char string[MSG_SIZ]; /* Space for game-list */
14450 if (!cmailMsgLoaded) return "";
14452 if (cmailMailedMove) {
14453 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14455 /* Create a list of games left */
14456 snprintf(string, MSG_SIZ, "[");
14457 for (i = 0; i < nCmailGames; i ++) {
14458 if (! ( cmailMoveRegistered[i]
14459 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14460 if (prependComma) {
14461 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14463 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14467 strcat(string, number);
14470 strcat(string, "]");
14472 if (nCmailMovesRegistered + nCmailResults == 0) {
14473 switch (nCmailGames) {
14475 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14479 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14483 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14488 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14490 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14495 if (nCmailResults == nCmailGames) {
14496 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14498 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14503 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14515 if (gameMode == Training)
14516 SetTrainingModeOff();
14519 cmailMsgLoaded = FALSE;
14520 if (appData.icsActive) {
14521 SendToICS(ics_prefix);
14522 SendToICS("refresh\n");
14527 ExitEvent (int status)
14531 /* Give up on clean exit */
14535 /* Keep trying for clean exit */
14539 if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14540 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14542 if (telnetISR != NULL) {
14543 RemoveInputSource(telnetISR);
14545 if (icsPR != NoProc) {
14546 DestroyChildProcess(icsPR, TRUE);
14549 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14550 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14552 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14553 /* make sure this other one finishes before killing it! */
14554 if(endingGame) { int count = 0;
14555 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14556 while(endingGame && count++ < 10) DoSleep(1);
14557 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14560 /* Kill off chess programs */
14561 if (first.pr != NoProc) {
14564 DoSleep( appData.delayBeforeQuit );
14565 SendToProgram("quit\n", &first);
14566 DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14568 if (second.pr != NoProc) {
14569 DoSleep( appData.delayBeforeQuit );
14570 SendToProgram("quit\n", &second);
14571 DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14573 if (first.isr != NULL) {
14574 RemoveInputSource(first.isr);
14576 if (second.isr != NULL) {
14577 RemoveInputSource(second.isr);
14580 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14581 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14583 ShutDownFrontEnd();
14588 PauseEngine (ChessProgramState *cps)
14590 SendToProgram("pause\n", cps);
14595 UnPauseEngine (ChessProgramState *cps)
14597 SendToProgram("resume\n", cps);
14604 if (appData.debugMode)
14605 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14609 if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14611 if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14612 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14613 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14615 if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14616 HandleMachineMove(stashedInputMove, stalledEngine);
14617 stalledEngine = NULL;
14620 if (gameMode == MachinePlaysWhite ||
14621 gameMode == TwoMachinesPlay ||
14622 gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14623 if(first.pause) UnPauseEngine(&first);
14624 else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14625 if(second.pause) UnPauseEngine(&second);
14626 else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14629 DisplayBothClocks();
14631 if (gameMode == PlayFromGameFile) {
14632 if (appData.timeDelay >= 0)
14633 AutoPlayGameLoop();
14634 } else if (gameMode == IcsExamining && pauseExamInvalid) {
14635 Reset(FALSE, TRUE);
14636 SendToICS(ics_prefix);
14637 SendToICS("refresh\n");
14638 } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14639 ForwardInner(forwardMostMove);
14641 pauseExamInvalid = FALSE;
14643 switch (gameMode) {
14647 pauseExamForwardMostMove = forwardMostMove;
14648 pauseExamInvalid = FALSE;
14651 case IcsPlayingWhite:
14652 case IcsPlayingBlack:
14656 case PlayFromGameFile:
14657 (void) StopLoadGameTimer();
14661 case BeginningOfGame:
14662 if (appData.icsActive) return;
14663 /* else fall through */
14664 case MachinePlaysWhite:
14665 case MachinePlaysBlack:
14666 case TwoMachinesPlay:
14667 if (forwardMostMove == 0)
14668 return; /* don't pause if no one has moved */
14669 if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14670 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14671 if(onMove->pause) { // thinking engine can be paused
14672 PauseEngine(onMove); // do it
14673 if(onMove->other->pause) // pondering opponent can always be paused immediately
14674 PauseEngine(onMove->other);
14676 SendToProgram("easy\n", onMove->other);
14678 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14679 } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14681 PauseEngine(&first);
14683 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14684 } else { // human on move, pause pondering by either method
14686 PauseEngine(&first);
14687 else if(appData.ponderNextMove)
14688 SendToProgram("easy\n", &first);
14691 // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14701 EditCommentEvent ()
14703 char title[MSG_SIZ];
14705 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14706 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14708 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14709 WhiteOnMove(currentMove - 1) ? " " : ".. ",
14710 parseList[currentMove - 1]);
14713 EditCommentPopUp(currentMove, title, commentList[currentMove]);
14720 char *tags = PGNTags(&gameInfo);
14722 EditTagsPopUp(tags, NULL);
14729 if(second.analyzing) {
14730 SendToProgram("exit\n", &second);
14731 second.analyzing = FALSE;
14733 if (second.pr == NoProc) StartChessProgram(&second);
14734 InitChessProgram(&second, FALSE);
14735 FeedMovesToProgram(&second, currentMove);
14737 SendToProgram("analyze\n", &second);
14738 second.analyzing = TRUE;
14742 /* Toggle ShowThinking */
14744 ToggleShowThinking()
14746 appData.showThinking = !appData.showThinking;
14747 ShowThinkingEvent();
14751 AnalyzeModeEvent ()
14755 if (!first.analysisSupport) {
14756 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14757 DisplayError(buf, 0);
14760 /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14761 if (appData.icsActive) {
14762 if (gameMode != IcsObserving) {
14763 snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14764 DisplayError(buf, 0);
14766 if (appData.icsEngineAnalyze) {
14767 if (appData.debugMode)
14768 fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14774 /* if enable, user wants to disable icsEngineAnalyze */
14775 if (appData.icsEngineAnalyze) {
14780 appData.icsEngineAnalyze = TRUE;
14781 if (appData.debugMode)
14782 fprintf(debugFP, "ICS engine analyze starting... \n");
14785 if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14786 if (appData.noChessProgram || gameMode == AnalyzeMode)
14789 if (gameMode != AnalyzeFile) {
14790 if (!appData.icsEngineAnalyze) {
14792 if (gameMode != EditGame) return 0;
14794 if (!appData.showThinking) ToggleShowThinking();
14795 ResurrectChessProgram();
14796 SendToProgram("analyze\n", &first);
14797 first.analyzing = TRUE;
14798 /*first.maybeThinking = TRUE;*/
14799 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14800 EngineOutputPopUp();
14802 if (!appData.icsEngineAnalyze) {
14803 gameMode = AnalyzeMode;
14804 ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14810 StartAnalysisClock();
14811 GetTimeMark(&lastNodeCountTime);
14817 AnalyzeFileEvent ()
14819 if (appData.noChessProgram || gameMode == AnalyzeFile)
14822 if (!first.analysisSupport) {
14824 snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14825 DisplayError(buf, 0);
14829 if (gameMode != AnalyzeMode) {
14830 keepInfo = 1; // mere annotating should not alter PGN tags
14833 if (gameMode != EditGame) return;
14834 if (!appData.showThinking) ToggleShowThinking();
14835 ResurrectChessProgram();
14836 SendToProgram("analyze\n", &first);
14837 first.analyzing = TRUE;
14838 /*first.maybeThinking = TRUE;*/
14839 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14840 EngineOutputPopUp();
14842 gameMode = AnalyzeFile;
14846 StartAnalysisClock();
14847 GetTimeMark(&lastNodeCountTime);
14849 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14850 AnalysisPeriodicEvent(1);
14854 MachineWhiteEvent ()
14857 char *bookHit = NULL;
14859 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14863 if (gameMode == PlayFromGameFile ||
14864 gameMode == TwoMachinesPlay ||
14865 gameMode == Training ||
14866 gameMode == AnalyzeMode ||
14867 gameMode == EndOfGame)
14870 if (gameMode == EditPosition)
14871 EditPositionDone(TRUE);
14873 if (!WhiteOnMove(currentMove)) {
14874 DisplayError(_("It is not White's turn"), 0);
14878 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14881 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14882 gameMode == AnalyzeFile)
14885 ResurrectChessProgram(); /* in case it isn't running */
14886 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14887 gameMode = MachinePlaysWhite;
14890 gameMode = MachinePlaysWhite;
14894 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14896 if (first.sendName) {
14897 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14898 SendToProgram(buf, &first);
14900 if (first.sendTime) {
14901 if (first.useColors) {
14902 SendToProgram("black\n", &first); /*gnu kludge*/
14904 SendTimeRemaining(&first, TRUE);
14906 if (first.useColors) {
14907 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14909 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14910 SetMachineThinkingEnables();
14911 first.maybeThinking = TRUE;
14915 if (appData.autoFlipView && !flipView) {
14916 flipView = !flipView;
14917 DrawPosition(FALSE, NULL);
14918 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14921 if(bookHit) { // [HGM] book: simulate book reply
14922 static char bookMove[MSG_SIZ]; // a bit generous?
14924 programStats.nodes = programStats.depth = programStats.time =
14925 programStats.score = programStats.got_only_move = 0;
14926 sprintf(programStats.movelist, "%s (xbook)", bookHit);
14928 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14929 strcat(bookMove, bookHit);
14930 savedMessage = bookMove; // args for deferred call
14931 savedState = &first;
14932 ScheduleDelayedEvent(DeferredBookMove, 1);
14937 MachineBlackEvent ()
14940 char *bookHit = NULL;
14942 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14946 if (gameMode == PlayFromGameFile ||
14947 gameMode == TwoMachinesPlay ||
14948 gameMode == Training ||
14949 gameMode == AnalyzeMode ||
14950 gameMode == EndOfGame)
14953 if (gameMode == EditPosition)
14954 EditPositionDone(TRUE);
14956 if (WhiteOnMove(currentMove)) {
14957 DisplayError(_("It is not Black's turn"), 0);
14961 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14964 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14965 gameMode == AnalyzeFile)
14968 ResurrectChessProgram(); /* in case it isn't running */
14969 gameMode = MachinePlaysBlack;
14973 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14975 if (first.sendName) {
14976 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14977 SendToProgram(buf, &first);
14979 if (first.sendTime) {
14980 if (first.useColors) {
14981 SendToProgram("white\n", &first); /*gnu kludge*/
14983 SendTimeRemaining(&first, FALSE);
14985 if (first.useColors) {
14986 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14988 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14989 SetMachineThinkingEnables();
14990 first.maybeThinking = TRUE;
14993 if (appData.autoFlipView && flipView) {
14994 flipView = !flipView;
14995 DrawPosition(FALSE, NULL);
14996 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
14998 if(bookHit) { // [HGM] book: simulate book reply
14999 static char bookMove[MSG_SIZ]; // a bit generous?
15001 programStats.nodes = programStats.depth = programStats.time =
15002 programStats.score = programStats.got_only_move = 0;
15003 sprintf(programStats.movelist, "%s (xbook)", bookHit);
15005 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15006 strcat(bookMove, bookHit);
15007 savedMessage = bookMove; // args for deferred call
15008 savedState = &first;
15009 ScheduleDelayedEvent(DeferredBookMove, 1);
15015 DisplayTwoMachinesTitle ()
15018 if (appData.matchGames > 0) {
15019 if(appData.tourneyFile[0]) {
15020 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
15021 gameInfo.white, _("vs."), gameInfo.black,
15022 nextGame+1, appData.matchGames+1,
15023 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
15025 if (first.twoMachinesColor[0] == 'w') {
15026 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15027 gameInfo.white, _("vs."), gameInfo.black,
15028 first.matchWins, second.matchWins,
15029 matchGame - 1 - (first.matchWins + second.matchWins));
15031 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15032 gameInfo.white, _("vs."), gameInfo.black,
15033 second.matchWins, first.matchWins,
15034 matchGame - 1 - (first.matchWins + second.matchWins));
15037 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15043 SettingsMenuIfReady ()
15045 if (second.lastPing != second.lastPong) {
15046 DisplayMessage("", _("Waiting for second chess program"));
15047 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
15051 DisplayMessage("", "");
15052 SettingsPopUp(&second);
15056 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
15059 if (cps->pr == NoProc) {
15060 StartChessProgram(cps);
15061 if (cps->protocolVersion == 1) {
15063 ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
15065 /* kludge: allow timeout for initial "feature" command */
15066 if(retry != TwoMachinesEventIfReady) FreezeUI();
15067 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
15068 DisplayMessage("", buf);
15069 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
15077 TwoMachinesEvent P((void))
15079 int i, move = forwardMostMove;
15081 ChessProgramState *onmove;
15082 char *bookHit = NULL;
15083 static int stalling = 0;
15087 if (appData.noChessProgram) return;
15089 switch (gameMode) {
15090 case TwoMachinesPlay:
15092 case MachinePlaysWhite:
15093 case MachinePlaysBlack:
15094 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15095 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15099 case BeginningOfGame:
15100 case PlayFromGameFile:
15103 if (gameMode != EditGame) return;
15106 EditPositionDone(TRUE);
15117 // forwardMostMove = currentMove;
15118 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15119 startingEngine = TRUE;
15121 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15123 if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15124 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15125 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15129 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15131 if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15132 gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15133 startingEngine = matchMode = FALSE;
15134 DisplayError("second engine does not play this", 0);
15135 gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15136 EditGameEvent(); // switch back to EditGame mode
15141 InitChessProgram(&second, FALSE); // unbalances ping of second engine
15142 SendToProgram("force\n", &second);
15144 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15148 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15149 if(appData.matchPause>10000 || appData.matchPause<10)
15150 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15151 wait = SubtractTimeMarks(&now, &pauseStart);
15152 if(wait < appData.matchPause) {
15153 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15156 // we are now committed to starting the game
15158 DisplayMessage("", "");
15160 if (startedFromSetupPosition) {
15161 SendBoard(&second, backwardMostMove);
15162 if (appData.debugMode) {
15163 fprintf(debugFP, "Two Machines\n");
15166 for (i = backwardMostMove; i < forwardMostMove; i++) {
15167 SendMoveToProgram(i, &second);
15171 gameMode = TwoMachinesPlay;
15172 pausing = startingEngine = FALSE;
15173 ModeHighlight(); // [HGM] logo: this triggers display update of logos
15175 DisplayTwoMachinesTitle();
15177 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15182 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15183 SendToProgram(first.computerString, &first);
15184 if (first.sendName) {
15185 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15186 SendToProgram(buf, &first);
15189 SendToProgram(second.computerString, &second);
15190 if (second.sendName) {
15191 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15192 SendToProgram(buf, &second);
15196 if (!first.sendTime || !second.sendTime || move == 0) { // [HGM] first engine changed sides from Reset, so recalc time odds
15198 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15199 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15201 if (onmove->sendTime) {
15202 if (onmove->useColors) {
15203 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15205 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15207 if (onmove->useColors) {
15208 SendToProgram(onmove->twoMachinesColor, onmove);
15210 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15211 // SendToProgram("go\n", onmove);
15212 onmove->maybeThinking = TRUE;
15213 SetMachineThinkingEnables();
15217 if(bookHit) { // [HGM] book: simulate book reply
15218 static char bookMove[MSG_SIZ]; // a bit generous?
15220 programStats.nodes = programStats.depth = programStats.time =
15221 programStats.score = programStats.got_only_move = 0;
15222 sprintf(programStats.movelist, "%s (xbook)", bookHit);
15224 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15225 strcat(bookMove, bookHit);
15226 savedMessage = bookMove; // args for deferred call
15227 savedState = onmove;
15228 ScheduleDelayedEvent(DeferredBookMove, 1);
15235 if (gameMode == Training) {
15236 SetTrainingModeOff();
15237 gameMode = PlayFromGameFile;
15238 DisplayMessage("", _("Training mode off"));
15240 gameMode = Training;
15241 animateTraining = appData.animate;
15243 /* make sure we are not already at the end of the game */
15244 if (currentMove < forwardMostMove) {
15245 SetTrainingModeOn();
15246 DisplayMessage("", _("Training mode on"));
15248 gameMode = PlayFromGameFile;
15249 DisplayError(_("Already at end of game"), 0);
15258 if (!appData.icsActive) return;
15259 switch (gameMode) {
15260 case IcsPlayingWhite:
15261 case IcsPlayingBlack:
15264 case BeginningOfGame:
15272 EditPositionDone(TRUE);
15285 gameMode = IcsIdle;
15295 switch (gameMode) {
15297 SetTrainingModeOff();
15299 case MachinePlaysWhite:
15300 case MachinePlaysBlack:
15301 case BeginningOfGame:
15302 SendToProgram("force\n", &first);
15303 if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15304 if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15306 abortEngineThink = TRUE;
15307 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15308 SendToProgram(buf, &first);
15309 DisplayMessage("Aborting engine think", "");
15313 SetUserThinkingEnables();
15315 case PlayFromGameFile:
15316 (void) StopLoadGameTimer();
15317 if (gameFileFP != NULL) {
15322 EditPositionDone(TRUE);
15327 SendToProgram("force\n", &first);
15329 case TwoMachinesPlay:
15330 GameEnds(EndOfFile, NULL, GE_PLAYER);
15331 ResurrectChessProgram();
15332 SetUserThinkingEnables();
15335 ResurrectChessProgram();
15337 case IcsPlayingBlack:
15338 case IcsPlayingWhite:
15339 DisplayError(_("Warning: You are still playing a game"), 0);
15342 DisplayError(_("Warning: You are still observing a game"), 0);
15345 DisplayError(_("Warning: You are still examining a game"), 0);
15356 first.offeredDraw = second.offeredDraw = 0;
15358 if (gameMode == PlayFromGameFile) {
15359 whiteTimeRemaining = timeRemaining[0][currentMove];
15360 blackTimeRemaining = timeRemaining[1][currentMove];
15364 if (gameMode == MachinePlaysWhite ||
15365 gameMode == MachinePlaysBlack ||
15366 gameMode == TwoMachinesPlay ||
15367 gameMode == EndOfGame) {
15368 i = forwardMostMove;
15369 while (i > currentMove) {
15370 SendToProgram("undo\n", &first);
15373 if(!adjustedClock) {
15374 whiteTimeRemaining = timeRemaining[0][currentMove];
15375 blackTimeRemaining = timeRemaining[1][currentMove];
15376 DisplayBothClocks();
15378 if (whiteFlag || blackFlag) {
15379 whiteFlag = blackFlag = 0;
15384 gameMode = EditGame;
15390 EditPositionEvent ()
15393 if (gameMode == EditPosition) {
15399 if (gameMode != EditGame) return;
15401 gameMode = EditPosition;
15404 CopyBoard(rightsBoard, nullBoard);
15405 if (currentMove > 0)
15406 CopyBoard(boards[0], boards[currentMove]);
15407 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15408 rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15410 blackPlaysFirst = !WhiteOnMove(currentMove);
15412 currentMove = forwardMostMove = backwardMostMove = 0;
15413 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15415 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15421 /* [DM] icsEngineAnalyze - possible call from other functions */
15422 if (appData.icsEngineAnalyze) {
15423 appData.icsEngineAnalyze = FALSE;
15425 DisplayMessage("",_("Close ICS engine analyze..."));
15427 if (first.analysisSupport && first.analyzing) {
15428 SendToBoth("exit\n");
15429 first.analyzing = second.analyzing = FALSE;
15431 thinkOutput[0] = NULLCHAR;
15435 EditPositionDone (Boolean fakeRights)
15437 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15439 startedFromSetupPosition = TRUE;
15440 InitChessProgram(&first, FALSE);
15441 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15443 boards[0][EP_STATUS] = EP_NONE;
15444 for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
15445 for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
15446 if(rightsBoard[r][f]) {
15447 ChessSquare p = boards[0][r][f];
15448 if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
15449 else if(p == king) boards[0][CASTLING][2] = f;
15450 else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
15451 else rightsBoard[r][f] = 2; // mark for second pass
15454 for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
15455 if(rightsBoard[r][f] == 2) {
15456 ChessSquare p = boards[0][r][f];
15457 if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
15458 if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
15462 SendToProgram("force\n", &first);
15463 if (blackPlaysFirst) {
15464 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15465 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15466 currentMove = forwardMostMove = backwardMostMove = 1;
15467 CopyBoard(boards[1], boards[0]);
15469 currentMove = forwardMostMove = backwardMostMove = 0;
15471 SendBoard(&first, forwardMostMove);
15472 if (appData.debugMode) {
15473 fprintf(debugFP, "EditPosDone\n");
15476 DisplayMessage("", "");
15477 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15478 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15479 gameMode = EditGame;
15481 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15482 ClearHighlights(); /* [AS] */
15485 /* Pause for `ms' milliseconds */
15486 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15488 TimeDelay (long ms)
15495 } while (SubtractTimeMarks(&m2, &m1) < ms);
15498 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15500 SendMultiLineToICS (char *buf)
15502 char temp[MSG_SIZ+1], *p;
15509 strncpy(temp, buf, len);
15514 if (*p == '\n' || *p == '\r')
15519 strcat(temp, "\n");
15521 SendToPlayer(temp, strlen(temp));
15525 SetWhiteToPlayEvent ()
15527 if (gameMode == EditPosition) {
15528 blackPlaysFirst = FALSE;
15529 DisplayBothClocks(); /* works because currentMove is 0 */
15530 } else if (gameMode == IcsExamining) {
15531 SendToICS(ics_prefix);
15532 SendToICS("tomove white\n");
15537 SetBlackToPlayEvent ()
15539 if (gameMode == EditPosition) {
15540 blackPlaysFirst = TRUE;
15541 currentMove = 1; /* kludge */
15542 DisplayBothClocks();
15544 } else if (gameMode == IcsExamining) {
15545 SendToICS(ics_prefix);
15546 SendToICS("tomove black\n");
15551 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15554 ChessSquare piece = boards[0][y][x];
15555 static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15556 static int lastVariant;
15557 int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15559 if (gameMode != EditPosition && gameMode != IcsExamining) return;
15561 switch (selection) {
15563 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15564 MarkTargetSquares(1);
15565 CopyBoard(currentBoard, boards[0]);
15566 CopyBoard(menuBoard, initialPosition);
15567 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15568 SendToICS(ics_prefix);
15569 SendToICS("bsetup clear\n");
15570 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15571 SendToICS(ics_prefix);
15572 SendToICS("clearboard\n");
15575 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15576 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15577 for (y = 0; y < BOARD_HEIGHT; y++) {
15578 if (gameMode == IcsExamining) {
15579 if (boards[currentMove][y][x] != EmptySquare) {
15580 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15584 } else if(boards[0][y][x] != DarkSquare) {
15585 if(boards[0][y][x] != p) nonEmpty++;
15586 boards[0][y][x] = p;
15590 CopyBoard(rightsBoard, nullBoard);
15591 if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15593 for(r = 0; r < BOARD_HEIGHT; r++) {
15594 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
15595 ChessSquare p = menuBoard[r][x];
15596 for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15599 menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
15600 DisplayMessage("Clicking clock again restores position", "");
15601 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15602 if(!nonEmpty) { // asked to clear an empty board
15603 CopyBoard(boards[0], menuBoard);
15605 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15606 CopyBoard(boards[0], initialPosition);
15608 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15609 && !CompareBoards(nullBoard, erasedBoard)) {
15610 CopyBoard(boards[0], erasedBoard);
15612 CopyBoard(erasedBoard, currentBoard);
15614 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15615 rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15618 if (gameMode == EditPosition) {
15619 DrawPosition(FALSE, boards[0]);
15624 SetWhiteToPlayEvent();
15628 SetBlackToPlayEvent();
15632 if (gameMode == IcsExamining) {
15633 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15634 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15637 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15638 if(x == BOARD_LEFT-2) {
15639 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15640 boards[0][y][1] = 0;
15642 if(x == BOARD_RGHT+1) {
15643 if(y >= gameInfo.holdingsSize) break;
15644 boards[0][y][BOARD_WIDTH-2] = 0;
15647 boards[0][y][x] = EmptySquare;
15648 DrawPosition(FALSE, boards[0]);
15653 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15654 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
15655 selection = (ChessSquare) (PROMOTED(piece));
15656 } else if(piece == EmptySquare) selection = WhiteSilver;
15657 else selection = (ChessSquare)((int)piece - 1);
15661 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15662 piece > (int)BlackMan && piece <= (int)BlackKing ) {
15663 selection = (ChessSquare) (DEMOTED(piece));
15664 } else if(piece == EmptySquare) selection = BlackSilver;
15665 else selection = (ChessSquare)((int)piece + 1);
15670 if(gameInfo.variant == VariantShatranj ||
15671 gameInfo.variant == VariantXiangqi ||
15672 gameInfo.variant == VariantCourier ||
15673 gameInfo.variant == VariantASEAN ||
15674 gameInfo.variant == VariantMakruk )
15675 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15681 if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15682 if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15688 if(gameInfo.variant == VariantXiangqi)
15689 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15690 if(gameInfo.variant == VariantKnightmate)
15691 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15692 if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15695 if (gameMode == IcsExamining) {
15696 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15697 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15698 PieceToChar(selection), AAA + x, ONE + y);
15701 rightsBoard[y][x] = hasRights;
15702 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15704 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15705 n = PieceToNumber(selection - BlackPawn);
15706 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15707 boards[0][BOARD_HEIGHT-1-n][0] = selection;
15708 boards[0][BOARD_HEIGHT-1-n][1]++;
15710 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15711 n = PieceToNumber(selection);
15712 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15713 boards[0][n][BOARD_WIDTH-1] = selection;
15714 boards[0][n][BOARD_WIDTH-2]++;
15717 boards[0][y][x] = selection;
15718 DrawPosition(TRUE, boards[0]);
15720 fromX = fromY = -1;
15728 DropMenuEvent (ChessSquare selection, int x, int y)
15730 ChessMove moveType;
15732 switch (gameMode) {
15733 case IcsPlayingWhite:
15734 case MachinePlaysBlack:
15735 if (!WhiteOnMove(currentMove)) {
15736 DisplayMoveError(_("It is Black's turn"));
15739 moveType = WhiteDrop;
15741 case IcsPlayingBlack:
15742 case MachinePlaysWhite:
15743 if (WhiteOnMove(currentMove)) {
15744 DisplayMoveError(_("It is White's turn"));
15747 moveType = BlackDrop;
15750 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15756 if (moveType == BlackDrop && selection < BlackPawn) {
15757 selection = (ChessSquare) ((int) selection
15758 + (int) BlackPawn - (int) WhitePawn);
15760 if (boards[currentMove][y][x] != EmptySquare) {
15761 DisplayMoveError(_("That square is occupied"));
15765 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15771 /* Accept a pending offer of any kind from opponent */
15773 if (appData.icsActive) {
15774 SendToICS(ics_prefix);
15775 SendToICS("accept\n");
15776 } else if (cmailMsgLoaded) {
15777 if (currentMove == cmailOldMove &&
15778 commentList[cmailOldMove] != NULL &&
15779 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15780 "Black offers a draw" : "White offers a draw")) {
15782 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15783 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15785 DisplayError(_("There is no pending offer on this move"), 0);
15786 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15789 /* Not used for offers from chess program */
15796 /* Decline a pending offer of any kind from opponent */
15798 if (appData.icsActive) {
15799 SendToICS(ics_prefix);
15800 SendToICS("decline\n");
15801 } else if (cmailMsgLoaded) {
15802 if (currentMove == cmailOldMove &&
15803 commentList[cmailOldMove] != NULL &&
15804 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15805 "Black offers a draw" : "White offers a draw")) {
15807 AppendComment(cmailOldMove, "Draw declined", TRUE);
15808 DisplayComment(cmailOldMove - 1, "Draw declined");
15811 DisplayError(_("There is no pending offer on this move"), 0);
15814 /* Not used for offers from chess program */
15821 /* Issue ICS rematch command */
15822 if (appData.icsActive) {
15823 SendToICS(ics_prefix);
15824 SendToICS("rematch\n");
15831 /* Call your opponent's flag (claim a win on time) */
15832 if (appData.icsActive) {
15833 SendToICS(ics_prefix);
15834 SendToICS("flag\n");
15836 switch (gameMode) {
15839 case MachinePlaysWhite:
15842 GameEnds(GameIsDrawn, "Both players ran out of time",
15845 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15847 DisplayError(_("Your opponent is not out of time"), 0);
15850 case MachinePlaysBlack:
15853 GameEnds(GameIsDrawn, "Both players ran out of time",
15856 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15858 DisplayError(_("Your opponent is not out of time"), 0);
15866 ClockClick (int which)
15867 { // [HGM] code moved to back-end from winboard.c
15868 if(which) { // black clock
15869 if (gameMode == EditPosition || gameMode == IcsExamining) {
15870 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15871 SetBlackToPlayEvent();
15872 } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15873 gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15874 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15875 } else if (shiftKey) {
15876 AdjustClock(which, -1);
15877 } else if (gameMode == IcsPlayingWhite ||
15878 gameMode == MachinePlaysBlack) {
15881 } else { // white clock
15882 if (gameMode == EditPosition || gameMode == IcsExamining) {
15883 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15884 SetWhiteToPlayEvent();
15885 } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15886 gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15887 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15888 } else if (shiftKey) {
15889 AdjustClock(which, -1);
15890 } else if (gameMode == IcsPlayingBlack ||
15891 gameMode == MachinePlaysWhite) {
15900 /* Offer draw or accept pending draw offer from opponent */
15902 if (appData.icsActive) {
15903 /* Note: tournament rules require draw offers to be
15904 made after you make your move but before you punch
15905 your clock. Currently ICS doesn't let you do that;
15906 instead, you immediately punch your clock after making
15907 a move, but you can offer a draw at any time. */
15909 SendToICS(ics_prefix);
15910 SendToICS("draw\n");
15911 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15912 } else if (cmailMsgLoaded) {
15913 if (currentMove == cmailOldMove &&
15914 commentList[cmailOldMove] != NULL &&
15915 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15916 "Black offers a draw" : "White offers a draw")) {
15917 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15918 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15919 } else if (currentMove == cmailOldMove + 1) {
15920 char *offer = WhiteOnMove(cmailOldMove) ?
15921 "White offers a draw" : "Black offers a draw";
15922 AppendComment(currentMove, offer, TRUE);
15923 DisplayComment(currentMove - 1, offer);
15924 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15926 DisplayError(_("You must make your move before offering a draw"), 0);
15927 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15929 } else if (first.offeredDraw) {
15930 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15932 if (first.sendDrawOffers) {
15933 SendToProgram("draw\n", &first);
15934 userOfferedDraw = TRUE;
15942 /* Offer Adjourn or accept pending Adjourn offer from opponent */
15944 if (appData.icsActive) {
15945 SendToICS(ics_prefix);
15946 SendToICS("adjourn\n");
15948 /* Currently GNU Chess doesn't offer or accept Adjourns */
15956 /* Offer Abort or accept pending Abort offer from opponent */
15958 if (appData.icsActive) {
15959 SendToICS(ics_prefix);
15960 SendToICS("abort\n");
15962 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15969 /* Resign. You can do this even if it's not your turn. */
15971 if (appData.icsActive) {
15972 SendToICS(ics_prefix);
15973 SendToICS("resign\n");
15975 switch (gameMode) {
15976 case MachinePlaysWhite:
15977 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15979 case MachinePlaysBlack:
15980 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15983 if (cmailMsgLoaded) {
15985 if (WhiteOnMove(cmailOldMove)) {
15986 GameEnds(BlackWins, "White resigns", GE_PLAYER);
15988 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15990 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
16001 StopObservingEvent ()
16003 /* Stop observing current games */
16004 SendToICS(ics_prefix);
16005 SendToICS("unobserve\n");
16009 StopExaminingEvent ()
16011 /* Stop observing current game */
16012 SendToICS(ics_prefix);
16013 SendToICS("unexamine\n");
16017 ForwardInner (int target)
16019 int limit; int oldSeekGraphUp = seekGraphUp;
16021 if (appData.debugMode)
16022 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
16023 target, currentMove, forwardMostMove);
16025 if (gameMode == EditPosition)
16028 seekGraphUp = FALSE;
16029 MarkTargetSquares(1);
16030 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16032 if (gameMode == PlayFromGameFile && !pausing)
16035 if (gameMode == IcsExamining && pausing)
16036 limit = pauseExamForwardMostMove;
16038 limit = forwardMostMove;
16040 if (target > limit) target = limit;
16042 if (target > 0 && moveList[target - 1][0]) {
16043 int fromX, fromY, toX, toY;
16044 toX = moveList[target - 1][2] - AAA;
16045 toY = moveList[target - 1][3] - ONE;
16046 if (moveList[target - 1][1] == '@') {
16047 if (appData.highlightLastMove) {
16048 SetHighlights(-1, -1, toX, toY);
16051 fromX = moveList[target - 1][0] - AAA;
16052 fromY = moveList[target - 1][1] - ONE;
16053 if (target == currentMove + 1) {
16054 if(moveList[target - 1][4] == ';') { // multi-leg
16055 killX = moveList[target - 1][5] - AAA;
16056 killY = moveList[target - 1][6] - ONE;
16058 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
16059 killX = killY = -1;
16061 if (appData.highlightLastMove) {
16062 SetHighlights(fromX, fromY, toX, toY);
16066 if (gameMode == EditGame || gameMode == AnalyzeMode ||
16067 gameMode == Training || gameMode == PlayFromGameFile ||
16068 gameMode == AnalyzeFile) {
16069 while (currentMove < target) {
16070 if(second.analyzing) SendMoveToProgram(currentMove, &second);
16071 SendMoveToProgram(currentMove++, &first);
16074 currentMove = target;
16077 if (gameMode == EditGame || gameMode == EndOfGame) {
16078 whiteTimeRemaining = timeRemaining[0][currentMove];
16079 blackTimeRemaining = timeRemaining[1][currentMove];
16081 DisplayBothClocks();
16082 DisplayMove(currentMove - 1);
16083 DrawPosition(oldSeekGraphUp, boards[currentMove]);
16084 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16085 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
16086 DisplayComment(currentMove - 1, commentList[currentMove]);
16088 ClearMap(); // [HGM] exclude: invalidate map
16095 if (gameMode == IcsExamining && !pausing) {
16096 SendToICS(ics_prefix);
16097 SendToICS("forward\n");
16099 ForwardInner(currentMove + 1);
16106 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16107 /* to optimze, we temporarily turn off analysis mode while we feed
16108 * the remaining moves to the engine. Otherwise we get analysis output
16111 if (first.analysisSupport) {
16112 SendToProgram("exit\nforce\n", &first);
16113 first.analyzing = FALSE;
16117 if (gameMode == IcsExamining && !pausing) {
16118 SendToICS(ics_prefix);
16119 SendToICS("forward 999999\n");
16121 ForwardInner(forwardMostMove);
16124 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16125 /* we have fed all the moves, so reactivate analysis mode */
16126 SendToProgram("analyze\n", &first);
16127 first.analyzing = TRUE;
16128 /*first.maybeThinking = TRUE;*/
16129 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16134 BackwardInner (int target)
16136 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16138 if (appData.debugMode)
16139 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16140 target, currentMove, forwardMostMove);
16142 if (gameMode == EditPosition) return;
16143 seekGraphUp = FALSE;
16144 MarkTargetSquares(1);
16145 fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16146 if (currentMove <= backwardMostMove) {
16148 DrawPosition(full_redraw, boards[currentMove]);
16151 if (gameMode == PlayFromGameFile && !pausing)
16154 if (moveList[target][0]) {
16155 int fromX, fromY, toX, toY;
16156 toX = moveList[target][2] - AAA;
16157 toY = moveList[target][3] - ONE;
16158 if (moveList[target][1] == '@') {
16159 if (appData.highlightLastMove) {
16160 SetHighlights(-1, -1, toX, toY);
16163 fromX = moveList[target][0] - AAA;
16164 fromY = moveList[target][1] - ONE;
16165 if (target == currentMove - 1) {
16166 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16168 if (appData.highlightLastMove) {
16169 SetHighlights(fromX, fromY, toX, toY);
16173 if (gameMode == EditGame || gameMode==AnalyzeMode ||
16174 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16175 while (currentMove > target) {
16176 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16177 // null move cannot be undone. Reload program with move history before it.
16179 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16180 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16182 SendBoard(&first, i);
16183 if(second.analyzing) SendBoard(&second, i);
16184 for(currentMove=i; currentMove<target; currentMove++) {
16185 SendMoveToProgram(currentMove, &first);
16186 if(second.analyzing) SendMoveToProgram(currentMove, &second);
16190 SendToBoth("undo\n");
16194 currentMove = target;
16197 if (gameMode == EditGame || gameMode == EndOfGame) {
16198 whiteTimeRemaining = timeRemaining[0][currentMove];
16199 blackTimeRemaining = timeRemaining[1][currentMove];
16201 DisplayBothClocks();
16202 DisplayMove(currentMove - 1);
16203 DrawPosition(full_redraw, boards[currentMove]);
16204 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16205 // [HGM] PV info: routine tests if comment empty
16206 DisplayComment(currentMove - 1, commentList[currentMove]);
16207 ClearMap(); // [HGM] exclude: invalidate map
16213 if (gameMode == IcsExamining && !pausing) {
16214 SendToICS(ics_prefix);
16215 SendToICS("backward\n");
16217 BackwardInner(currentMove - 1);
16224 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16225 /* to optimize, we temporarily turn off analysis mode while we undo
16226 * all the moves. Otherwise we get analysis output after each undo.
16228 if (first.analysisSupport) {
16229 SendToProgram("exit\nforce\n", &first);
16230 first.analyzing = FALSE;
16234 if (gameMode == IcsExamining && !pausing) {
16235 SendToICS(ics_prefix);
16236 SendToICS("backward 999999\n");
16238 BackwardInner(backwardMostMove);
16241 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16242 /* we have fed all the moves, so reactivate analysis mode */
16243 SendToProgram("analyze\n", &first);
16244 first.analyzing = TRUE;
16245 /*first.maybeThinking = TRUE;*/
16246 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16253 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16254 if (to >= forwardMostMove) to = forwardMostMove;
16255 if (to <= backwardMostMove) to = backwardMostMove;
16256 if (to < currentMove) {
16264 RevertEvent (Boolean annotate)
16266 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16269 if (gameMode != IcsExamining) {
16270 DisplayError(_("You are not examining a game"), 0);
16274 DisplayError(_("You can't revert while pausing"), 0);
16277 SendToICS(ics_prefix);
16278 SendToICS("revert\n");
16282 RetractMoveEvent ()
16284 switch (gameMode) {
16285 case MachinePlaysWhite:
16286 case MachinePlaysBlack:
16287 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16288 DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16291 if (forwardMostMove < 2) return;
16292 currentMove = forwardMostMove = forwardMostMove - 2;
16293 whiteTimeRemaining = timeRemaining[0][currentMove];
16294 blackTimeRemaining = timeRemaining[1][currentMove];
16295 DisplayBothClocks();
16296 DisplayMove(currentMove - 1);
16297 ClearHighlights();/*!! could figure this out*/
16298 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16299 SendToProgram("remove\n", &first);
16300 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16303 case BeginningOfGame:
16307 case IcsPlayingWhite:
16308 case IcsPlayingBlack:
16309 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16310 SendToICS(ics_prefix);
16311 SendToICS("takeback 2\n");
16313 SendToICS(ics_prefix);
16314 SendToICS("takeback 1\n");
16323 ChessProgramState *cps;
16325 switch (gameMode) {
16326 case MachinePlaysWhite:
16327 if (!WhiteOnMove(forwardMostMove)) {
16328 DisplayError(_("It is your turn"), 0);
16333 case MachinePlaysBlack:
16334 if (WhiteOnMove(forwardMostMove)) {
16335 DisplayError(_("It is your turn"), 0);
16340 case TwoMachinesPlay:
16341 if (WhiteOnMove(forwardMostMove) ==
16342 (first.twoMachinesColor[0] == 'w')) {
16348 case BeginningOfGame:
16352 SendToProgram("?\n", cps);
16356 TruncateGameEvent ()
16359 if (gameMode != EditGame) return;
16366 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16367 if (forwardMostMove > currentMove) {
16368 if (gameInfo.resultDetails != NULL) {
16369 free(gameInfo.resultDetails);
16370 gameInfo.resultDetails = NULL;
16371 gameInfo.result = GameUnfinished;
16373 forwardMostMove = currentMove;
16374 HistorySet(parseList, backwardMostMove, forwardMostMove,
16382 if (appData.noChessProgram) return;
16383 switch (gameMode) {
16384 case MachinePlaysWhite:
16385 if (WhiteOnMove(forwardMostMove)) {
16386 DisplayError(_("Wait until your turn."), 0);
16390 case BeginningOfGame:
16391 case MachinePlaysBlack:
16392 if (!WhiteOnMove(forwardMostMove)) {
16393 DisplayError(_("Wait until your turn."), 0);
16398 DisplayError(_("No hint available"), 0);
16401 SendToProgram("hint\n", &first);
16402 hintRequested = TRUE;
16406 SaveSelected (FILE *g, int dummy, char *dummy2)
16408 ListGame * lg = (ListGame *) gameList.head;
16412 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16413 DisplayError(_("Game list not loaded or empty"), 0);
16417 creatingBook = TRUE; // suppresses stuff during load game
16419 /* Get list size */
16420 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16421 if(lg->position >= 0) { // selected?
16422 LoadGame(f, nItem, "", TRUE);
16423 SaveGamePGN2(g); // leaves g open
16426 lg = (ListGame *) lg->node.succ;
16430 creatingBook = FALSE;
16438 ListGame * lg = (ListGame *) gameList.head;
16441 static int secondTime = FALSE;
16443 if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16444 DisplayError(_("Game list not loaded or empty"), 0);
16448 if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16451 DisplayNote(_("Book file exists! Try again for overwrite."));
16455 creatingBook = TRUE;
16456 secondTime = FALSE;
16458 /* Get list size */
16459 for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16460 if(lg->position >= 0) {
16461 LoadGame(f, nItem, "", TRUE);
16462 AddGameToBook(TRUE);
16465 lg = (ListGame *) lg->node.succ;
16468 creatingBook = FALSE;
16475 if (appData.noChessProgram) return;
16476 switch (gameMode) {
16477 case MachinePlaysWhite:
16478 if (WhiteOnMove(forwardMostMove)) {
16479 DisplayError(_("Wait until your turn."), 0);
16483 case BeginningOfGame:
16484 case MachinePlaysBlack:
16485 if (!WhiteOnMove(forwardMostMove)) {
16486 DisplayError(_("Wait until your turn."), 0);
16491 EditPositionDone(TRUE);
16493 case TwoMachinesPlay:
16498 SendToProgram("bk\n", &first);
16499 bookOutput[0] = NULLCHAR;
16500 bookRequested = TRUE;
16506 char *tags = PGNTags(&gameInfo);
16507 TagsPopUp(tags, CmailMsg());
16511 /* end button procedures */
16514 PrintPosition (FILE *fp, int move)
16518 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16519 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16520 char c = PieceToChar(boards[move][i][j]);
16521 fputc(c == '?' ? '.' : c, fp);
16522 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16525 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16526 fprintf(fp, "white to play\n");
16528 fprintf(fp, "black to play\n");
16532 PrintOpponents (FILE *fp)
16534 if (gameInfo.white != NULL) {
16535 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16541 /* Find last component of program's own name, using some heuristics */
16543 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16546 int local = (strcmp(host, "localhost") == 0);
16547 while (!local && (p = strchr(prog, ';')) != NULL) {
16549 while (*p == ' ') p++;
16552 if (*prog == '"' || *prog == '\'') {
16553 q = strchr(prog + 1, *prog);
16555 q = strchr(prog, ' ');
16557 if (q == NULL) q = prog + strlen(prog);
16559 while (p >= prog && *p != '/' && *p != '\\') p--;
16561 if(p == prog && *p == '"') p++;
16563 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16564 memcpy(buf, p, q - p);
16565 buf[q - p] = NULLCHAR;
16573 TimeControlTagValue ()
16576 if (!appData.clockMode) {
16577 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16578 } else if (movesPerSession > 0) {
16579 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16580 } else if (timeIncrement == 0) {
16581 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16583 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16585 return StrSave(buf);
16591 /* This routine is used only for certain modes */
16592 VariantClass v = gameInfo.variant;
16593 ChessMove r = GameUnfinished;
16596 if(keepInfo) return;
16598 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16599 r = gameInfo.result;
16600 p = gameInfo.resultDetails;
16601 gameInfo.resultDetails = NULL;
16603 ClearGameInfo(&gameInfo);
16604 gameInfo.variant = v;
16606 switch (gameMode) {
16607 case MachinePlaysWhite:
16608 gameInfo.event = StrSave( appData.pgnEventHeader );
16609 gameInfo.site = StrSave(HostName());
16610 gameInfo.date = PGNDate();
16611 gameInfo.round = StrSave("-");
16612 gameInfo.white = StrSave(first.tidy);
16613 gameInfo.black = StrSave(UserName());
16614 gameInfo.timeControl = TimeControlTagValue();
16617 case MachinePlaysBlack:
16618 gameInfo.event = StrSave( appData.pgnEventHeader );
16619 gameInfo.site = StrSave(HostName());
16620 gameInfo.date = PGNDate();
16621 gameInfo.round = StrSave("-");
16622 gameInfo.white = StrSave(UserName());
16623 gameInfo.black = StrSave(first.tidy);
16624 gameInfo.timeControl = TimeControlTagValue();
16627 case TwoMachinesPlay:
16628 gameInfo.event = StrSave( appData.pgnEventHeader );
16629 gameInfo.site = StrSave(HostName());
16630 gameInfo.date = PGNDate();
16633 snprintf(buf, MSG_SIZ, "%d", roundNr);
16634 gameInfo.round = StrSave(buf);
16636 gameInfo.round = StrSave("-");
16638 if (first.twoMachinesColor[0] == 'w') {
16639 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16640 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16642 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16643 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16645 gameInfo.timeControl = TimeControlTagValue();
16649 gameInfo.event = StrSave("Edited game");
16650 gameInfo.site = StrSave(HostName());
16651 gameInfo.date = PGNDate();
16652 gameInfo.round = StrSave("-");
16653 gameInfo.white = StrSave("-");
16654 gameInfo.black = StrSave("-");
16655 gameInfo.result = r;
16656 gameInfo.resultDetails = p;
16660 gameInfo.event = StrSave("Edited position");
16661 gameInfo.site = StrSave(HostName());
16662 gameInfo.date = PGNDate();
16663 gameInfo.round = StrSave("-");
16664 gameInfo.white = StrSave("-");
16665 gameInfo.black = StrSave("-");
16668 case IcsPlayingWhite:
16669 case IcsPlayingBlack:
16674 case PlayFromGameFile:
16675 gameInfo.event = StrSave("Game from non-PGN file");
16676 gameInfo.site = StrSave(HostName());
16677 gameInfo.date = PGNDate();
16678 gameInfo.round = StrSave("-");
16679 gameInfo.white = StrSave("?");
16680 gameInfo.black = StrSave("?");
16689 ReplaceComment (int index, char *text)
16695 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16696 pvInfoList[index-1].depth == len &&
16697 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16698 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16699 while (*text == '\n') text++;
16700 len = strlen(text);
16701 while (len > 0 && text[len - 1] == '\n') len--;
16703 if (commentList[index] != NULL)
16704 free(commentList[index]);
16707 commentList[index] = NULL;
16710 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16711 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16712 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16713 commentList[index] = (char *) malloc(len + 2);
16714 strncpy(commentList[index], text, len);
16715 commentList[index][len] = '\n';
16716 commentList[index][len + 1] = NULLCHAR;
16718 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16720 commentList[index] = (char *) malloc(len + 7);
16721 safeStrCpy(commentList[index], "{\n", 3);
16722 safeStrCpy(commentList[index]+2, text, len+1);
16723 commentList[index][len+2] = NULLCHAR;
16724 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16725 strcat(commentList[index], "\n}\n");
16730 CrushCRs (char *text)
16738 if (ch == '\r') continue;
16740 } while (ch != '\0');
16744 AppendComment (int index, char *text, Boolean addBraces)
16745 /* addBraces tells if we should add {} */
16750 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16751 if(addBraces == 3) addBraces = 0; else // force appending literally
16752 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16755 while (*text == '\n') text++;
16756 len = strlen(text);
16757 while (len > 0 && text[len - 1] == '\n') len--;
16758 text[len] = NULLCHAR;
16760 if (len == 0) return;
16762 if (commentList[index] != NULL) {
16763 Boolean addClosingBrace = addBraces;
16764 old = commentList[index];
16765 oldlen = strlen(old);
16766 while(commentList[index][oldlen-1] == '\n')
16767 commentList[index][--oldlen] = NULLCHAR;
16768 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16769 safeStrCpy(commentList[index], old, oldlen + len + 6);
16771 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16772 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16773 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16774 while (*text == '\n') { text++; len--; }
16775 commentList[index][--oldlen] = NULLCHAR;
16777 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16778 else strcat(commentList[index], "\n");
16779 strcat(commentList[index], text);
16780 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16781 else strcat(commentList[index], "\n");
16783 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16785 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16786 else commentList[index][0] = NULLCHAR;
16787 strcat(commentList[index], text);
16788 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16789 if(addBraces == TRUE) strcat(commentList[index], "}\n");
16794 FindStr (char * text, char * sub_text)
16796 char * result = strstr( text, sub_text );
16798 if( result != NULL ) {
16799 result += strlen( sub_text );
16805 /* [AS] Try to extract PV info from PGN comment */
16806 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16808 GetInfoFromComment (int index, char * text)
16810 char * sep = text, *p;
16812 if( text != NULL && index > 0 ) {
16815 int time = -1, sec = 0, deci;
16816 char * s_eval = FindStr( text, "[%eval " );
16817 char * s_emt = FindStr( text, "[%emt " );
16819 if( s_eval != NULL || s_emt != NULL ) {
16821 if(0) { // [HGM] this code is not finished, and could actually be detrimental
16826 if( s_eval != NULL ) {
16827 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16831 if( delim != ']' ) {
16836 if( s_emt != NULL ) {
16841 /* We expect something like: [+|-]nnn.nn/dd */
16844 if(*text != '{') return text; // [HGM] braces: must be normal comment
16846 sep = strchr( text, '/' );
16847 if( sep == NULL || sep < (text+4) ) {
16852 if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16853 if(p[1] == '(') { // comment starts with PV
16854 p = strchr(p, ')'); // locate end of PV
16855 if(p == NULL || sep < p+5) return text;
16856 // at this point we have something like "{(.*) +0.23/6 ..."
16857 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16858 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16859 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16861 time = -1; sec = -1; deci = -1;
16862 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16863 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16864 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16865 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
16869 if( score_lo < 0 || score_lo >= 100 ) {
16873 if(sec >= 0) time = 600*time + 10*sec; else
16874 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16876 score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16878 /* [HGM] PV time: now locate end of PV info */
16879 while( *++sep >= '0' && *sep <= '9'); // strip depth
16881 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16883 while( *++sep >= '0' && *sep <= '9'); // strip seconds
16885 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16886 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16897 pvInfoList[index-1].depth = depth;
16898 pvInfoList[index-1].score = score;
16899 pvInfoList[index-1].time = 10*time; // centi-sec
16900 if(*sep == '}') *sep = 0; else *--sep = '{';
16902 while(*p++ = *sep++)
16905 } // squeeze out space between PV and comment, and return both
16911 SendToProgram (char *message, ChessProgramState *cps)
16913 int count, outCount, error;
16916 if (cps->pr == NoProc) return;
16919 if (appData.debugMode) {
16922 fprintf(debugFP, "%ld >%-6s: %s",
16923 SubtractTimeMarks(&now, &programStartTime),
16924 cps->which, message);
16926 fprintf(serverFP, "%ld >%-6s: %s",
16927 SubtractTimeMarks(&now, &programStartTime),
16928 cps->which, message), fflush(serverFP);
16931 count = strlen(message);
16932 outCount = OutputToProcess(cps->pr, message, count, &error);
16933 if (outCount < count && !exiting
16934 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16935 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16936 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16937 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16938 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16939 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16940 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16941 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16943 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16944 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16945 gameInfo.result = res;
16947 gameInfo.resultDetails = StrSave(buf);
16949 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16950 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16955 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16959 ChessProgramState *cps = (ChessProgramState *)closure;
16961 if (isr != cps->isr) return; /* Killed intentionally */
16964 RemoveInputSource(cps->isr);
16965 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16966 _(cps->which), cps->program);
16967 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16968 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16969 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16970 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16971 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16972 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16974 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16975 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16976 gameInfo.result = res;
16978 gameInfo.resultDetails = StrSave(buf);
16980 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16981 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16983 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16984 _(cps->which), cps->program);
16985 RemoveInputSource(cps->isr);
16987 /* [AS] Program is misbehaving badly... kill it */
16988 if( count == -2 ) {
16989 DestroyChildProcess( cps->pr, 9 );
16993 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16998 if ((end_str = strchr(message, '\r')) != NULL)
16999 *end_str = NULLCHAR;
17000 if ((end_str = strchr(message, '\n')) != NULL)
17001 *end_str = NULLCHAR;
17003 if (appData.debugMode) {
17004 TimeMark now; int print = 1;
17005 char *quote = ""; char c; int i;
17007 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
17008 char start = message[0];
17009 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
17010 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
17011 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
17012 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
17013 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
17014 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
17015 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
17016 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
17017 sscanf(message, "hint: %c", &c)!=1 &&
17018 sscanf(message, "pong %c", &c)!=1 && start != '#') {
17019 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
17020 print = (appData.engineComments >= 2);
17022 message[0] = start; // restore original message
17026 fprintf(debugFP, "%ld <%-6s: %s%s\n",
17027 SubtractTimeMarks(&now, &programStartTime), cps->which,
17031 fprintf(serverFP, "%ld <%-6s: %s%s\n",
17032 SubtractTimeMarks(&now, &programStartTime), cps->which,
17034 message), fflush(serverFP);
17038 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
17039 if (appData.icsEngineAnalyze) {
17040 if (strstr(message, "whisper") != NULL ||
17041 strstr(message, "kibitz") != NULL ||
17042 strstr(message, "tellics") != NULL) return;
17045 HandleMachineMove(message, cps);
17050 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
17055 if( timeControl_2 > 0 ) {
17056 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
17057 tc = timeControl_2;
17060 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
17061 inc /= cps->timeOdds;
17062 st /= cps->timeOdds;
17064 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
17067 /* Set exact time per move, normally using st command */
17068 if (cps->stKludge) {
17069 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
17071 if (seconds == 0) {
17072 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
17074 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
17077 snprintf(buf, MSG_SIZ, "st %d\n", st);
17080 /* Set conventional or incremental time control, using level command */
17081 if (seconds == 0) {
17082 /* Note old gnuchess bug -- minutes:seconds used to not work.
17083 Fixed in later versions, but still avoid :seconds
17084 when seconds is 0. */
17085 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
17087 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
17088 seconds, inc/1000.);
17091 SendToProgram(buf, cps);
17093 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17094 /* Orthogonally, limit search to given depth */
17096 if (cps->sdKludge) {
17097 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17099 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17101 SendToProgram(buf, cps);
17104 if(cps->nps >= 0) { /* [HGM] nps */
17105 if(cps->supportsNPS == FALSE)
17106 cps->nps = -1; // don't use if engine explicitly says not supported!
17108 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17109 SendToProgram(buf, cps);
17114 ChessProgramState *
17116 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17118 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17119 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17125 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17127 char message[MSG_SIZ];
17130 /* Note: this routine must be called when the clocks are stopped
17131 or when they have *just* been set or switched; otherwise
17132 it will be off by the time since the current tick started.
17134 if (machineWhite) {
17135 time = whiteTimeRemaining / 10;
17136 otime = blackTimeRemaining / 10;
17138 time = blackTimeRemaining / 10;
17139 otime = whiteTimeRemaining / 10;
17141 /* [HGM] translate opponent's time by time-odds factor */
17142 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17144 if (time <= 0) time = 1;
17145 if (otime <= 0) otime = 1;
17147 snprintf(message, MSG_SIZ, "time %ld\n", time);
17148 SendToProgram(message, cps);
17150 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17151 SendToProgram(message, cps);
17155 EngineDefinedVariant (ChessProgramState *cps, int n)
17156 { // return name of n-th unknown variant that engine supports
17157 static char buf[MSG_SIZ];
17158 char *p, *s = cps->variants;
17159 if(!s) return NULL;
17160 do { // parse string from variants feature
17162 p = strchr(s, ',');
17163 if(p) *p = NULLCHAR;
17164 v = StringToVariant(s);
17165 if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17166 if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17167 if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17168 !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17169 !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17170 !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17171 if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17174 if(n < 0) return buf;
17180 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17183 int len = strlen(name);
17186 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17188 sscanf(*p, "%d", &val);
17190 while (**p && **p != ' ')
17192 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17193 SendToProgram(buf, cps);
17200 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17203 int len = strlen(name);
17204 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17206 sscanf(*p, "%d", loc);
17207 while (**p && **p != ' ') (*p)++;
17208 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17209 SendToProgram(buf, cps);
17216 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17219 int len = strlen(name);
17220 if (strncmp((*p), name, len) == 0
17221 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17223 len = strlen(*p) + 1; if(len < MSG_SIZ && !strcmp(name, "option")) len = MSG_SIZ; // make sure string options have enough space to change their value
17224 FREE(*loc); *loc = malloc(len);
17225 strncpy(*loc, *p, len);
17226 sscanf(*p, "%[^\"]", *loc); // should always fit, because we allocated at least strlen(*p)
17227 while (**p && **p != '\"') (*p)++;
17228 if (**p == '\"') (*p)++;
17229 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17230 SendToProgram(buf, cps);
17237 ParseOption (Option *opt, ChessProgramState *cps)
17238 // [HGM] options: process the string that defines an engine option, and determine
17239 // name, type, default value, and allowed value range
17241 char *p, *q, buf[MSG_SIZ];
17242 int n, min = (-1)<<31, max = 1<<31, def;
17244 opt->target = &opt->value; // OK for spin/slider and checkbox
17245 if(p = strstr(opt->name, " -spin ")) {
17246 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17247 if(max < min) max = min; // enforce consistency
17248 if(def < min) def = min;
17249 if(def > max) def = max;
17254 } else if((p = strstr(opt->name, " -slider "))) {
17255 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17256 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17257 if(max < min) max = min; // enforce consistency
17258 if(def < min) def = min;
17259 if(def > max) def = max;
17263 opt->type = Spin; // Slider;
17264 } else if((p = strstr(opt->name, " -string "))) {
17265 opt->textValue = p+9;
17266 opt->type = TextBox;
17267 opt->target = &opt->textValue;
17268 } else if((p = strstr(opt->name, " -file "))) {
17269 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17270 opt->target = opt->textValue = p+7;
17271 opt->type = FileName; // FileName;
17272 opt->target = &opt->textValue;
17273 } else if((p = strstr(opt->name, " -path "))) {
17274 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17275 opt->target = opt->textValue = p+7;
17276 opt->type = PathName; // PathName;
17277 opt->target = &opt->textValue;
17278 } else if(p = strstr(opt->name, " -check ")) {
17279 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17280 opt->value = (def != 0);
17281 opt->type = CheckBox;
17282 } else if(p = strstr(opt->name, " -combo ")) {
17283 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17284 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17285 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17286 opt->value = n = 0;
17287 while(q = StrStr(q, " /// ")) {
17288 n++; *q = 0; // count choices, and null-terminate each of them
17290 if(*q == '*') { // remember default, which is marked with * prefix
17294 cps->comboList[cps->comboCnt++] = q;
17296 cps->comboList[cps->comboCnt++] = NULL;
17298 opt->type = ComboBox;
17299 } else if(p = strstr(opt->name, " -button")) {
17300 opt->type = Button;
17301 } else if(p = strstr(opt->name, " -save")) {
17302 opt->type = SaveButton;
17303 } else return FALSE;
17304 *p = 0; // terminate option name
17305 // now look if the command-line options define a setting for this engine option.
17306 if(cps->optionSettings && cps->optionSettings[0])
17307 p = strstr(cps->optionSettings, opt->name); else p = NULL;
17308 if(p && (p == cps->optionSettings || p[-1] == ',')) {
17309 snprintf(buf, MSG_SIZ, "option %s", p);
17310 if(p = strstr(buf, ",")) *p = 0;
17311 if(q = strchr(buf, '=')) switch(opt->type) {
17313 for(n=0; n<opt->max; n++)
17314 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17317 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17321 opt->value = atoi(q+1);
17326 SendToProgram(buf, cps);
17328 *(int*) (opt->name + MSG_SIZ - 104) = opt->value; // hide default values somewhere
17329 if(opt->target == &opt->textValue) strncpy(opt->name + MSG_SIZ - 100, opt->textValue, 99);
17334 FeatureDone (ChessProgramState *cps, int val)
17336 DelayedEventCallback cb = GetDelayedEvent();
17337 if ((cb == InitBackEnd3 && cps == &first) ||
17338 (cb == SettingsMenuIfReady && cps == &second) ||
17339 (cb == LoadEngine) ||
17340 (cb == TwoMachinesEventIfReady)) {
17341 CancelDelayedEvent();
17342 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17343 } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17344 cps->initDone = val;
17345 if(val) cps->reload = FALSE, RefreshSettingsDialog(cps, val);
17348 /* Parse feature command from engine */
17350 ParseFeatures (char *args, ChessProgramState *cps)
17358 while (*p == ' ') p++;
17359 if (*p == NULLCHAR) return;
17361 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17362 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17363 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17364 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17365 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17366 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17367 if (BoolFeature(&p, "reuse", &val, cps)) {
17368 /* Engine can disable reuse, but can't enable it if user said no */
17369 if (!val) cps->reuse = FALSE;
17372 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17373 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17374 if (gameMode == TwoMachinesPlay) {
17375 DisplayTwoMachinesTitle();
17381 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17382 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17383 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17384 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17385 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17386 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17387 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17388 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17389 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17390 if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17391 if (IntFeature(&p, "done", &val, cps)) {
17392 FeatureDone(cps, val);
17395 /* Added by Tord: */
17396 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17397 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17398 /* End of additions by Tord */
17400 /* [HGM] added features: */
17401 if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17402 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17403 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17404 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17405 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17406 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17407 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17408 if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17409 if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17410 if(cps->nrOptions == 0) { ASSIGN(cps->option[0].name, _("Make Persistent -save")); ParseOption(&(cps->option[cps->nrOptions++]), cps); }
17411 FREE(cps->option[cps->nrOptions].name);
17412 cps->option[cps->nrOptions].name = q; q = NULL;
17413 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17414 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17415 SendToProgram(buf, cps);
17418 if(cps->nrOptions >= MAX_OPTIONS) {
17420 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17421 DisplayError(buf, 0);
17425 /* End of additions by HGM */
17427 /* unknown feature: complain and skip */
17429 while (*q && *q != '=') q++;
17430 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17431 SendToProgram(buf, cps);
17437 while (*p && *p != '\"') p++;
17438 if (*p == '\"') p++;
17440 while (*p && *p != ' ') p++;
17448 PeriodicUpdatesEvent (int newState)
17450 if (newState == appData.periodicUpdates)
17453 appData.periodicUpdates=newState;
17455 /* Display type changes, so update it now */
17456 // DisplayAnalysis();
17458 /* Get the ball rolling again... */
17460 AnalysisPeriodicEvent(1);
17461 StartAnalysisClock();
17466 PonderNextMoveEvent (int newState)
17468 if (newState == appData.ponderNextMove) return;
17469 if (gameMode == EditPosition) EditPositionDone(TRUE);
17471 SendToProgram("hard\n", &first);
17472 if (gameMode == TwoMachinesPlay) {
17473 SendToProgram("hard\n", &second);
17476 SendToProgram("easy\n", &first);
17477 thinkOutput[0] = NULLCHAR;
17478 if (gameMode == TwoMachinesPlay) {
17479 SendToProgram("easy\n", &second);
17482 appData.ponderNextMove = newState;
17486 NewSettingEvent (int option, int *feature, char *command, int value)
17490 if (gameMode == EditPosition) EditPositionDone(TRUE);
17491 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17492 if(feature == NULL || *feature) SendToProgram(buf, &first);
17493 if (gameMode == TwoMachinesPlay) {
17494 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17499 ShowThinkingEvent ()
17500 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17502 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17503 int newState = appData.showThinking
17504 // [HGM] thinking: other features now need thinking output as well
17505 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17507 if (oldState == newState) return;
17508 oldState = newState;
17509 if (gameMode == EditPosition) EditPositionDone(TRUE);
17511 SendToProgram("post\n", &first);
17512 if (gameMode == TwoMachinesPlay) {
17513 SendToProgram("post\n", &second);
17516 SendToProgram("nopost\n", &first);
17517 thinkOutput[0] = NULLCHAR;
17518 if (gameMode == TwoMachinesPlay) {
17519 SendToProgram("nopost\n", &second);
17522 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17526 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17528 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17529 if (pr == NoProc) return;
17530 AskQuestion(title, question, replyPrefix, pr);
17534 TypeInEvent (char firstChar)
17536 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17537 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17538 gameMode == AnalyzeMode || gameMode == EditGame ||
17539 gameMode == EditPosition || gameMode == IcsExamining ||
17540 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17541 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17542 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17543 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
17544 gameMode == Training) PopUpMoveDialog(firstChar);
17548 TypeInDoneEvent (char *move)
17551 int n, fromX, fromY, toX, toY;
17553 ChessMove moveType;
17556 if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17557 EditPositionPasteFEN(move);
17560 // [HGM] movenum: allow move number to be typed in any mode
17561 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17565 // undocumented kludge: allow command-line option to be typed in!
17566 // (potentially fatal, and does not implement the effect of the option.)
17567 // should only be used for options that are values on which future decisions will be made,
17568 // and definitely not on options that would be used during initialization.
17569 if(strstr(move, "!!! -") == move) {
17570 ParseArgsFromString(move+4);
17574 if (gameMode != EditGame && currentMove != forwardMostMove &&
17575 gameMode != Training) {
17576 DisplayMoveError(_("Displayed move is not current"));
17578 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17579 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17580 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17581 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17582 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17583 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17585 DisplayMoveError(_("Could not parse move"));
17591 DisplayMove (int moveNumber)
17593 char message[MSG_SIZ];
17595 char cpThinkOutput[MSG_SIZ];
17597 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17599 if (moveNumber == forwardMostMove - 1 ||
17600 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17602 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17604 if (strchr(cpThinkOutput, '\n')) {
17605 *strchr(cpThinkOutput, '\n') = NULLCHAR;
17608 *cpThinkOutput = NULLCHAR;
17611 /* [AS] Hide thinking from human user */
17612 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17613 *cpThinkOutput = NULLCHAR;
17614 if( thinkOutput[0] != NULLCHAR ) {
17617 for( i=0; i<=hiddenThinkOutputState; i++ ) {
17618 cpThinkOutput[i] = '.';
17620 cpThinkOutput[i] = NULLCHAR;
17621 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17625 if (moveNumber == forwardMostMove - 1 &&
17626 gameInfo.resultDetails != NULL) {
17627 if (gameInfo.resultDetails[0] == NULLCHAR) {
17628 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17630 snprintf(res, MSG_SIZ, " {%s} %s",
17631 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17637 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17638 DisplayMessage(res, cpThinkOutput);
17640 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17641 WhiteOnMove(moveNumber) ? " " : ".. ",
17642 parseList[moveNumber], res);
17643 DisplayMessage(message, cpThinkOutput);
17648 DisplayComment (int moveNumber, char *text)
17650 char title[MSG_SIZ];
17652 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17653 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17655 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17656 WhiteOnMove(moveNumber) ? " " : ".. ",
17657 parseList[moveNumber]);
17659 if (text != NULL && (appData.autoDisplayComment || commentUp))
17660 CommentPopUp(title, text);
17663 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17664 * might be busy thinking or pondering. It can be omitted if your
17665 * gnuchess is configured to stop thinking immediately on any user
17666 * input. However, that gnuchess feature depends on the FIONREAD
17667 * ioctl, which does not work properly on some flavors of Unix.
17670 Attention (ChessProgramState *cps)
17673 if (!cps->useSigint) return;
17674 if (appData.noChessProgram || (cps->pr == NoProc)) return;
17675 switch (gameMode) {
17676 case MachinePlaysWhite:
17677 case MachinePlaysBlack:
17678 case TwoMachinesPlay:
17679 case IcsPlayingWhite:
17680 case IcsPlayingBlack:
17683 /* Skip if we know it isn't thinking */
17684 if (!cps->maybeThinking) return;
17685 if (appData.debugMode)
17686 fprintf(debugFP, "Interrupting %s\n", cps->which);
17687 InterruptChildProcess(cps->pr);
17688 cps->maybeThinking = FALSE;
17693 #endif /*ATTENTION*/
17699 if (whiteTimeRemaining <= 0) {
17702 if (appData.icsActive) {
17703 if (appData.autoCallFlag &&
17704 gameMode == IcsPlayingBlack && !blackFlag) {
17705 SendToICS(ics_prefix);
17706 SendToICS("flag\n");
17710 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17712 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17713 if (appData.autoCallFlag) {
17714 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17721 if (blackTimeRemaining <= 0) {
17724 if (appData.icsActive) {
17725 if (appData.autoCallFlag &&
17726 gameMode == IcsPlayingWhite && !whiteFlag) {
17727 SendToICS(ics_prefix);
17728 SendToICS("flag\n");
17732 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17734 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17735 if (appData.autoCallFlag) {
17736 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17747 CheckTimeControl ()
17749 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17750 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17753 * add time to clocks when time control is achieved ([HGM] now also used for increment)
17755 if ( !WhiteOnMove(forwardMostMove) ) {
17756 /* White made time control */
17757 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17758 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17759 /* [HGM] time odds: correct new time quota for time odds! */
17760 / WhitePlayer()->timeOdds;
17761 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17763 lastBlack -= blackTimeRemaining;
17764 /* Black made time control */
17765 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17766 / WhitePlayer()->other->timeOdds;
17767 lastWhite = whiteTimeRemaining;
17772 DisplayBothClocks ()
17774 int wom = gameMode == EditPosition ?
17775 !blackPlaysFirst : WhiteOnMove(currentMove);
17776 DisplayWhiteClock(whiteTimeRemaining, wom);
17777 DisplayBlackClock(blackTimeRemaining, !wom);
17781 /* Timekeeping seems to be a portability nightmare. I think everyone
17782 has ftime(), but I'm really not sure, so I'm including some ifdefs
17783 to use other calls if you don't. Clocks will be less accurate if
17784 you have neither ftime nor gettimeofday.
17787 /* VS 2008 requires the #include outside of the function */
17788 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17789 #include <sys/timeb.h>
17792 /* Get the current time as a TimeMark */
17794 GetTimeMark (TimeMark *tm)
17796 #if HAVE_GETTIMEOFDAY
17798 struct timeval timeVal;
17799 struct timezone timeZone;
17801 gettimeofday(&timeVal, &timeZone);
17802 tm->sec = (long) timeVal.tv_sec;
17803 tm->ms = (int) (timeVal.tv_usec / 1000L);
17805 #else /*!HAVE_GETTIMEOFDAY*/
17808 // include <sys/timeb.h> / moved to just above start of function
17809 struct timeb timeB;
17812 tm->sec = (long) timeB.time;
17813 tm->ms = (int) timeB.millitm;
17815 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17816 tm->sec = (long) time(NULL);
17822 /* Return the difference in milliseconds between two
17823 time marks. We assume the difference will fit in a long!
17826 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17828 return 1000L*(tm2->sec - tm1->sec) +
17829 (long) (tm2->ms - tm1->ms);
17834 * Code to manage the game clocks.
17836 * In tournament play, black starts the clock and then white makes a move.
17837 * We give the human user a slight advantage if he is playing white---the
17838 * clocks don't run until he makes his first move, so it takes zero time.
17839 * Also, we don't account for network lag, so we could get out of sync
17840 * with GNU Chess's clock -- but then, referees are always right.
17843 static TimeMark tickStartTM;
17844 static long intendedTickLength;
17847 NextTickLength (long timeRemaining)
17849 long nominalTickLength, nextTickLength;
17851 if (timeRemaining > 0L && timeRemaining <= 10000L)
17852 nominalTickLength = 100L;
17854 nominalTickLength = 1000L;
17855 nextTickLength = timeRemaining % nominalTickLength;
17856 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17858 return nextTickLength;
17861 /* Adjust clock one minute up or down */
17863 AdjustClock (Boolean which, int dir)
17865 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17866 if(which) blackTimeRemaining += 60000*dir;
17867 else whiteTimeRemaining += 60000*dir;
17868 DisplayBothClocks();
17869 adjustedClock = TRUE;
17872 /* Stop clocks and reset to a fresh time control */
17876 (void) StopClockTimer();
17877 if (appData.icsActive) {
17878 whiteTimeRemaining = blackTimeRemaining = 0;
17879 } else if (searchTime) {
17880 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17881 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17882 } else { /* [HGM] correct new time quote for time odds */
17883 whiteTC = blackTC = fullTimeControlString;
17884 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17885 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17887 if (whiteFlag || blackFlag) {
17889 whiteFlag = blackFlag = FALSE;
17891 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17892 DisplayBothClocks();
17893 adjustedClock = FALSE;
17896 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17898 static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness...
17900 /* Decrement running clock by amount of time that has passed */
17905 long lastTickLength, fudge;
17908 if (!appData.clockMode) return;
17909 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17913 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17915 /* Fudge if we woke up a little too soon */
17916 fudge = intendedTickLength - lastTickLength;
17917 if (fudge < 0 || fudge > FUDGE) fudge = 0;
17919 if (WhiteOnMove(forwardMostMove)) {
17920 if(whiteNPS >= 0) lastTickLength = 0;
17921 tRemaining = whiteTimeRemaining -= lastTickLength;
17922 if( tRemaining < 0 && !appData.icsActive) {
17923 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17924 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17925 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17926 lastWhite= tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17929 if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining;
17930 DisplayWhiteClock(whiteTimeRemaining - fudge,
17931 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17934 if(blackNPS >= 0) lastTickLength = 0;
17935 tRemaining = blackTimeRemaining -= lastTickLength;
17936 if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17937 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17939 blackStartMove = forwardMostMove;
17940 lastBlack = tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17943 if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining;
17944 DisplayBlackClock(blackTimeRemaining - fudge,
17945 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17948 if (CheckFlags()) return;
17950 if(twoBoards) { // count down secondary board's clocks as well
17951 activePartnerTime -= lastTickLength;
17953 if(activePartner == 'W')
17954 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17956 DisplayBlackClock(activePartnerTime, TRUE);
17961 intendedTickLength = NextTickLength( tRemaining - fudge) + fudge;
17962 StartClockTimer(intendedTickLength);
17964 /* if the time remaining has fallen below the alarm threshold, sound the
17965 * alarm. if the alarm has sounded and (due to a takeback or time control
17966 * with increment) the time remaining has increased to a level above the
17967 * threshold, reset the alarm so it can sound again.
17970 if (appData.icsActive && appData.icsAlarm) {
17972 /* make sure we are dealing with the user's clock */
17973 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17974 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17977 if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) {
17978 alarmSounded = FALSE;
17979 } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) {
17981 alarmSounded = TRUE;
17987 /* A player has just moved, so stop the previously running
17988 clock and (if in clock mode) start the other one.
17989 We redisplay both clocks in case we're in ICS mode, because
17990 ICS gives us an update to both clocks after every move.
17991 Note that this routine is called *after* forwardMostMove
17992 is updated, so the last fractional tick must be subtracted
17993 from the color that is *not* on move now.
17996 SwitchClocks (int newMoveNr)
17998 long lastTickLength;
18000 int flagged = FALSE;
18004 if (StopClockTimer() && appData.clockMode) {
18005 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18006 if (!WhiteOnMove(forwardMostMove)) {
18007 if(blackNPS >= 0) lastTickLength = 0;
18008 blackTimeRemaining -= lastTickLength;
18009 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18010 // if(pvInfoList[forwardMostMove].time == -1)
18011 pvInfoList[forwardMostMove].time = // use GUI time
18012 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
18014 if(whiteNPS >= 0) lastTickLength = 0;
18015 whiteTimeRemaining -= lastTickLength;
18016 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18017 // if(pvInfoList[forwardMostMove].time == -1)
18018 pvInfoList[forwardMostMove].time =
18019 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
18021 flagged = CheckFlags();
18023 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
18024 CheckTimeControl();
18026 if (flagged || !appData.clockMode) return;
18028 switch (gameMode) {
18029 case MachinePlaysBlack:
18030 case MachinePlaysWhite:
18031 case BeginningOfGame:
18032 if (pausing) return;
18036 case PlayFromGameFile:
18044 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
18045 if(WhiteOnMove(forwardMostMove))
18046 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
18047 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
18051 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18052 whiteTimeRemaining : blackTimeRemaining);
18053 StartClockTimer(intendedTickLength);
18057 /* Stop both clocks */
18061 long lastTickLength;
18064 if (!StopClockTimer()) return;
18065 if (!appData.clockMode) return;
18069 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18070 if (WhiteOnMove(forwardMostMove)) {
18071 if(whiteNPS >= 0) lastTickLength = 0;
18072 whiteTimeRemaining -= lastTickLength;
18073 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
18075 if(blackNPS >= 0) lastTickLength = 0;
18076 blackTimeRemaining -= lastTickLength;
18077 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
18082 /* Start clock of player on move. Time may have been reset, so
18083 if clock is already running, stop and restart it. */
18087 (void) StopClockTimer(); /* in case it was running already */
18088 DisplayBothClocks();
18089 if (CheckFlags()) return;
18091 if (!appData.clockMode) return;
18092 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
18094 GetTimeMark(&tickStartTM);
18095 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18096 whiteTimeRemaining : blackTimeRemaining);
18098 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
18099 whiteNPS = blackNPS = -1;
18100 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
18101 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
18102 whiteNPS = first.nps;
18103 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18104 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18105 blackNPS = first.nps;
18106 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18107 whiteNPS = second.nps;
18108 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18109 blackNPS = second.nps;
18110 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18112 StartClockTimer(intendedTickLength);
18116 TimeString (long ms)
18118 long second, minute, hour, day;
18120 static char buf[40], moveTime[8];
18122 if (ms > 0 && ms <= 9900) {
18123 /* convert milliseconds to tenths, rounding up */
18124 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18126 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18130 /* convert milliseconds to seconds, rounding up */
18131 /* use floating point to avoid strangeness of integer division
18132 with negative dividends on many machines */
18133 second = (long) floor(((double) (ms + 999L)) / 1000.0);
18140 day = second / (60 * 60 * 24);
18141 second = second % (60 * 60 * 24);
18142 hour = second / (60 * 60);
18143 second = second % (60 * 60);
18144 minute = second / 60;
18145 second = second % 60;
18147 if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time
18148 else *moveTime = NULLCHAR;
18151 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ",
18152 sign, day, hour, minute, second, moveTime);
18154 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime);
18156 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime);
18163 * This is necessary because some C libraries aren't ANSI C compliant yet.
18166 StrStr (char *string, char *match)
18170 length = strlen(match);
18172 for (i = strlen(string) - length; i >= 0; i--, string++)
18173 if (!strncmp(match, string, length))
18180 StrCaseStr (char *string, char *match)
18184 length = strlen(match);
18186 for (i = strlen(string) - length; i >= 0; i--, string++) {
18187 for (j = 0; j < length; j++) {
18188 if (ToLower(match[j]) != ToLower(string[j]))
18191 if (j == length) return string;
18199 StrCaseCmp (char *s1, char *s2)
18204 c1 = ToLower(*s1++);
18205 c2 = ToLower(*s2++);
18206 if (c1 > c2) return 1;
18207 if (c1 < c2) return -1;
18208 if (c1 == NULLCHAR) return 0;
18216 return isupper(c) ? tolower(c) : c;
18223 return islower(c) ? toupper(c) : c;
18225 #endif /* !_amigados */
18232 if ((ret = (char *) malloc(strlen(s) + 1)))
18234 safeStrCpy(ret, s, strlen(s)+1);
18240 StrSavePtr (char *s, char **savePtr)
18245 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18246 safeStrCpy(*savePtr, s, strlen(s)+1);
18258 clock = time((time_t *)NULL);
18259 tm = localtime(&clock);
18260 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18261 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18262 return StrSave(buf);
18267 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18269 int i, j, fromX, fromY, toX, toY;
18270 int whiteToPlay, haveRights = nrCastlingRights;
18276 whiteToPlay = (gameMode == EditPosition) ?
18277 !blackPlaysFirst : (move % 2 == 0);
18280 /* Piece placement data */
18281 for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18282 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18284 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18285 if (boards[move][i][j] == EmptySquare) {
18287 } else { ChessSquare piece = boards[move][i][j];
18288 if (emptycount > 0) {
18289 if(emptycount<10) /* [HGM] can be >= 10 */
18290 *p++ = '0' + emptycount;
18291 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18294 if(PieceToChar(piece) == '+') {
18295 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18297 piece = (ChessSquare)(CHUDEMOTED(piece));
18299 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18300 if(*p = PieceSuffix(piece)) p++;
18302 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18303 p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18308 if (emptycount > 0) {
18309 if(emptycount<10) /* [HGM] can be >= 10 */
18310 *p++ = '0' + emptycount;
18311 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18318 /* [HGM] print Crazyhouse or Shogi holdings */
18319 if( gameInfo.holdingsWidth ) {
18320 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18322 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18323 piece = boards[move][i][BOARD_WIDTH-1];
18324 if( piece != EmptySquare )
18325 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18326 *p++ = PieceToChar(piece);
18328 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18329 piece = boards[move][BOARD_HEIGHT-i-1][0];
18330 if( piece != EmptySquare )
18331 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18332 *p++ = PieceToChar(piece);
18335 if( q == p ) *p++ = '-';
18341 *p++ = whiteToPlay ? 'w' : 'b';
18344 if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18345 haveRights = 0; q = p;
18346 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18347 piece = boards[move][0][i];
18348 if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18349 if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18352 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18353 piece = boards[move][BOARD_HEIGHT-1][i];
18354 if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18355 if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18358 if(p == q) *p++ = '-';
18362 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18365 if(q != overrideCastling+1) p[-1] = ' '; else --p;
18368 int handW=0, handB=0;
18369 if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18370 for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18371 for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18374 if(appData.fischerCastling) {
18375 if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18376 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18377 if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18379 /* [HGM] write directly from rights */
18380 if(boards[move][CASTLING][2] != NoRights &&
18381 boards[move][CASTLING][0] != NoRights )
18382 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18383 if(boards[move][CASTLING][2] != NoRights &&
18384 boards[move][CASTLING][1] != NoRights )
18385 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18388 for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18389 if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18391 if(boards[move][CASTLING][5] != NoRights &&
18392 boards[move][CASTLING][3] != NoRights )
18393 *p++ = boards[move][CASTLING][3] + AAA;
18394 if(boards[move][CASTLING][5] != NoRights &&
18395 boards[move][CASTLING][4] != NoRights )
18396 *p++ = boards[move][CASTLING][4] + AAA;
18400 /* [HGM] write true castling rights */
18401 if( nrCastlingRights == 6 ) {
18403 if(boards[move][CASTLING][0] != NoRights &&
18404 boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K';
18405 q = (boards[move][CASTLING][1] != NoRights &&
18406 boards[move][CASTLING][2] != NoRights );
18407 if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18408 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18409 if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18410 boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18414 if(boards[move][CASTLING][3] != NoRights &&
18415 boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k';
18416 q = (boards[move][CASTLING][4] != NoRights &&
18417 boards[move][CASTLING][5] != NoRights );
18419 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18420 if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18421 boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18426 if (q == p) *p++ = '-'; /* No castling rights */
18430 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
18431 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18432 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18433 /* En passant target square */
18434 if (move > backwardMostMove) {
18435 fromX = moveList[move - 1][0] - AAA;
18436 fromY = moveList[move - 1][1] - ONE;
18437 toX = moveList[move - 1][2] - AAA;
18438 toY = moveList[move - 1][3] - ONE;
18439 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18440 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18441 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18443 /* 2-square pawn move just happened */
18445 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18449 } else if(move == backwardMostMove) {
18450 // [HGM] perhaps we should always do it like this, and forget the above?
18451 if((signed char)boards[move][EP_STATUS] >= 0) {
18452 *p++ = boards[move][EP_STATUS] + AAA;
18453 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18465 { int i = 0, j=move;
18467 /* [HGM] find reversible plies */
18468 if (appData.debugMode) { int k;
18469 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18470 for(k=backwardMostMove; k<=forwardMostMove; k++)
18471 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18475 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18476 if( j == backwardMostMove ) i += initialRulePlies;
18477 sprintf(p, "%d ", i);
18478 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18480 /* Fullmove number */
18481 sprintf(p, "%d", (move / 2) + 1);
18482 } else *--p = NULLCHAR;
18484 return StrSave(buf);
18488 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18490 int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18492 int emptycount, virgin[BOARD_FILES];
18493 ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18497 for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) board[BOARD_HEIGHT-i][j] = DarkSquare;
18499 /* Piece placement data */
18500 for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18503 if (*p == '/' || *p == ' ' || *p == '[' ) {
18505 emptycount = gameInfo.boardWidth - j;
18506 while (emptycount--)
18507 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18508 if (*p == '/') p++;
18509 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18510 for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18511 for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18513 appData.NrRanks = gameInfo.boardHeight - i; i=0;
18516 #if(BOARD_FILES >= 10)*0
18517 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18518 p++; emptycount=10;
18519 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18520 while (emptycount--)
18521 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18523 } else if (*p == '*') {
18524 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18525 } else if (isdigit(*p)) {
18526 emptycount = *p++ - '0';
18527 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18528 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18529 while (emptycount--)
18530 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18531 } else if (*p == '<') {
18532 if(i == BOARD_HEIGHT-1) shuffle = 1;
18533 else if (i != 0 || !shuffle) return FALSE;
18535 } else if (shuffle && *p == '>') {
18536 p++; // for now ignore closing shuffle range, and assume rank-end
18537 } else if (*p == '?') {
18538 if (j >= gameInfo.boardWidth) return FALSE;
18539 if (i != 0 && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18540 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18541 } else if (*p == '+' || isalpha(*p)) {
18542 char *q, *s = SUFFIXES;
18543 if (j >= gameInfo.boardWidth) return FALSE;
18546 if(q = strchr(s, p[1])) p++;
18547 piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18548 if(piece == EmptySquare) return FALSE; /* unknown piece */
18549 piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18550 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18553 if(q = strchr(s, *p)) p++;
18554 piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18557 if(piece==EmptySquare) return FALSE; /* unknown piece */
18558 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18559 piece = (ChessSquare) (PROMOTED(piece));
18560 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18563 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18564 if(piece == king) wKingRank = i;
18565 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18571 while (*p == '/' || *p == ' ') p++;
18573 if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18575 /* [HGM] by default clear Crazyhouse holdings, if present */
18576 if(gameInfo.holdingsWidth) {
18577 for(i=0; i<BOARD_HEIGHT; i++) {
18578 board[i][0] = EmptySquare; /* black holdings */
18579 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18580 board[i][1] = (ChessSquare) 0; /* black counts */
18581 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18585 /* [HGM] look for Crazyhouse holdings here */
18586 while(*p==' ') p++;
18587 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18588 int swap=0, wcnt=0, bcnt=0;
18590 if(*p == '<') swap++, p++;
18591 if(*p == '-' ) p++; /* empty holdings */ else {
18592 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18593 /* if we would allow FEN reading to set board size, we would */
18594 /* have to add holdings and shift the board read so far here */
18595 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18597 if((int) piece >= (int) BlackPawn ) {
18598 i = (int)piece - (int)BlackPawn;
18599 i = PieceToNumber((ChessSquare)i);
18600 if( i >= gameInfo.holdingsSize ) return FALSE;
18601 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18602 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
18605 i = (int)piece - (int)WhitePawn;
18606 i = PieceToNumber((ChessSquare)i);
18607 if( i >= gameInfo.holdingsSize ) return FALSE;
18608 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
18609 board[i][BOARD_WIDTH-2]++; /* black holdings */
18613 if(subst) { // substitute back-rank question marks by holdings pieces
18614 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18615 int k, m, n = bcnt + 1;
18616 if(board[0][j] == ClearBoard) {
18617 if(!wcnt) return FALSE;
18619 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18620 board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18621 if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18625 if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18626 if(!bcnt) return FALSE;
18627 if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18628 for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18629 board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18630 if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18641 if(subst) return FALSE; // substitution requested, but no holdings
18643 while(*p == ' ') p++;
18647 if(appData.colorNickNames) {
18648 if( c == appData.colorNickNames[0] ) c = 'w'; else
18649 if( c == appData.colorNickNames[1] ) c = 'b';
18653 *blackPlaysFirst = FALSE;
18656 *blackPlaysFirst = TRUE;
18662 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18663 /* return the extra info in global variiables */
18665 while(*p==' ') p++;
18667 if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18668 if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18669 if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18672 /* set defaults in case FEN is incomplete */
18673 board[EP_STATUS] = EP_UNKNOWN;
18674 board[TOUCHED_W] = board[TOUCHED_B] = 0;
18675 for(i=0; i<nrCastlingRights; i++ ) {
18676 board[CASTLING][i] =
18677 appData.fischerCastling ? NoRights : initialRights[i];
18678 } /* assume possible unless obviously impossible */
18679 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18680 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18681 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18682 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18683 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18684 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18685 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18686 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18689 if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18692 while(isalpha(*p)) {
18693 if(isupper(*p)) w |= 1 << (*p++ - 'A');
18694 if(islower(*p)) b |= 1 << (*p++ - 'a');
18698 board[TOUCHED_W] = ~w;
18699 board[TOUCHED_B] = ~b;
18700 while(*p == ' ') p++;
18704 if(nrCastlingRights) {
18706 if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18707 if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18708 /* castling indicator present, so default becomes no castlings */
18709 for(i=0; i<nrCastlingRights; i++ ) {
18710 board[CASTLING][i] = NoRights;
18713 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18714 (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18715 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18716 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
18717 int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18719 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18720 if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18721 if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18723 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18724 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18725 if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18726 && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18727 if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18728 && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18731 for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18732 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18733 board[CASTLING][2] = whiteKingFile;
18734 if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18735 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18736 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18739 for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18740 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18741 board[CASTLING][2] = whiteKingFile;
18742 if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18743 if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18744 if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18747 for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18748 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18749 board[CASTLING][5] = blackKingFile;
18750 if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18751 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18752 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18755 for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18756 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18757 board[CASTLING][5] = blackKingFile;
18758 if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18759 if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18760 if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18763 default: /* FRC castlings */
18764 if(c >= 'a') { /* black rights */
18765 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18766 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18767 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18768 if(i == BOARD_RGHT) break;
18769 board[CASTLING][5] = i;
18771 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
18772 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
18774 board[CASTLING][3] = c;
18776 board[CASTLING][4] = c;
18777 } else { /* white rights */
18778 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18779 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18780 if(board[0][i] == WhiteKing) break;
18781 if(i == BOARD_RGHT) break;
18782 board[CASTLING][2] = i;
18783 c -= AAA - 'a' + 'A';
18784 if(board[0][c] >= WhiteKing) break;
18786 board[CASTLING][0] = c;
18788 board[CASTLING][1] = c;
18792 for(i=0; i<nrCastlingRights; i++)
18793 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18794 if(gameInfo.variant == VariantSChess)
18795 for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18796 if(fischer && shuffle) appData.fischerCastling = TRUE;
18797 if (appData.debugMode) {
18798 fprintf(debugFP, "FEN castling rights:");
18799 for(i=0; i<nrCastlingRights; i++)
18800 fprintf(debugFP, " %d", board[CASTLING][i]);
18801 fprintf(debugFP, "\n");
18804 while(*p==' ') p++;
18807 if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18809 /* read e.p. field in games that know e.p. capture */
18810 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
18811 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18812 gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18814 p++; board[EP_STATUS] = EP_NONE;
18816 char c = *p++ - AAA;
18818 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18819 if(*p >= '0' && *p <='9') p++;
18820 board[EP_STATUS] = c;
18825 if(sscanf(p, "%d", &i) == 1) {
18826 FENrulePlies = i; /* 50-move ply counter */
18827 /* (The move number is still ignored) */
18834 EditPositionPasteFEN (char *fen)
18837 Board initial_position;
18839 if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18840 DisplayError(_("Bad FEN position in clipboard"), 0);
18843 int savedBlackPlaysFirst = blackPlaysFirst;
18844 EditPositionEvent();
18845 blackPlaysFirst = savedBlackPlaysFirst;
18846 CopyBoard(boards[0], initial_position);
18847 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18848 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18849 DisplayBothClocks();
18850 DrawPosition(FALSE, boards[currentMove]);
18855 static char cseq[12] = "\\ ";
18858 set_cont_sequence (char *new_seq)
18863 // handle bad attempts to set the sequence
18865 return 0; // acceptable error - no debug
18867 len = strlen(new_seq);
18868 ret = (len > 0) && (len < sizeof(cseq));
18870 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18871 else if (appData.debugMode)
18872 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18877 reformat a source message so words don't cross the width boundary. internal
18878 newlines are not removed. returns the wrapped size (no null character unless
18879 included in source message). If dest is NULL, only calculate the size required
18880 for the dest buffer. lp argument indicats line position upon entry, and it's
18881 passed back upon exit.
18884 wrap (char *dest, char *src, int count, int width, int *lp)
18886 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18888 cseq_len = strlen(cseq);
18889 old_line = line = *lp;
18890 ansi = len = clen = 0;
18892 for (i=0; i < count; i++)
18894 if (src[i] == '\033')
18897 // if we hit the width, back up
18898 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18900 // store i & len in case the word is too long
18901 old_i = i, old_len = len;
18903 // find the end of the last word
18904 while (i && src[i] != ' ' && src[i] != '\n')
18910 // word too long? restore i & len before splitting it
18911 if ((old_i-i+clen) >= width)
18918 if (i && src[i-1] == ' ')
18921 if (src[i] != ' ' && src[i] != '\n')
18928 // now append the newline and continuation sequence
18933 strncpy(dest+len, cseq, cseq_len);
18941 dest[len] = src[i];
18945 if (src[i] == '\n')
18950 if (dest && appData.debugMode)
18952 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18953 count, width, line, len, *lp);
18954 show_bytes(debugFP, src, count);
18955 fprintf(debugFP, "\ndest: ");
18956 show_bytes(debugFP, dest, len);
18957 fprintf(debugFP, "\n");
18959 *lp = dest ? line : old_line;
18964 // [HGM] vari: routines for shelving variations
18965 Boolean modeRestore = FALSE;
18968 PushInner (int firstMove, int lastMove)
18970 int i, j, nrMoves = lastMove - firstMove;
18972 // push current tail of game on stack
18973 savedResult[storedGames] = gameInfo.result;
18974 savedDetails[storedGames] = gameInfo.resultDetails;
18975 gameInfo.resultDetails = NULL;
18976 savedFirst[storedGames] = firstMove;
18977 savedLast [storedGames] = lastMove;
18978 savedFramePtr[storedGames] = framePtr;
18979 framePtr -= nrMoves; // reserve space for the boards
18980 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18981 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18982 for(j=0; j<MOVE_LEN; j++)
18983 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18984 for(j=0; j<2*MOVE_LEN; j++)
18985 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18986 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18987 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18988 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18989 pvInfoList[firstMove+i-1].depth = 0;
18990 commentList[framePtr+i] = commentList[firstMove+i];
18991 commentList[firstMove+i] = NULL;
18995 forwardMostMove = firstMove; // truncate game so we can start variation
18999 PushTail (int firstMove, int lastMove)
19001 if(appData.icsActive) { // only in local mode
19002 forwardMostMove = currentMove; // mimic old ICS behavior
19005 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
19007 PushInner(firstMove, lastMove);
19008 if(storedGames == 1) GreyRevert(FALSE);
19009 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
19013 PopInner (Boolean annotate)
19016 char buf[8000], moveBuf[20];
19018 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
19019 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
19020 nrMoves = savedLast[storedGames] - currentMove;
19023 if(!WhiteOnMove(currentMove))
19024 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
19025 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
19026 for(i=currentMove; i<forwardMostMove; i++) {
19028 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
19029 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
19030 strcat(buf, moveBuf);
19031 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
19032 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
19036 for(i=1; i<=nrMoves; i++) { // copy last variation back
19037 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
19038 for(j=0; j<MOVE_LEN; j++)
19039 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
19040 for(j=0; j<2*MOVE_LEN; j++)
19041 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
19042 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
19043 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
19044 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
19045 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
19046 commentList[currentMove+i] = commentList[framePtr+i];
19047 commentList[framePtr+i] = NULL;
19049 if(annotate) AppendComment(currentMove+1, buf, FALSE);
19050 framePtr = savedFramePtr[storedGames];
19051 gameInfo.result = savedResult[storedGames];
19052 if(gameInfo.resultDetails != NULL) {
19053 free(gameInfo.resultDetails);
19055 gameInfo.resultDetails = savedDetails[storedGames];
19056 forwardMostMove = currentMove + nrMoves;
19060 PopTail (Boolean annotate)
19062 if(appData.icsActive) return FALSE; // only in local mode
19063 if(!storedGames) return FALSE; // sanity
19064 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
19066 PopInner(annotate);
19067 if(currentMove < forwardMostMove) ForwardEvent(); else
19068 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
19070 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
19076 { // remove all shelved variations
19078 for(i=0; i<storedGames; i++) {
19079 if(savedDetails[i])
19080 free(savedDetails[i]);
19081 savedDetails[i] = NULL;
19083 for(i=framePtr; i<MAX_MOVES; i++) {
19084 if(commentList[i]) free(commentList[i]);
19085 commentList[i] = NULL;
19087 framePtr = MAX_MOVES-1;
19092 LoadVariation (int index, char *text)
19093 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
19094 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
19095 int level = 0, move;
19097 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
19098 // first find outermost bracketing variation
19099 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
19100 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
19101 if(*p == '{') wait = '}'; else
19102 if(*p == '[') wait = ']'; else
19103 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
19104 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
19106 if(*p == wait) wait = NULLCHAR; // closing ]} found
19109 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19110 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19111 end[1] = NULLCHAR; // clip off comment beyond variation
19112 ToNrEvent(currentMove-1);
19113 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19114 // kludge: use ParsePV() to append variation to game
19115 move = currentMove;
19116 ParsePV(start, TRUE, TRUE);
19117 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19118 ClearPremoveHighlights();
19120 ToNrEvent(currentMove+1);
19123 int transparency[2];
19128 #define BUF_SIZ (2*MSG_SIZ)
19129 char *p, *q, buf[BUF_SIZ];
19130 if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19131 snprintf(buf, BUF_SIZ, "-theme %s", engineLine);
19132 ParseArgsFromString(buf);
19133 ActivateTheme(TRUE); // also redo colors
19137 if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19140 q = appData.themeNames;
19141 snprintf(buf, BUF_SIZ, "\"%s\"", nickName);
19142 if(appData.useBitmaps) {
19143 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt true -lbtf \"%s\"",
19144 Shorten(appData.liteBackTextureFile));
19145 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dbtf \"%s\" -lbtm %d -dbtm %d",
19146 Shorten(appData.darkBackTextureFile),
19147 appData.liteBackTextureMode,
19148 appData.darkBackTextureMode );
19150 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt false");
19152 if(!appData.useBitmaps || transparency[0]) {
19153 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -lsc %s", Col2Text(2) ); // lightSquareColor
19155 if(!appData.useBitmaps || transparency[1]) {
19156 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dsc %s", Col2Text(3) ); // darkSquareColor
19158 if(appData.useBorder) {
19159 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub true -border \"%s\"",
19162 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub false");
19164 if(appData.useFont) {
19165 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19166 appData.renderPiecesWithFont,
19167 appData.fontToPieceTable,
19168 Col2Text(9), // appData.fontBackColorWhite
19169 Col2Text(10) ); // appData.fontForeColorBlack
19171 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf false");
19172 if(appData.pieceDirectory[0]) {
19173 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -pid \"%s\"", Shorten(appData.pieceDirectory));
19174 if(appData.trueColors != 2) // 2 is a kludge to suppress this in WinBoard
19175 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -trueColors %s", appData.trueColors ? "true" : "false");
19177 if(!appData.pieceDirectory[0] || !appData.trueColors)
19178 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -wpc %s -bpc %s",
19179 Col2Text(0), // whitePieceColor
19180 Col2Text(1) ); // blackPieceColor
19182 snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19183 Col2Text(4), // highlightSquareColor
19184 Col2Text(5) ); // premoveHighlightColor
19185 appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19186 if(insert != q) insert[-1] = NULLCHAR;
19187 snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19190 ActivateTheme(FALSE);